Liwan Analytics 1.8.2
Overview
This guide covers how to launch and use the Liwan Analytics 1.8.2 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud BI and web analytics listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.
Liwan is a lightweight web analytics server. You add a small script to your web pages, and Liwan counts page views, unique visitors, time on site and bounce rate. It breaks them down by page, referrer, country, browser, operating system and device.
Liwan sets no cookies on your visitors and stores no IP address. A visitor is counted with a hash that changes every day. One program serves the dashboard, its API, the tracking script and the event endpoint. It keeps its data in an embedded DuckDB database (events) and a SQLite database (users and projects).
What you get:
- An admin password unique to your instance. The
adminaccount and its random 24-character password are created on the first boot of your instance and written to a root-only file. No account, password, session, certificate or key is baked into the image, and Liwan's one-time setup link is never issued. - Sign-in on the dashboard and its API. Every dashboard page and every
/api/dashboardcall needs a Liwan session (anHttpOnly,Secure,SameSite=Strictcookie). Projects are private unless you make one public. - Rate limiting per client. nginx limits sign-ins to 10 a minute and events to 20 a second per client address, and answers
429beyond that. The tracking script and the event endpoint are public by design, because your visitors' browsers must reach them. - Real client addresses only. nginx replaces any
X-Forwarded-For,X-Real-IPorForwardedheader a client sends with the address it connected from. Liwan trusts that header from localhost only, so nobody can choose the address Liwan counts or rate-limits. - HTTPS from the first boot. nginx serves Liwan on port 443 with a certificate that is generated on first boot for the public address of your instance. Port 80 redirects to HTTPS. Requests for a host name that the instance does not serve get no answer.
- Liwan listens on localhost only (
127.0.0.1:9042). It runs as its own unprivilegedliwanuser with no Linux capabilities and can write only to its data directory. Other local accounts (includingubuntu) cannot connect to it directly. - Other web sites cannot use your session. nginx refuses a sign-in or any change to the dashboard that comes from another site's page.
- No outbound reach. At the kernel level and on every boot, Liwan and nginx are refused any connection to the EC2 instance metadata service, link-local, private (RFC 1918), carrier-grade NAT and reserved addresses, the IPv4 and IPv6 ranges of your VPC, and the public internet. Liwan needs none of them to count visits.
- Built from source and checked. SecureAnyCloud builds Liwan 1.8.2 from the upstream liwan-v1.8.2 tag with no change to the source. The licence of every bundled component is listed on the instance. No compiler or build tool is on the image.
- Fully patched Ubuntu 24.04 LTS at build time.
- 24/7 support from SecureAnyCloud.
The image ships with a fictional demo site, Northwind Coffee (demo) for demo.example. It holds about 1,800 synthetic page views over 30 days, so you can explore the dashboard straight away. Step 6 shows how to track your own site.
What is on the image: Liwan 1.8.2 (/usr/local/bin/liwan), its configuration /etc/liwan/liwan.config.toml, nginx, the sqlite3 command-line shell, the tools liwan-reset-password, liwan-set-domain and liwan-selftest, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.
Prerequisites
- An AWS account that is subscribed to the listing, an EC2 key pair, and a VPC subnet with internet access.
- A security group that allows TCP 22 (SSH) from your own network and TCP 443 (Liwan) from everywhere your visitors are, because the tracking script and events come from their browsers. Also allow TCP 80 (redirects to HTTPS) if you want plain
http://links to work. - To track a live web site, a domain name for the instance and a certificate that your visitors' browsers trust (Step 8).
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Liwan Analytics 1.8.2 on Ubuntu 24.04 as the software version, select your Region, and choose Launch through EC2.
Choose m5.large, your key pair, a subnet and a security group as described above. A smaller instance such as t3.small serves a few small sites well. An Elastic IP keeps the address, and so the name on the certificate, stable across a stop and start.
To launch with the AWS CLI instead, replace the values in angle brackets:
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=liwan}]'Connect to your instance
Version SSH user
Liwan Analytics 1.8.2 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot reads the public address of the instance from EC2 instance metadata. It creates the data directory, the configuration, the certificate, the admin account and its password, and the demo site, and it writes the credentials file. The egress backstop then loads, and Liwan and nginx start:
systemctl is-active sac-liwan-egress liwan nginx
sudo test -f /var/lib/sac/data/liwan-firstboot.done && echo FIRSTBOOT_DONEConfirm that all three units are active and the first-boot marker is present:
active active active FIRSTBOOT_DONEConfirm that only SSH and nginx listen on the network and that Liwan listens on localhost only:
sudo ss -lntpH | awk '{print $4, $6}' | sed -E 's/users:\(\("([^"]+)".*/\1/' | grep -v ':53 ' | sort -u0.0.0.0:22 sshd 0.0.0.0:443 nginx 0.0.0.0:80 nginx 127.0.0.1:9042 liwan [::]:22 sshd [::]:443 nginx [::]:80 nginxGet your URL and password
sudo cat /root/liwan-credentials.txtOnly root can read the file. It holds the URL, the user name, the password and the fingerprint of the certificate:
# Liwan 1.8.2 (sac) on this instance. Open LIWAN_URL in a browser and sign in as LIWAN_USERNAME / LIWAN_PASSWORD.
# The password is unique to this instance. Change it in the dashboard (Settings > Account), or:
sudo liwan-reset-password
# The certificate is self-signed for this instance: compare its SHA-256 fingerprint with LIWAN_TLS_SHA256.
# The "Northwind Coffee (demo)" project is fictional sample data for demo.example; delete it in Settings when you like. LIWAN_URL=https://<public-ip>/ LIWAN_HOST=<public-ip> LIWAN_USERNAME=admin LIWAN_PASSWORD=<LIWAN_PASSWORD> LIWAN_TLS_SHA256=<64 hex digits, colon separated> LIWAN_ADDRESS_SOURCE=imdsv2-public-ipv4 GUIDE=https://secureanycloud.com/stacks/liwan-analytics-1-8-2/LIWAN_ADDRESS_SOURCE says where the address came from: the public IPv4 address from instance metadata, or the private address when the instance has no public one.
The image includes a self-test. It runs through nginx and is pinned to the certificate of this instance. It checks that:
- the dashboard API refuses every request without a session;
- a wrong password is refused;
- your password signs in and sees the page views of the demo site;
- signing out ends the session;
- the tracking script and the event endpoint work.
Run the self-test:
sudo liwan-selftestliwan-selftest: 7 dashboard API paths + setup refused without a session (401), no project listed and the demo project + its stats not found (404); a wrong password refused; this instance's password signs in (HttpOnly Secure SameSite=Strict), sees <n> demo page views, and its session ends at sign-out; script.js served; a page view accepted LIWAN_SELFTEST_OKEach run records one page view on the demo site.
Sign in and explore
Open LIWAN_URL in your browser. The certificate is self-signed for your instance, so the browser shows a warning once. To confirm that you are talking to your instance, open the certificate details and compare its SHA-256 fingerprint with LIWAN_TLS_SHA256, then continue. Sign in as admin with LIWAN_PASSWORD:
The home page shows each project you can see, with its views, unique visitors, average time on site, bounce rate and a chart for the chosen period. Use the date picker at the top right to change the period:
Click the name of a project to open it. Below the chart, tables break the views down by page, referrer, country and city, platform and browser, and device type. Click any row to filter the whole project by it, or click Show details for the full list. The demo data has no country, because the image ships no GeoIP database (Step 9).
Track your website
Liwan groups what it tracks in two levels. An entity is one web site (one tracking ID), and a project is a dashboard that shows one or more entities.
- Open Settings from the
adminmenu at the top right, then open Entities and create an entity, for example with IDmy-siteand namewww.example.com. - Open Projects, create a project (for example
My site) and add the entity to it. Leave the project Private so that a sign-in is needed to see it. - Add the tracking script to every page of your site, inside
<head>, with the domain of your instance and your entity ID:
<script type="module" data-entity="my-site" src="https://<your-domain>/script.js"></script>The script sends a page view to https://<your-domain>/api/event for each page. It sets no cookie and stores nothing in the visitor's browser.
Your visitors' browsers load the script and send events straight to the instance. The instance therefore needs a domain name and a certificate that they trust, because a browser will not load a script that uses a self-signed certificate (Step 8). Open port 443 in the security group to the internet.
Do not put the instance behind a load balancer or proxy. nginx records the address it is connected from, so every visit would appear to come from the load balancer.
The projects page lists your projects, their visibility and entities:
When you no longer need the demo site, delete the Northwind Coffee (demo) project and the demo.example (fictional) entity in Settings.
Other people can have their own accounts. Create a user under Settings, Users, and use Teams to give users access to chosen projects.
Change the password
Change the password in the dashboard under Settings, My Account. If you lose the password, generate a new random one over SSH. The command writes it to the credentials file as LIWAN_PASSWORD:
sudo liwan-reset-passwordTo choose your own password, use 12 characters or more. The command reads it from standard input and does not store it on the instance:
sudo liwan-reset-password --stdinA reset ends every session of that user. Liwan stores the password as an Argon2id hash.
Use your own domain and certificate
The certificate and the host names that nginx answers are set for the address the instance had on first boot. If you stop and start an instance that has no Elastic IP, its public address changes, so run liwan-set-domain with the new address.
To use a domain, point an A record (for example stats.example.com) at the public IP address of the instance, then run:
sudo liwan-set-domain <your-domain>The command sets the name that nginx accepts and the address that Liwan shows in its tracking snippets. It generates a new self-signed certificate for that name and updates LIWAN_URL, LIWAN_HOST and LIWAN_TLS_SHA256 in the credentials file. To serve more names or addresses, add them after the first (sudo liwan-set-domain <your-domain> <public-ip>).
For a certificate that your visitors' browsers trust, put the certificate chain for <your-domain> in /etc/ssl/liwan/liwan.crt and its private key in /etc/ssl/liwan/liwan.key (root-only, mode 600). Then run sudo systemctl reload nginx.
Outbound connections
Liwan, nginx and the first-boot service are refused any connection to the instance metadata service. The liwan and www-data accounts are refused every address except localhost: link-local, private, carrier-grade NAT, reserved and multicast addresses, the IPv4 and IPv6 ranges of your VPC (read from instance metadata on every boot) and the public internet.
Liwan needs no outbound connection to count visits. Site icons are not fetched, and the instance keeps the clock.
Two optional Liwan features do need an outbound connection. One is sign-in through your own OpenID Connect identity provider (Settings, Authentication). The other is the MaxMind GeoLite2 database for countries and cities (the [geoip] settings in /etc/liwan/liwan.config.toml, with your MaxMind account ID and licence key). To use them, list the networks they need in the allow list, which is empty by default:
cat /etc/sac/liwan-egress-allow# sac Liwan egress allow-list (empty by default: Liwan and nginx reach nothing but loopback).
# Liwan needs no network for analytics. Its OPTIONAL outbound features need one:
# * OIDC sign-in (configured in the dashboard: Settings > Authentication): your identity provider's addresses; # * MaxMind GeoIP downloads ([geoip] maxmind_account_id / maxmind_license_key): download.maxmind.com's addresses.
# List one CIDR per line. A PUBLIC range (or 0.0.0.0/0 for the whole internet, ::/0 for IPv6) is opened for the liwan
# account only, and never includes metadata, link-local, RFC 1918, CGNAT or this VPC's own ranges. A PRIVATE range (inside # 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10 or fc00::/7) is opened for liwan, e.g. an internal IdP. # Metadata and link-local addresses can never be allowed. Then run:
sudo systemctl reload sac-liwan-egressA public range is opened only after the ranges of your VPC are refused, so it never reaches into your VPC. This needs the VPC ranges from instance metadata for the same address family. A public IPv6 range is honoured only when your VPC has an IPv6 block, and it stays closed otherwise (use an IPv4 range instead). nginx is never opened, and the instance metadata service can never be allowed.
Security checks
These checks confirm that:
- the dashboard API needs a session;
- a wrong password and the one-time setup of Liwan are refused;
- a sign-in from the page of another site is refused;
- the tracking script is public;
- a host name that the instance does not serve gets no answer;
- plain HTTP redirects;
- other local accounts cannot reach Liwan directly.
H=$(sudo sed -n 's/^LIWAN_HOST=//p' /root/liwan-credentials.txt)
curl -sk -o /dev/null -w 'dashboard API without a session: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/api/dashboard/auth/me"
curl -sk -o /dev/null -w 'wrong password: %{http_code}\n' --resolve "$H:443:127.0.0.1" -H "Origin: https://$H" -H 'Content-Type: application/json' --data '{"username":"admin","password":"not-the-password"}' "https://$H/api/dashboard/auth/login"
curl -sk -o /dev/null -w 'one-time setup: %{http_code}\n' --resolve "$H:443:127.0.0.1" -H "Origin: https://$H" -H 'Content-Type: application/json' --data '{"token":"x","username":"x","password":"xxxxxxxxxxxx"}' "https://$H/api/dashboard/auth/setup"
curl -sk -o /dev/null -w 'sign-in from another site: %{http_code}\n' --resolve "$H:443:127.0.0.1" -H 'Origin: https://evil.example' -H 'Content-Type: application/json' --data '{"username":"admin","password":"x"}' "https://$H/api/dashboard/auth/login"
curl -sk -o /dev/null -w 'tracking script: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/script.js"
curl -sk -o /dev/null -w 'unknown host name: %{http_code}\n' --resolve evil.example:443:127.0.0.1 https://evil.example/ || true
curl -s -o /dev/null -w 'http: %{http_code}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -s -o /dev/null -w 'Liwan directly, as ubuntu: %{http_code}\n' http://127.0.0.1:9042/ || truedashboard API without a session: 401 wrong password: 401 one-time setup: 401 sign-in from another site: 403 tracking script: 200 unknown host name: 000 http: 301 Liwan directly, as ubuntu: 000Liwan and nginx are blocked from the instance metadata service and from reserved addresses:
for u in liwan nginx; do printf '%s ' "$u"; systemctl show -p IPAddressDeny --value "$u" | tr ' ' '\n' | sort | tr '\n' ' '; echo; done
sudo -u liwan curl -s -m 5 -o /dev/null -w 'metadata from the liwan account: %{http_code}\n' http://169.254.169.254/latest/meta-data/ || trueliwan 168.63.129.16/32 169.254.169.254/32 192.0.0.0/24 198.18.0.0/15 2001::/32 2002::/16 224.0.0.0/4 240.0.0.0/4 64:ff9b:1::/48 64:ff9b::/96 ::ffff:0.0.0.0/96 fd00:ec2::254/128 nginx 168.63.129.16/32 169.254.169.254/32 192.0.0.0/24 198.18.0.0/15 2001::/32 2002::/16 224.0.0.0/4 240.0.0.0/4 64:ff9b:1::/48 64:ff9b::/96 ::ffff:0.0.0.0/96 fd00:ec2::254/128 metadata from the liwan account: 000The egress backstop refuses the private ranges and your VPC. It reads the ranges of the VPC on every boot:
sudo iptables -S SAC-LIWAN-EGRESS | sed -n '1,4p' cat /run/sac/liwan-vpc-cidrs-N SAC-LIWAN-EGRESS -A SAC-LIWAN-EGRESS -d 127.0.0.0/8 -o lo -j RETURN -A SAC-LIWAN-EGRESS -d 169.254.0.0/16 -p tcp -j REJECT --reject-with tcp-reset -A SAC-LIWAN-EGRESS -d 169.254.0.0/16 -j REJECT --reject-with icmp-admin-prohibited <your VPC CIDR, for example 172.31.0.0/16>When your VPC has IPv6 blocks, they are listed in /run/sac/liwan-vpc6-cidrs and refused the same way in ip6tables.
Liwan runs as liwan, on localhost, with no Linux capabilities, and it cannot gain any:
P=$(systemctl show -p MainPID --value liwan) ps -o user:6=,args= -p "$P"
sudo grep -E '^(CapEff|CapBnd|CapAmb|NoNewPrivs)' /proc/"$P"/statusliwan /usr/local/bin/liwan --config /etc/liwan/liwan.config.toml CapEff: 0000000000000000 CapBnd: 0000000000000000 CapAmb: 0000000000000000 NoNewPrivs: 1Only root can read the credentials and the TLS key, and only the liwan user can read the Liwan data:
sudo stat -c '%a %U:%G %n' /root/liwan-credentials.txt /etc/ssl/liwan/liwan.key /etc/liwan/liwan.config.toml /var/lib/liwan/data600 root:root /root/liwan-credentials.txt 600 root:root /etc/ssl/liwan/liwan.key 640 root:liwan /etc/liwan/liwan.config.toml 700 liwan:liwan /var/lib/liwan/dataOther notes:
- Rate limits. Sign-ins: 10 a minute per client address (bursts of 10). Events: 20 a second (bursts of 40). Everything else: 20 a second (bursts of 200), which covers normal use of the dashboard.
- Logs. The nginx access log (
/var/log/nginx/access.log) holds the method, the path without any query string, the status and the size. The nginx error log records only critical errors. Liwan logs to the journal (journalctl -u liwan) without request details. - Response headers. nginx sends HSTS, a content security policy,
X-Content-Type-Options: nosniffandX-Frame-Options: DENY, and it sends no version number. - NVMe host identity. No NVMe-oF host identity ships in the image. If you install
nvme-cli, its host NQN and host ID belong to your own instance. - Who can reach it. Keep port 22 open only to your own networks.
Back up
Liwan keeps everything in /var/lib/liwan/data: liwan-app.sqlite (users, projects, entities, settings) and liwan-events.duckdb (events). Stop Liwan to get a consistent copy, then copy the directory to your computer:
sudo systemctl stop liwan
sudo tar -C /var/lib/liwan -czf /home/ubuntu/liwan-data.tar.gz data
sudo chown ubuntu: /home/ubuntu/liwan-data.tar.gz && sudo chmod 600 /home/ubuntu/liwan-data.tar.gz
sudo systemctl start liwanThen run this on your computer:
scp -i <your-key.pem> ubuntu@<public-ip>:liwan-data.tar.gz .An EBS snapshot of the volume of the instance is also a complete backup.
Keep it up to date
To apply Ubuntu updates, run:
sudo apt-get update && sudo apt-get upgrade -yNew Liwan releases arrive as new versions of this listing, built and checked the same way. To move to a new version, launch it, stop Liwan on both instances and copy /var/lib/liwan/data across (owned by liwan, mode 700). Read the Liwan changelog before you move.
Support and licensing
Liwan is open source under the Apache License 2.0. The image ships a SecureAnyCloud build of the liwan-v1.8.2 tag with no change to the source.
The licence and notice, the build record and the licence of every bundled Rust crate and JavaScript package are in /usr/share/doc/liwan. All of them are permissive: MIT, Apache-2.0, BSD, ISC and similar. DuckDB is MIT, SQLite is public domain and the Google Sans font of the dashboard is under the SIL Open Font License:
ls /usr/share/doc/liwanBUILD-INFO Cargo.lock LICENSE.md NOTICE.md THIRD-PARTY-CRATES.txt THIRD-PARTY-NPM.txtThe source code is at github.com/explodingcamera/liwan. Liwan is not endorsed by or affiliated with SecureAnyCloud. There is no per-user licence fee. The AMI price covers the SecureAnyCloud build, hardening, first-boot setup and support.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24 hour response SLA
The other versions on this listing are on the BI and web analytics guide.
Deploy on AWS
Launch Liwan Analytics 1.8.2 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com