Deployment guide

Tianji 1.33.11

Overview

Tianji is an open source (Apache-2.0) "insight hub" that puts several tools in one dashboard. It combines 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.

This guide covers launching and using Tianji 1.33.11 on Ubuntu 24.04 from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud 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, and the listing's guide hub describes the other monitoring and status page products in the listing.

What you get:

  • 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.
  • No default admin/admin login. Tianji's database setup creates an admin account with the published password admin. On first boot, before Tianji ever starts, the image replaces it with a password unique to your instance. The password is 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, which your browser sends 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, because 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-refresh timer and service are masked, and fwupd stays installed. If you use LVFS firmware updates, run sudo fwupdmgr refresh yourself.
  • A fully patched base. Ubuntu 24.04 LTS is patched 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 port 22 to your own networks. If you track public websites, visitors' browsers send page views to port 443, so port 443 must be reachable from wherever your visitors are.
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 choose your Region. Then choose Launch through EC2. Select m5.large, your key pair and a security group as described above.

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 \
  --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.

2

Connect to your instance

On the Tianji 1.33.11 on Ubuntu 24.04 version, the SSH user is ubuntu. Connect with your key pair:

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the services

Confirm that PostgreSQL, Tianji and nginx are active and that 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/sac/data/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. List the listening sockets:

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)
4

Get your URL and admin password

Read the credentials file as root:

sudo cat /root/tianji-credentials.txt

Only root can read the file. It 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://secureanycloud.com/stacks/tianji-1-33-11/

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 the name 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.

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. You can install your own certificate instead (Step 9). Sign in as admin with the password from Step 4. There is no "register" link, because an administrator creates accounts.

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 to see 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 and uptime. For HTTPS, it also shows the certificate's expiry.

A typical first setup has five parts:

  • Add each site under Website and put its tracker snippet on your pages.
  • Add monitors under Monitor.
  • Add a notification channel under Settings > Notifications, such as email, a webhook, Telegram, Feishu or Apprise.
  • Publish a status page under Pages.
  • Invite your team under Settings > Workspace.
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. Verify each behaviour:

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 are refused, and nobody can register an account through either of Tianji's APIs. Check both:

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

This script signs in as admin through Tianji's OpenAPI, adds a Shop website and sends three page views exactly as the tracker script in a visitor's browser does. It then creates an HTTP uptime monitor.

The password goes to curl on standard input. The session token travels in an Authorization header read from a root-only file in memory. 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 appear in the dashboard. The monitor runs inside Tianji, which is blocked from the instance metadata service. A monitor or webhook pointed at 169.254.169.254 therefore cannot read the instance's credentials.

7

Track your websites and servers

Websites. Open the website in the dashboard and choose Code to copy its snippet. Add the snippet 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, along 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 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 locations, create a free MaxMind account and download GeoLite2-City.mmdb to the instance. Then install it from the download 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, which happens weekly. Their licence requires this.

Servers. Open Servers > Add to see the command for Tianji's open source reporter agent (tianji-reporter). Install the agent 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.

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. Only root can read it, and systemd reads it before starting Tianji as its own user. Run these commands. The second one 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= <set-at-first-boot>= 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 and 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. Confirm each point:

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. Verify all three:

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. It 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. Verify each point:

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 = sac 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
9

Your own DNS name and certificate

Point a DNS record at the instance, then set the name that Tianji is served as. The command below mints a new self-signed certificate for the name and points Tianji's public URL at it. It updates nginx and /root/tianji-credentials.txt, and it keeps the name across reboots and stop/start cycles:

sudo tianji-set-hostname <your-domain>

To use a certificate from your own certificate authority, so that 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 certificate:

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>.

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 the file does not hold an old password.

Tianji logs to the journal. Read the latest start-up line with:

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.

11

Server components and build record

These are the components on the image and where each one lives:

  • 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/sac-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/

Print the runtime versions and the build record:

/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 - sac build Licence: Apache-2.0 (LICENSE in this directory). Source changes: sac-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
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. Only the production dependencies are kept, so the Vite build tool and the client sources are not on the image.

SecureAnyCloud made three small source changes, each marked in the file and listed in /usr/share/doc/tianji/sac-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. They include 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 are listed in Step 11 and in npm-advisories-shipped.txt. They 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_SERVER is not set, and getToken(), which Tianji does not call. If you set EMAIL_SERVER, you turn on that provider.
  • nodemailer 7.0.13 (high): used only for the email notification channel that 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 that the server does not use and through file pattern matching that no request input reaches.

The web interface's own npm packages run in your browser, not on the server. They 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 and your AI gateway providers. Your browser loads user avatars from avatar.vercel.sh and, on the visitor map, map tiles from OpenFreeMap. 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. Allow ports 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 that browsers trust (Step 9).
  • Keep the operating system patched. Unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies 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. SecureAnyCloud is not affiliated with or endorsed by the Tianji project.

Deploy on AWS

Launch Tianji 1.33.11 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

Deploy on AWS