# 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 - Trit installed and healthy in local mode - A domain active in the intended Cloudflare account - A Cloudflare Zero Trust organization with an identity provider configured - `cloudflared` installed on the Trit host from Cloudflare's official package or repository - A dedicated hostname such as `trit.example.com` - A working systemd user manager if the tunnel will run as a user service 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: ```bash 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: ```bash 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: ```bash 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: ```yaml 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: ```bash 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: ```bash 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`: ```bash 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: ```bash cloudflared tunnel route dns trit trit.example.com ``` Run the tunnel in the foreground for the first test: ```bash 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: - Open `/api/whoami` and confirm the expected email and `cloudflare-access` mode. - Create or open an agent thread and confirm streaming output. - Open a terminal, disconnect it, and reconnect. - Try an identity outside the policy and confirm it is denied. 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`: ```ini [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: ```bash 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: ```bash 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 | Symptom | Check | | --- | --- | | Trit fails to start | Validate the exact HTTPS `TRIT_PUBLIC_ORIGIN`, team domain, and non-empty AUD value | | Access login loops | Confirm the Access application domain exactly matches the hostname | | Trit returns unauthorized after Access login | Confirm the AUD tag, team domain, JWT issuer, and Trit email allowlist | | Browser API requests return forbidden origin | Remove trailing slashes or paths from `TRIT_PUBLIC_ORIGIN` and use only that hostname in the browser | | Page loads but streams or terminals fail | Check WebSocket requests, tunnel logs, and browser origin; do not add a second proxy hostname | | Tunnel reports ingress errors | Run `cloudflared tunnel --config "$HOME/.cloudflared/trit.yml" ingress validate` | | Service stops after logout | Check 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.