Cloudflare Tunnel
A Cloudflare tunnel replaces the need for an open inbound port or a TLS certificate. The cloudflared daemon on the server opens an outbound connection to Cloudflare's network, and Cloudflare forwards HTTPS traffic from docs.trickfirerobotics.com through that connection to nginx on localhost:80.
1. Install cloudflared
$ curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \
| sudo gpg --dearmor -o /usr/share/keyrings/cloudflare-main.gpg
$ echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] \
https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/cloudflared.list
$ sudo apt update
$ sudo apt install -y cloudflaredVerify:
$ cloudflared --version2. Authenticate with Cloudflare
$ cloudflared tunnel loginThis opens a browser URL. Log in and select the trickfirerobotics.com zone. A certificate is saved to ~/.cloudflared/cert.pem.
3. Create the tunnel
$ cloudflared tunnel create trickfire-docsThis creates a tunnel and saves a credentials file at ~/.cloudflared/<tunnel-id>.json. Note the tunnel ID in the output - you'll need it in the next step.
4. Write the tunnel config
Create /etc/cloudflared/trickfire-docs.yml (named after the tunnel, not the generic config.yml, so it coexists with other tunnels on the same server):
tunnel: <tunnel-id>
credentials-file: /home/trickfire/.cloudflared/<tunnel-id>.json
ingress:
- hostname: docs.trickfirerobotics.com
service: http://localhost:80
- service: http_status:404Replace <tunnel-id> with the UUID from step 3.
5. Add the DNS record
$ cloudflared tunnel route dns trickfire-docs docs.trickfirerobotics.comThis creates a CNAME record in Cloudflare pointing docs.trickfirerobotics.com → <tunnel-id>.cfargotunnel.com. Cloudflare automatically sets the record to proxied (orange cloud).
Verify in the Cloudflare dashboard under DNS → Records.
6. Run as a systemd service
cloudflared service install always creates a unit named cloudflared.service regardless of any --config flag. To get a named service that can coexist with other tunnels, write the unit file manually:
$ sudo tee /etc/systemd/system/cloudflared-trickfire-docs.service << 'EOF'
$ [Unit]
$ Description=cloudflared - trickfire-docs
$ After=network-online.target
$ Wants=network-online.target
$ [Service]
$ TimeoutStartSec=15
$ Type=notify
$ ExecStart=/usr/bin/cloudflared --no-autoupdate --config /etc/cloudflared/trickfire-docs.yml tunnel run
$ Restart=on-failure
$ RestartSec=5s
$ [Install]
$ WantedBy=multi-user.target
$ EOF
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now cloudflared-trickfire-docsCheck status:
$ sudo systemctl status cloudflared-trickfire-docsThe tunnel starts automatically on boot. Cloudflare will reconnect automatically if the server restarts.
7. Verify end-to-end
$ curl -I https://docs.trickfirerobotics.com
# HTTP/2 200You should see HTTP/2 200 and Cloudflare response headers (cf-ray, server: cloudflare).
nginx config for tunnel
When using a Cloudflare tunnel, nginx only needs to handle plain HTTP on localhost. TLS termination is done by Cloudflare, so no certificate is needed. The config in scripts/nginx.conf is already written for this setup.
Key points in the nginx config:
- Listens only on
127.0.0.1:80- not reachable from outside - No SSL directives
try_filesfor Next.js static export routing (falls back to<uri>.html, thenindex.html)- Immutable cache headers for hashed JS/CSS assets
Troubleshooting
Tunnel shows as unhealthy in Cloudflare dashboard:
$ sudo journalctl -u cloudflared-trickfire-docs -fCommon causes: server can't reach cloudflare.com, or credentials file path is wrong.
Site loads but returns 502: Check nginx is running and the build directory exists:
$ sudo systemctl status nginx
$ ls /home/trickfire/docs/out/index.htmlDNS not resolving:
DNS propagation can take a few minutes. The cloudflared tunnel route dns command should update it instantly in Cloudflare, but local resolvers may cache the old value.