Tianji 1.33.11 on AWS User Guide
Overview
This guide covers launching and using Tianji 1.33.11 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened Uptime Kuma, Gatus & Uptime and Status Pages AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other monitoring and status page products in the listing are described on the listing's guide hub.
Tianji is an open source (Apache-2.0) "insight hub" that puts several tools in one dashboard: privacy friendly website analytics (a small tracker script on your pages), uptime monitors (HTTP, TCP port, ping, DNS, push and more) with notifications, server status reported by a lightweight agent, public status pages, surveys and feeds. It is a Node.js server with a React interface, backed by PostgreSQL.
What the cloudimg image adds:
- HTTPS from the first boot. nginx serves Tianji on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS, and requests for any host name the instance does not serve are dropped.
- The default admin/admin login is gone. Tianji's database setup creates an
adminaccount with the published passwordadmin. On first boot, before Tianji ever starts, the image replaces it with a password unique to your instance, stored only as a bcrypt hash and in a root-only file. - No open sign up. Self registration is switched off (
ALLOW_REGISTER=false), so nobody can create an account from the sign in page or the API. - Per-instance secrets. The token signing secret and the Auth.js secret are generated for your instance alone and kept in a root-only environment file.
- A database with no password and no network port. First boot creates the PostgreSQL 16 cluster on your instance. PostgreSQL listens on no TCP port at all; Tianji reaches it over the local socket as its own system user (peer authentication), so there is no database password to leak.
- No phone home. Tianji's anonymous usage ping (sent from your browser to the Tianji project) is switched off with
DISABLE_ANONYMOUS_TELEMETRY=true. - Fail closed. Tianji and nginx refuse to start until first boot has finished, so the web interface is never reachable with the default login or without its certificate.
- Hardened defaults. Tianji listens on loopback only and runs as an unprivileged user in a systemd sandbox; Tianji, nginx and PostgreSQL are all blocked from the instance metadata service (monitors and webhooks fetch addresses your users enter), the real client address comes from the connection rather than from request headers, and the AMI requires IMDSv2.
- Patched dependencies. The server is built from the 1.33.11 source with 18 npm dependencies raised to their fixed releases (see Step 12).
- fwupd's online refresh is off. The
fwupd-refreshtimer and service are masked (fwupd stays installed); runsudo fwupdmgr refreshyourself if you use LVFS firmware updates. - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: Tianji 1.33.11 (/opt/tianji/app), Node.js 22.23.4 (/opt/node), PostgreSQL 16, nginx, Apprise (for Tianji's Apprise notification channel), 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 subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
- A security group that allows TCP 22 (SSH), TCP 443 (the dashboard, the website tracker and the reporting APIs) and TCP 80 (redirects to HTTPS). Restrict 22 to your own networks. If you track public websites, the visitors' browsers send page views to port 443, so 443 has to be reachable from wherever your visitors are.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Tianji 1.33.11 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above.
With the AWS CLI (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 \
--block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=20,VolumeType=gp3}' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=tianji}]'
First boot takes about a minute: it creates the database, replaces the default admin password, generates the secrets and the certificate, and then starts Tianji.
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| Tianji 1.33.11 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
PostgreSQL, Tianji and nginx are active, and the first boot and per boot host units have completed:
systemctl is-active postgresql@16-main tianji nginx tianji-firstboot tianji-configure
sudo test -f /var/lib/cloudimg/tianji-firstboot.done && echo FIRSTBOOT_DONE
active
active
active
active
active
FIRSTBOOT_DONE
Only SSH and nginx listen on public addresses. Tianji, its internal message queue (12351) and its UDP status ingress listen on loopback, and PostgreSQL has no TCP listener at all:
sudo ss -lntuHp | awk '{print $1, $5, $7}' | grep -Ev '127\.0\.0\.5[34]|:323 |:68 ' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g; s/\(\("([a-z0-9-]+)".*/(\1)/' | sort -u
tcp 0.0.0.0:22 users:(sshd)
tcp 0.0.0.0:443 users:(nginx)
tcp 0.0.0.0:80 users:(nginx)
tcp 127.0.0.1:12345 users:(node)
tcp 127.0.0.1:12351 users:(node)
tcp [::]:22 users:(sshd)
tcp [::]:443 users:(nginx)
tcp [::]:80 users:(nginx)
udp 127.0.0.1:12345 users:(node)
Step 4: Get your URL and admin password
sudo cat /root/tianji-credentials.txt
The file is readable by root only and holds the sign in URL, the user name and this instance's password:
# Tianji - generated on first boot by tianji-firstboot.service
# These credentials are unique to this instance. Store them somewhere safe.
TIANJI_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
TIANJI_ADMIN_USER=admin
TIANJI_ADMIN_PASSWORD=<TIANJI_ADMIN_PASSWORD>
#
# Open TIANJI_URL in a browser (self-signed certificate minted on this instance's first boot; port 80 redirects
# to https) and sign in with the username and password above. Open self-registration is disabled.
# Serve it under your own DNS name: sudo tianji-set-hostname <your-dns-name>
# Full deployment guide: https://www.cloudimg.co.uk/guides/tianji-1-33-11-on-ubuntu-24-04-aws/
The URL uses the instance's public DNS name from EC2 instance metadata, and the certificate also names the public and private IP addresses. If you stop and start the instance, the public name changes; at the next boot the image re-reads it from instance metadata and updates the URL, the certificate and /root/tianji-credentials.txt for you. To use your own DNS name instead, see Step 9.
Step 5: Sign in from your browser
Open the TIANJI_URL in your browser. The certificate is self signed, so your browser asks you to confirm it the first time (or install your own certificate, Step 9). Sign in as admin with the password from Step 4. There is no "register" link: accounts are created by an administrator.

The menu on the left opens each tool: Website (analytics), Application, Monitor, Servers, Telemetry, Pages (status pages), Survey, Feed, Short Link and the AI Gateway. The Website Overview shows every site you track with its visitors in the last 24 hours:

Open a website for its page views, visitors, bounce rate and visit time over a period you choose, plus the pages, referrers, browsers, operating systems and countries behind them. The Code button shows the tracker snippet to add to your pages:

A monitor checks a service on a schedule and shows its status, response times, uptime and, for HTTPS, the certificate's expiry:

A typical first setup: add each site under Website and put its tracker snippet on your pages, add monitors under Monitor (with a notification channel such as email, a webhook, Telegram, Feishu or Apprise under Settings > Notifications), publish a status page under Pages, and invite your team under Settings > Workspace.
Step 6: Check the defaults and try it from the command line
Port 80 redirects to HTTPS, only the static health check answers without signing in, Tianji's internal health endpoint is not exposed, and other host names are dropped:
H=$(sudo grep '^TIANJI_URL=' /root/tianji-credentials.txt | sed -E 's#^TIANJI_URL=https://([^/]+)/.*#\1#')
curl -s -o /dev/null -w 'http:// -> %{http_code} %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/website"
echo "/healthz -> $(curl -sk --resolve "$H:443:127.0.0.1" "https://$H/healthz")"
curl -sk -o /dev/null -w '/health -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/health"
curl -sk -o /dev/null -w 'other Host -> %{http_code} (connection closed)\n' -H 'Host: example.com' https://127.0.0.1/ || true
http:// -> 301 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/website
/healthz -> ok
/health -> HTTP 404
other Host -> 000 (connection closed)
The default admin / admin login (and other guesses) is refused, and nobody can register an account through either of Tianji's APIs:
H=$(sudo grep '^TIANJI_URL=' /root/tianji-credentials.txt | sed -E 's#^TIANJI_URL=https://([^/]+)/.*#\1#')
B="https://$H"; R=(--resolve "$H:443:127.0.0.1")
for PW in admin password; do
OUT=$(printf '{"username":"admin","password":"%s"}' "$PW" | curl -sk "${R[@]}" -H 'Content-Type: application/json' --data-binary @- "$B/open/login")
if [ -n "$(jq -r '.token // empty' <<<"$OUT")" ]; then echo "admin/$PW: ACCEPTED"; exit 1; fi
echo "admin/$PW: refused ($(jq -r '.message' <<<"$OUT"))"
done
C=$(printf '%s' '{"username":"someone","password":"S0me-passw0rd"}' | curl -sk "${R[@]}" -o /dev/null -w '%{http_code}' -H 'Content-Type: application/json' --data-binary @- "$B/open/register")
echo "OpenAPI /open/register -> HTTP $C"; [ "$C" = 404 ] || exit 1
OUT=$(printf '%s' '{"username":"someone","password":"S0me-passw0rd"}' | curl -sk "${R[@]}" -H "Origin: $B" -H 'Content-Type: application/json' --data-binary @- "$B/trpc/user.register")
echo "tRPC user.register -> $(jq -r '.error.data.code + ": " + .error.message' <<<"$OUT")"
[ "$(jq -r '.error.data.code' <<<"$OUT")" = FORBIDDEN ] || exit 1
admin/admin: refused (Password incorrected)
admin/password: refused (Password incorrected)
OpenAPI /open/register -> HTTP 404
tRPC user.register -> FORBIDDEN: Not allow register
The following signs in as admin through Tianji's OpenAPI, adds a Shop website, sends three page views exactly as the tracker script in a visitor's browser does, and creates an HTTP uptime monitor. The password goes to curl on standard input and the session token travels in an Authorization header read from a root-only file in memory, so neither appears in the process list, your shell history or the web server's access log:
sudo bash <<'EOF_TIANJI'
set -euo pipefail
F=/root/tianji-credentials.txt
P=$(grep '^TIANJI_ADMIN_PASSWORD=' "$F" | cut -d= -f2-)
H=$(grep '^TIANJI_URL=' "$F" | sed -E 's#^TIANJI_URL=https://([^/]+)/.*#\1#')
B="https://$H"; R=(--resolve "$H:443:127.0.0.1"); A=$(mktemp -p /run); trap 'rm -f "$A"' EXIT
# Sign in as admin: the JSON body goes to curl on stdin, so the password never appears in the process list
LOGIN=$(printf '{"username":"admin","password":"%s"}' "$P" | curl -sk "${R[@]}" -H 'Content-Type: application/json' --data-binary @- "$B/open/login")
T=$(jq -r '.token // empty' <<<"$LOGIN"); W=$(jq -r '.info.currentWorkspaceId // empty' <<<"$LOGIN")
[ -n "$T" ] && [ -n "$W" ] || { echo "sign-in failed"; exit 1; }
printf 'Authorization: Bearer %s\n' "$T" > "$A"
api() { curl -sk "${R[@]}" -H @"$A" -H 'Content-Type: application/json' "$@"; }
# A website (reused if it already exists)
WID=$(api "$B/open/workspace/$W/website/all" | jq -r '[.[] | select(.name == "Shop")][0].id // empty')
[ -n "$WID" ] || WID=$(printf '%s' '{"name":"Shop","domain":"shop.example.com"}' | api --data-binary @- "$B/open/workspace/$W/website/add" | jq -r '.id // empty')
[ -n "$WID" ] || { echo "website creation failed"; exit 1; }
echo "website: Shop"
# Three page views, sent exactly as the tracker script in a visitor's browser sends them
for PG in / /pricing /checkout; do
C=$(printf '{"type":"event","payload":{"website":"%s","hostname":"shop.example.com","url":"%s","screen":"1920x1080","language":"en-GB","title":"Shop","referrer":""}}' "$WID" "$PG" \
| curl -sk "${R[@]}" -o /dev/null -w '%{http_code}' -A 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0 Safari/537.36' \
-H 'Content-Type: application/json' --data-binary @- "$B/api/website/send")
[ "$C" = 200 ] || { echo "page view $PG was not accepted (HTTP $C)"; exit 1; }
echo "page view $PG: HTTP $C"
done
# An HTTP uptime monitor (reused if it already exists)
MID=$(api "$B/open/workspace/$W/monitor/all" | jq -r '[.[] | select(.name == "AWS check IP")][0].id // empty')
if [ -z "$MID" ]; then
MID=$(printf '%s' '{"name":"AWS check IP","type":"http","interval":60,"maxRetries":0,"payload":{"url":"https://checkip.amazonaws.com/","method":"GET","timeout":10,"maxRedirects":3}}' \
| api --data-binary @- "$B/open/workspace/$W/monitor/upsert" | jq -r '.id // empty')
fi
[ -n "$MID" ] || { echo "monitor creation failed"; exit 1; }
echo "monitor: AWS check IP (HTTP, every 60 s)"
for i in $(seq 1 30); do
V=$(api "$B/open/workspace/$W/monitor/$MID/recentData?take=1" | jq -r '.[0].value // empty' 2>/dev/null || true)
[ -n "$V" ] && break; sleep 2
done
case "$V" in ''|-1) echo "the monitor has no successful check yet (value '$V')"; exit 1 ;; *) echo "first check: UP" ;; esac
EOF_TIANJI
website: Shop
page view /: HTTP 200
page view /pricing: HTTP 200
page view /checkout: HTTP 200
monitor: AWS check IP (HTTP, every 60 s)
first check: UP
The website and monitor now show in the dashboard. The monitor runs inside Tianji, which is blocked from the instance metadata service, so a monitor (or webhook) pointed at 169.254.169.254 cannot read the instance's credentials.
Step 7: Track your websites and servers
Websites. Open the website in the dashboard and choose Code to copy its snippet, then add it to every page of your site:
<script async defer src="https://<your-tianji-host>/tracker.js" data-website-id="<website-id>"></script>
Visitors' browsers then send page views to https://<your-tianji-host>/api/website/send. That endpoint (with the telemetry, server status, push, application and OpenAPI endpoints) accepts requests from other web sites by design; the dashboard itself refuses any state changing request or WebSocket upgrade whose Origin is another site. For the browsers to trust the tracker, serve Tianji under your own DNS name with a trusted certificate (Step 9).
Visitor locations. Countries and cities come from a MaxMind GeoLite2 City database. The image does not include one, because MaxMind's licence requires each user to keep the database current; without it the location columns stay empty and everything else works. To enable them, create a free MaxMind account, download GeoLite2-City.mmdb to the instance, then install it from that directory and restart Tianji:
sudo install -m 0644 GeoLite2-City.mmdb /opt/tianji/app/geo/GeoLite2-City.mmdb
sudo systemctl restart tianji
Replace the file the same way whenever MaxMind publishes an update (weekly), as their licence requires.
Servers. Under Servers > Add, Tianji shows the command for its open source reporter agent (tianji-reporter), which you install on each server you want to watch. It reports over HTTPS to https://<your-tianji-host> by default. Tianji's alternative UDP ingress is bound to loopback on this image, so keep the reporter in its default HTTP mode.
Step 8: Security checks
The environment file holds the database URL (a local socket, no password), this instance's token signing and Auth.js secrets and the hardened settings; it is readable by root only and systemd reads it before starting Tianji as its own user. The first command prints the variable names only:
sudo stat -c '%a %U:%G %n' /etc/tianji/tianji.env /root/tianji-credentials.txt /etc/ssl/tianji/tianji.key
sudo grep -oE '^[A-Z_]+=' /etc/tianji/tianji.env
sudo grep -E '^(DATABASE_URL|ALLOW_REGISTER|DISABLE_ANONYMOUS_TELEMETRY|CLIENT_IP_HEADER|TIANJI_LISTEN_HOST)=' /etc/tianji/tianji.env
600 root:root /etc/tianji/tianji.env
600 root:root /root/tianji-credentials.txt
600 root:root /etc/ssl/tianji/tianji.key
DATABASE_URL=
JWT_SECRET=
AUTH_SECRET=
PORT=
TIANJI_LISTEN_HOST=
NODE_ENV=
ALLOW_REGISTER=
ALLOW_OPENAPI=
DISABLE_ANONYMOUS_TELEMETRY=
CLIENT_IP_HEADER=
AUTH_USE_SECURE_COOKIES=
DATABASE_URL=postgresql://tianji@localhost/tianji?host=/var/run/postgresql&schema=public
TIANJI_LISTEN_HOST=127.0.0.1
ALLOW_REGISTER=false
DISABLE_ANONYMOUS_TELEMETRY=true
CLIENT_IP_HEADER=x-real-ip
PostgreSQL has no network listener, no role has a password, local connections are allowed only for the matching operating system user, and the seeded admin/admin password hash is gone:
sudo -u postgres psql -XtAc "SHOW listen_addresses" | sed 's/^$/(empty: no TCP listener)/'
sudo -u postgres psql -XtAc "SELECT count(*) || ' roles with a password' FROM pg_authid WHERE rolpassword IS NOT NULL"
sudo -u postgres psql -XtAc "SELECT type, database, user_name, auth_method FROM pg_hba_file_rules WHERE type = 'local'"
sudo -u postgres psql -XtAd tianji -c "SELECT count(*) || ' user(s); seeded admin/admin hash present: ' || bool_or(password = '\$2a\$10\$V384zbubCaykNgBHs5mag.AngiWhAgRR.KLoxal7xh8sfOBwnASN2') FROM \"User\""
(empty: no TCP listener)
0 roles with a password
local|{all}|{all}|peer
local|{replication}|{all}|peer
1 user(s); seeded admin/admin hash present: false
Tianji runs unprivileged in a sandbox with no capabilities; Tianji, nginx and PostgreSQL cannot reach the instance metadata service; and fwupd's online refresh is masked:
systemctl show tianji -p User -p ProtectSystem -p NoNewPrivileges -p CapabilityBoundingSet
systemctl show tianji nginx postgresql@16-main -p Id -p IPAddressDeny
for U in fwupd-refresh.timer fwupd-refresh.service; do S=$(systemctl is-enabled "$U" || true); echo "$U: $S"; [ "$S" = masked ] || exit 1; done
CapabilityBoundingSet=
User=tianji
ProtectSystem=strict
NoNewPrivileges=yes
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
Id=tianji.service
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
Id=nginx.service
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
Id=postgresql@16-main.service
fwupd-refresh.timer: masked
fwupd-refresh.service: masked
nginx passes Tianji the configured host name and the real client address from the connection, and blanks the other client address headers, so a visitor cannot fake the address your analytics record. A state changing request from another web site is refused, registration and the anonymous usage ping are off in the configuration Tianji serves to browsers, the certificate was generated on this instance, and SSH allows key based logins only:
grep -hE 'proxy_set_header (Host|X-Forwarded-For|X-Real-IP|X-Client-IP|X-Forwarded-Host) ' /etc/nginx/tianji-proxy.conf
H=$(sudo grep '^TIANJI_URL=' /root/tianji-credentials.txt | sed -E 's#^TIANJI_URL=https://([^/]+)/.*#\1#')
curl -sk -o /dev/null -w 'cross-site POST to the dashboard API -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" -H 'Origin: https://evil.example' -H 'Content-Type: application/json' --data-binary '{}' "https://$H/trpc/user.info"
curl -sk "https://$H/trpc/global.config" --resolve "$H:443:127.0.0.1" | jq -c '.result.data | {allowRegister, disableAnonymousTelemetry}'
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication|x11forwarding) '
proxy_set_header Host $tianji_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host "";
proxy_set_header X-Client-IP "";
cross-site POST to the dashboard API -> HTTP 403
{"allowRegister":false,"disableAnonymousTelemetry":true}
subject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name:
DNS:ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost
permitrootlogin no
passwordauthentication no
kbdinteractiveauthentication no
x11forwarding no
Step 9: Your own DNS name and certificate
Point a DNS record at the instance, then set the name Tianji is served as. This mints a new self signed certificate for the name, points Tianji's public URL at it, updates nginx and /root/tianji-credentials.txt, and keeps the name across reboots and stop/start:
sudo tianji-set-hostname <your-domain>
To use a certificate from your own certificate authority (so visitors' browsers accept the tracker without warnings), copy it over the generated pair and reload nginx (running tianji-set-hostname again replaces it with a self signed one):
sudo install -m 0644 <your-domain>.crt /etc/ssl/tianji/tianji.crt
sudo install -m 0600 <your-domain>.key /etc/ssl/tianji/tianji.key
sudo nginx -t && sudo systemctl reload nginx
nginx serves only the configured names. Tianji's custom domains for status pages need their own names served too, so publish status pages under https://<your-domain>/status/<slug>.
Step 10: Backups, password and maintenance
Everything Tianji knows lives in the tianji PostgreSQL database. Back it up together with /etc/tianji/tianji.env (without the signing secret, every signed in session ends after a restore):
sudo -u postgres pg_dump -Fc tianji | sudo tee <backup-dir>/tianji-$(date +%F).dump >/dev/null
sudo install -m 0600 /etc/tianji/tianji.env <backup-dir>/tianji-$(date +%F).env
To change the admin password, sign in and open Settings > Profile. Afterwards update or delete TIANJI_ADMIN_PASSWORD in /root/tianji-credentials.txt so it does not hold an old password.
Tianji logs to the journal:
sudo journalctl -u tianji -o cat --no-pager -g 'Server is listening on port' | sed 's/\x1b\[[0-9;]*m//g' | tail -1
2026-10-21 18:14:20.583 - info: Server is listening on port 12345...
Tianji reads its settings from environment variables (see the Tianji documentation for the full list, for example EMAIL_SERVER and EMAIL_FROM for email notifications); add them to /etc/tianji/tianji.env and run sudo systemctl restart tianji. Leave ALLOW_REGISTER=false unless you want anyone who can reach the sign in page to be able to create an account.
Step 11: Server components and build record
| Component | Path |
|---|---|
| Tianji server and web interface | /opt/tianji/app (root owned, read only for the service) |
| Node.js runtime | /opt/node (npm, npx and corepack removed) |
| Environment (database URL, secrets, settings) | /etc/tianji/tianji.env (root only) |
| Public URL | /etc/tianji/public-url.env (written by tianji-set-hostname) |
| Service unit | /etc/systemd/system/tianji.service |
| Database | PostgreSQL 16 cluster 16/main, /var/lib/postgresql/16/main, socket /var/run/postgresql |
| nginx site | /etc/nginx/sites-available/cloudimg-tianji and /etc/nginx/tianji-*.conf |
| Certificate and key | /etc/ssl/tianji/ |
| First boot and per boot host scripts | /usr/local/sbin/tianji-firstboot.sh, /usr/local/sbin/tianji-configure.sh |
| Host name helper | /usr/local/sbin/tianji-set-hostname |
| Credentials | /root/tianji-credentials.txt (mode 0600) |
| Build record, licence, source changes, dependency audit | /usr/share/doc/tianji/ |
/opt/node/bin/node --version
jq -r '"Tianji " + .version' /opt/tianji/app/package.json
sed -n '1,2p;/^source sha256/p' /usr/share/doc/tianji/BUILD-INFO.txt
grep -c '^ok ' /usr/share/doc/tianji/override-check.txt | sed 's/$/ security overrides resolved/'
cut -f1,2,3 /usr/share/doc/tianji/npm-advisories-shipped.txt
v22.23.4
Tianji 1.33.11
Tianji v1.33.11 - cloudimg build
Licence: Apache-2.0 (LICENSE in this directory). Source changes: cloudimg-patches.txt.
source sha256 17b56736e304b46bb4845f7dd19cbcd038a59f878f17a334e2a9716c6790065e (tag commit 0e5379993ad3c228ef5496489fc72de6e07722f8)
18 security overrides resolved
critical @auth/core@0.34.3 GHSA-7rqj-j65f-68wh
high @auth/core@0.34.3 GHSA-xmf8-cvqr-rfgj
high basic-ftp@5.3.1 GHSA-c475-qrg2-pj4r
high braces@3.0.3 GHSA-vfj7-8cjw-p6xm
high extract-zip@2.0.1 GHSA-7pqw-9j4j-h8q3
high extract-zip@2.0.1 GHSA-jmr9-qjv8-65gv
high http-cache-semantics@4.1.1 GHSA-ch52-4w7c-c8xp
high ip-address@9.0.5 GHSA-mwp4-54f8-5fhr
high nodemailer@7.0.13 GHSA-2x7j-588g-ccc2
high nodemailer@7.0.13 GHSA-p6gq-j5cr-w38f
high nodemailer@7.0.13 GHSA-r7g4-qg5f-qqm2
high nodemailer@7.0.13 GHSA-v53p-9fqp-m79j
Step 12: Security notes and recommendations
About this build. The server is built from the Tianji 1.33.11 source (tag commit 0e537999) with its locked dependencies, using the same steps as Tianji's own container image, and only the production dependencies are kept (the Vite build tool and the client sources are not on the image). cloudimg made three small source changes, each marked in the file and listed in /usr/share/doc/tianji/cloudimg-patches.txt: Tianji and its UDP status ingress listen on loopback, and a missing GeoLite2 database leaves the visitor location empty instead of failing the request.
npm audit (via pnpm) reported high and critical advisories in Tianji's locked dependencies. 18 packages are raised to their fixed releases of the same major version, among them vm2 3.12.2, axios 1.20.0, follow-redirects 1.16.1, compression 1.8.2, engine.io 6.6.10, socket.io-parser 4.2.7, shell-quote 1.11.0, proxy-addr 2.0.8 and mysql2 3.22.0 (the full list is in /usr/share/doc/tianji/security-overrides.txt). The advisories that remain, listed in Step 11 and in npm-advisories-shipped.txt, have no fixed release in the version line Tianji uses and are not reachable in this configuration:
- @auth/core 0.34.3 (one critical, one high): the email address handling of the Auth.js email (magic link) sign in, which is off because
EMAIL_SERVERis not set, andgetToken(), which Tianji does not call. If you setEMAIL_SERVER, you turn on that provider. - nodemailer 7.0.13 (high): used only for the email notification channel an administrator configures, with the addresses and servers they enter.
- basic-ftp, extract-zip, ip-address (high): parts of the Puppeteer browser download and proxy code used by Tianji's optional page screenshot and Lighthouse features. No browser is installed and no proxy is configured, so this code never runs.
- http-cache-semantics, braces (high): reached only through a caching HTTP client the server does not use and through file pattern matching that no request input reaches.
The web interface's own npm packages (which run in your browser, not on the server) are listed in /usr/share/doc/tianji/pnpm-audit-prod.json.
Network calls. The server itself contacts only what you configure: the sites your monitors check, your notification channels, your AI gateway providers. Your browser loads user avatars from avatar.vercel.sh and, on the visitor map, map tiles from OpenFreeMap, and the optional API reference page at /open/_ui loads its viewer from unpkg.com. Tianji's anonymous usage ping is switched off.
Recommendations:
- Allow port 22 in the security group from your own networks only, and 80 and 443 from the networks your visitors, servers and monitoring use.
- Keep the admin password secret and give every person their own account under Settings > Workspace.
- Use your own DNS name and a certificate browsers trust (Step 9).
- Keep the operating system patched; unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else. - Back up the database and the environment file together (Step 10), or snapshot the root volume.
Tianji is a trademark of its respective owner. cloudimg is not affiliated with or endorsed by the Tianji project.