Skip to content

cloudflared — Cloudflare Tunnel (post-Kamal)

How the Heatwave hosts receive inbound web traffic without opening a single inbound web port. Companion to INFRASTRUCTURE_INVENTORY.md — see its Edge & network protection and Fleet sections for the surrounding edge facts.

The pre-Kamal version of this file (hand-run cloudflared tunnel login / create / route dns against Vultr boxes, with a local config.yaml ingress and a Capistrano deploy model) was removed — it described an architecture that no longer exists.

cloudflared is the Cloudflare Tunnel connector. It runs as a host systemd service on the active app box and holds a single outbound-only QUIC connection out to Cloudflare. Cloudflare routes inbound HTTPS for the public hostnames back down that connection to kamal-proxy :80 on the host.

Consequences of the outbound model:

  • No inbound web port is open on any box — DOCKER-USER iptables drops public 80/443. The tunnel is the only path in for web traffic.
  • TLS terminates at Cloudflare. Kamal runs proxy.ssl: false; the tunnel hands plain HTTP to kamal-proxy over the loopback.
  • Public hostnames crm/www/api/scan/mcp.warmlyyours.com (.warmlyyours.ws for staging) resolve to the tunnel via proxied *.cfargotunnel.com CNAMEs.

Installed by cloud-init, managed remotely (Terraform)

Section titled “Installed by cloud-init, managed remotely (Terraform)”

There is no tunnel config file on the host. The tunnel, its ingress rules, and the DNS records are declared in Cloudflare and managed by Terraform — prod in infra/terraform/cloudflare-production/, staging in infra/terraform/cloudflare/. Both create a cloudflare_zero_trust_tunnel_cloudflared with config_src = "cloudflare" (remotely-managed) plus a …_cloudflared_config ingress block (hostname → http://localhost:80, with the required http_status:404 catch-all) and the proxied DNS records.

The host side is just the connector binary + service, installed by cloud-init (infra/terraform/latitude/cloud-init.yaml.tftpl):

Terminal window
# cloud-init runcmd (only runs if cloudflared_token is non-empty):
dpkg -i cloudflared-linux-amd64.deb
cloudflared service install <connector-token>

The connector token is the tunnel_token output of the Cloudflare module, piped into the latitude module as var.cloudflared_token:

Terminal window
-var cloudflared_token="$(tofu -chdir=../cloudflare output -raw tunnel_token)"

No cloudflared tunnel login / create / route dns is ever run by hand — those steps are the Terraform resources.

cloudflared runs only on the active-primary node (today Dallas, dal-latitude-heatwave-01). It fronts the public hostnames, which point at the active node, so it is correctly inactive on the standby (Chicago, chi-latitude-heatwave-02) and comes up only on promotion.

The cloudflare-production module also defines a Chicago tunnel for a future cutover (“W3”), gated behind var.activate_dns = false: tofu apply creates that tunnel + ingress and emits its connector token without moving any traffic. Flipping activate_dns=true repoints the prod CNAMEs to the Chicago tunnel — the production traffic cutover — and is done only after the Chicago app stack is deployed and Postgres is promoted (rollback = re-apply with activate_dns=false).

On the active node (over the tailnet):

Terminal window
systemctl status cloudflared # expect active (running), QUIC connected

Tunnel/connector health is also visible in the Cloudflare Zero Trust dashboard (Networks → Tunnels) — the box should show HEALTHY.