Deployment guide

Cloudflare Tunnel and Access

This guide exposes Trit through a stable HTTPS hostname while keeping its origin on 127.0.0.1. Cloudflare Access authenticates the browser, and Trit verifies the signed Access JWT instead of trusting an unsigned proxy email header.

Prerequisites

Do not open port 9420 in a host firewall, router, cloud security group, or public reverse proxy.

1. Record the current state

Run read-only checks and keep their output for rollback:

trit version
trit paths
trit doctor
systemctl --user is-active trit.service
curl -fsS http://127.0.0.1:9420/health
command -v cloudflared
cloudflared version

Back up Trit’s environment before editing it:

timestamp="$(date -u +%Y%m%dT%H%M%SZ)"
backup_dir="${XDG_STATE_HOME:-$HOME/.local/state}/trit/backups/cloudflare-$timestamp"
install -d -m 700 "$backup_dir"
install -m 600 "$HOME/.config/trit/env" "$backup_dir/env"

Use the custom environment path if the installation sets TRIT_ENV_FILE.

2. Create the named tunnel

Authenticate cloudflared and create a tunnel, but do not start it as a persistent service yet:

cloudflared tunnel login
cloudflared tunnel create trit
cloudflared tunnel list

Record the tunnel UUID and the generated credentials-file path. Keep the credentials file private.

Create ~/.cloudflared/trit.yml, replacing the placeholders with the real UUID, home directory, and hostname:

tunnel: 00000000-0000-0000-0000-000000000000
credentials-file: /home/user/.cloudflared/00000000-0000-0000-0000-000000000000.json
ingress:
  - hostname: trit.example.com
    service: http://127.0.0.1:9420
  - service: http_status:404

Protect the files and validate the ingress rules:

chmod 600 "$HOME/.cloudflared/trit.yml" "$HOME/.cloudflared/00000000-0000-0000-0000-000000000000.json"
cloudflared tunnel --config "$HOME/.cloudflared/trit.yml" ingress validate

3. Create the Access application

In Cloudflare Zero Trust:

  1. Open Access > Applications and add a Self-hosted application.
  2. Set the application domain to the dedicated Trit hostname, with no path restriction.
  3. Add an Allow policy containing only the intended emails or identity-provider groups.
  4. Keep the default deny behavior for every identity that does not match an Allow policy.
  5. Choose an appropriate session duration for a development shell with repository access.
  6. Save the application and record its Application Audience (AUD) Tag.
  7. Record the Zero Trust team domain, such as your-team.cloudflareaccess.com.

Use an explicit email allowlist in Trit even when the Access policy already restricts users. The two checks provide defense in depth.

4. Configure Trit’s remote mode

Edit ~/.config/trit/env and set:

NODE_ENV=production
TRIT_AUTH_MODE=cloudflare-access
TRIT_PUBLIC_ORIGIN=https://trit.example.com
TRIT_CF_ACCESS_TEAM_DOMAIN=your-team.cloudflareaccess.com
TRIT_CF_ACCESS_AUD=your-access-application-audience
TRIT_ALLOWED_EMAILS=owner@example.com,friend@example.com
HOST=127.0.0.1
PORT=9420

TRIT_PUBLIC_ORIGIN must be the exact canonical HTTPS origin, without a trailing slash or path. The audience value is the Access application’s AUD tag. Keep the environment file mode 0600:

chmod 600 "$HOME/.config/trit/env"
trit restart
trit doctor
curl -fsS http://127.0.0.1:9420/health

Trit should remain healthy locally before the public hostname is connected.

5. Connect DNS and test the tunnel

Create the Cloudflare DNS route for the named tunnel:

cloudflared tunnel route dns trit trit.example.com

Run the tunnel in the foreground for the first test:

cloudflared tunnel --config "$HOME/.cloudflared/trit.yml" run trit

From a private browser window, open the HTTPS hostname. Cloudflare should require Access authentication before Trit loads. After authentication:

Stop the foreground tunnel after this test.

6. Run the tunnel as a user service

Create ~/.config/systemd/user/cloudflared-trit.service. Replace /usr/bin/cloudflared with the absolute path returned by command -v cloudflared:

[Unit]
Description=Cloudflare Tunnel for Trit
After=network-online.target trit.service
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/cloudflared --config=%h/.cloudflared/trit.yml tunnel run trit
Restart=on-failure
RestartSec=5
UMask=0077
NoNewPrivileges=true

[Install]
WantedBy=default.target

Enable and inspect it:

systemctl --user daemon-reload
systemctl --user enable --now cloudflared-trit.service
systemctl --user status cloudflared-trit.service --no-pager
journalctl --user-unit cloudflared-trit.service --since '-10 minutes' --no-pager

If the service must survive logout, inspect loginctl show-user "$USER" -p Linger. Enabling lingering may require an administrator and should be an explicit host-level decision.

7. Verify the boundary

On the Trit host:

ss -ltn | grep ':9420'
curl -fsS http://127.0.0.1:9420/health
systemctl --user is-active trit.service cloudflared-trit.service

The only Trit listener must be 127.0.0.1:9420 or another loopback address. There must be no 0.0.0.0:9420, public firewall rule, or router forwarding rule.

From a client without an Access session, requesting the public hostname should redirect to or be denied by Access rather than return Trit directly. From an approved browser session, /api/whoami, HTTP API requests, and WebSockets must all succeed with the same origin and identity.

Troubleshooting

SymptomCheck
Trit fails to startValidate the exact HTTPS TRIT_PUBLIC_ORIGIN, team domain, and non-empty AUD value
Access login loopsConfirm the Access application domain exactly matches the hostname
Trit returns unauthorized after Access loginConfirm the AUD tag, team domain, JWT issuer, and Trit email allowlist
Browser API requests return forbidden originRemove trailing slashes or paths from TRIT_PUBLIC_ORIGIN and use only that hostname in the browser
Page loads but streams or terminals failCheck WebSocket requests, tunnel logs, and browser origin; do not add a second proxy hostname
Tunnel reports ingress errorsRun cloudflared tunnel --config "$HOME/.cloudflared/trit.yml" ingress validate
Service stops after logoutCheck the systemd user manager and lingering policy

Use journalctl --user-unit trit.service for Trit and journalctl --user-unit cloudflared-trit.service for the tunnel. Do not paste environment files, tunnel credentials, cookies, or JWTs into issue reports.

Rollback

  1. Disable the tunnel service: systemctl --user disable --now cloudflared-trit.service.
  2. Remove or disable the tunnel’s public hostname and DNS route in Cloudflare.
  3. Restore the previous Trit environment backup or set TRIT_AUTH_MODE=local, HOST=127.0.0.1, and PORT=9420.
  4. Run trit restart, trit doctor, and the loopback health check.
  5. Remove the Access application only after the hostname no longer routes to the tunnel.

Retain the backup and tunnel credentials until local mode is verified. Deleting a tunnel is optional and should be a separate, deliberate cleanup step.