# โ˜๏ธ Run behind a Cloudflare Tunnel A Cloudflare Tunnel uses an outbound connection from `cloudflared` to Cloudflare, and Cloudflare sends requests back over that connection. Nothing listens on a public port, the edge terminates TLS, and the origin receives plain HTTP on loopback. This page configures the tunnel, then configures the origin to record the actual client address instead of `127.0.0.1`. ๐Ÿ“Œ This page describes **v0.2.2**. ## ๐Ÿงพ Before you start - The domain in a Cloudflare account, with Zero Trust available. - `cloudflared` on the same host as Pingclair, and Pingclair serving the site ([Serve a static site](/guides/static-site/)). - Either the dashboard (Zero Trust โ†’ Networks โ†’ Tunnels) or an API token with **Cloudflare Tunnel: Write** and **DNS: Edit** for the zone. The examples here use the API, with `$CF_TOKEN`, `$ACCOUNT` and `$ZONE` set to the token, the account ID, and the zone ID. ## ๐ŸŒ Create the tunnel ```bash curl -s -X POST -H "Authorization: Bearer $CF_TOKEN" -H 'Content-Type: application/json' \ --data '{"name":"docs-origin","config_src":"cloudflare"}' \ "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT/cfd_tunnel" ``` ```text {"success":true,"result":{"id":"bc6869fa-19cf-4780-b95b-f11be77eb329","name":"docs-origin", โ€ฆ}} ``` `config_src: cloudflare` means the tunnel is **remotely managed**: its ingress rules live in Cloudflare and are pushed through the API, so nothing has to be written next to the connector. Retrieve the connector credential with a separate API request: ```bash curl -s -H "Authorization: Bearer $CF_TOKEN" \ "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT/cfd_tunnel/$TUNNEL_ID/token" ``` That token is a secret: it lets any host join the tunnel. Treat it like a password, and rotate it if it leaks. ## ๐Ÿ”Œ Connect the host ```bash curl -fsSL -o /tmp/cloudflared.deb \ https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb sudo dpkg -i /tmp/cloudflared.deb sudo cloudflared service install "$TUNNEL_TOKEN" ``` ```text INF Linux service for cloudflared installed successfully ``` The connector opens four connections to nearby Cloudflare locations, over QUIC by default: ```text INF Registered tunnel connection connIndex=2 โ€ฆ location=pdx02 protocol=quic INF Registered tunnel connection connIndex=3 โ€ฆ location=sea10 protocol=quic ``` ## ๐ŸŒ Route a hostname The ingress rules decide which hostname reaches which origin service: ```bash curl -s -X PUT -H "Authorization: Bearer $CF_TOKEN" -H 'Content-Type: application/json' \ --data '{"config":{"ingress":[ {"hostname":"tunnel-test.pingclair.com","service":"http://127.0.0.1:80"}, {"service":"http_status:404"}]}}' \ "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT/cfd_tunnel/$TUNNEL_ID/configurations" ``` The last rule is the catch-all: a request for any other hostname receives `404` instead of reaching the origin. Create a proxied DNS record for the tunnel: ```bash curl -s -X POST -H "Authorization: Bearer $CF_TOKEN" -H 'Content-Type: application/json' \ --data '{"type":"CNAME","name":"tunnel-test.pingclair.com","content":"'$TUNNEL_ID'.cfargotunnel.com","proxied":true,"ttl":60}' \ "https://api.cloudflare.com/client/v4/zones/$ZONE/dns_records" ``` Verify the route from a host outside the origin network: ```bash curl -I https://tunnel-test.pingclair.com/ ``` ```text HTTP/2 200 content-type: text/html; charset=utf-8 accept-ranges: bytes server: cloudflare ``` `server: cloudflare` shows the edge answering. The origin was reached through the tunnel, and no inbound port was opened for it. ## ๐ŸŽฏ Record the client address Every request arrives from the connector on loopback, so by default the access log records the connector, not the client: ```text ๐Ÿ“ Access โ€ฆ host="tunnel-test.pingclair.com" status=200 remote_ip=127.0.0.1 user_agent="curl/8.7.1" ``` `trusted_proxies` lists the peers allowed to state the client address in forwarding headers. The connector runs on the same host, so loopback is the whole list: ```caddyfile { admin 127.0.0.1:2019 servers { trusted_proxies static 127.0.0.1/32 client_ip_headers CF-Connecting-IP } } http://:80 { root * /srv/site file_server } ``` Measured, with the same request before and after the option: ```text remote_ip=127.0.0.1 # before remote_ip=16.162.199.171 # after: the client that started the request ``` The same setting enables per-client rate limits and the `client_ip` matcher to use the forwarded client address. It is read at startup, so a change needs a restart rather than a reload ([what a reload means](/start/service/#-what-a-reload-means)). `client_ip` and `{client_ip}` use the client from the listed headers; `remote_ip` and `{remote_host}` remain the connector address. Only trusted peers may supply these headers. `CF-Connecting-IP` must be listed explicitly in `client_ip_headers`. ## โš ๏ธ Troubleshooting - **`HTTP/2 530` with `error code: 1033`.** The tunnel has no connector. `systemctl is-active cloudflared` on the origin reports its status; requests answer `200` again within seconds of the connector registering. - **A request reaches a different site, or `404`.** The ingress rules are matched in order and end with the catch-all; check the hostname spelling in the rule before investigating DNS. - **`502` from the edge.** The connector is running, but the origin service refused the connection: Pingclair is not listening on the port the rule names. - **The access log records `127.0.0.1` for every client.** `trusted_proxies` is missing, as above. - **The hostname does not resolve.** The record has to be a proxied CNAME to `.cfargotunnel.com`; a DNS-only record bypasses the tunnel entirely. - **The connector token was exposed.** Rotate the tunnel's token and reinstall the service with the new one. ## ๐Ÿงญ Next steps - [Serve a static site](/guides/static-site/): the origin these examples point at. - [TLS: what you can tune](/guides/tls-tuning/): what the origin can do with certificates when the edge is not terminating. - [Run it as a service](/start/service/): the unit on the origin, and the reload semantics the `trusted_proxies` note refers to.