sandbox.example.com can reach the controller
while https://review-42.tunnel.example.com reaches one published service.A/AAAA plus Origin CA | Origin ports remain public unless firewalled | Cloudflare edge plus Full (strict) origin TLS | A dedicated host already uses Cloudflare proxying |
| Direct Caddy or Traefik | Public origin | Public CA to the origin | No Cloudflare dependency is wanted |compose.cloudflare-origin.yaml.123Controller: sandbox.example.com Tunnel base: tunnel.example.com Tunnel wildcard: *.tunnel.example.com
tunnel.example.com in Sandbox configuration. Do not include *.
or a trailing dot.12sandbox.example.com A ORIGIN_IPV4 proxied *.tunnel.example.com A ORIGIN_IPV4 proxied
AAAA only when the origin is intentionally reachable over IPv6. Never
publish worker addresses.*.example.com does not cover
*.tunnel.example.com. Order an Advanced certificate with the zone apex and the
exact deep wildcard, choose TXT validation, publish every requested DCV record,
and wait for an active certificate before continuing.12tunnel.example.com *.tunnel.example.com
1234567sudo install -d -m 0700 /etc/sandbox/origin-tls sudo install -m 0644 cloudflare-origin.crt /etc/sandbox/origin-tls/tunnel.crt sudo install -m 0600 cloudflare-origin.key /etc/sandbox/origin-tls/tunnel.key sudo openssl x509 \ -in /etc/sandbox/origin-tls/tunnel.crt \ -noout -subject -issuer -dates -ext subjectAltName
*.tunnel.example.com. Never commit or transmit
the private key to a client or coding agent. Origin CA certificates are only for
the proxied Cloudflare-to-origin connection, so keep both DNS records proxied.12345678910SANDBOX_DOMAIN=sandbox.example.com SANDBOX_PORT=127.0.0.1:8080 SANDBOX_TUNNEL_ENABLED=true SANDBOX_TUNNEL_DOMAIN=tunnel.example.com SANDBOX_TUNNEL_SCHEME=https SANDBOX_TUNNEL_ENTRYPOINT=web SANDBOX_TUNNEL_EDGE_TLS=false SANDBOX_ACME_EMAIL=admin@example.com CLOUDFLARE_ORIGIN_CERT_FILE=/etc/sandbox/origin-tls/tunnel.crt CLOUDFLARE_ORIGIN_KEY_FILE=/etc/sandbox/origin-tls/tunnel.key
1234567sudo docker compose \ --env-file /etc/sandbox/runtime.env \ -f deploy/compose/compose.yaml \ -f deploy/compose/compose.caddy.yaml \ -f deploy/compose/compose.cloudflare-origin.yaml \ --profile caddy-edge \ up --build -d
404.123curl -sS -o /dev/null \ -w 'code=%{http_code} tls=%{ssl_verify_result}\n' \ https://preflight.tunnel.example.com/
code=404 and tls=0. A 525 or 526 means the Cloudflare-to-origin
TLS path is not ready. Then run a disposable lifecycle and verify the public
service before deleting both tunnel and sandbox.12345678sandbox create --tenant domain-smoke --image python:3.13-alpine --ttl 600 --network deny sandbox wait OPERATION_ID --timeout 120 sandbox exec SANDBOX_ID -- sh -c \ 'printf domain-ok > /tmp/index.html; cd /tmp; python -m http.server 8000 --bind 0.0.0.0 >/tmp/http.log 2>&1 &' sandbox tunnel create SANDBOX_ID --port 8000 curl --fail PUBLIC_HTTPS_URL/index.html sandbox tunnel delete SANDBOX_ID TUNNEL_ID sandbox delete SANDBOX_ID --wait
404 and the worker must have no route file, per-sandbox
tunnel network, or runtime container for the test.| Public hostname | Internal service |
sandbox.example.com | http://controller:8080 |
relay.tunnel.example.com | http://controller:8080 |
*.tunnel.example.com | http://tunnel-edge:8080 |
0600 file. Keep it out of Git,
prompts, and agent configuration.123456789SANDBOX_PORT=127.0.0.1:8080 SANDBOX_TUNNEL_ENABLED=true SANDBOX_TUNNEL_DOMAIN=tunnel.example.com SANDBOX_TUNNEL_SCHEME=https SANDBOX_TUNNEL_ENTRYPOINT=web SANDBOX_TUNNEL_EDGE_TLS=false SANDBOX_LOCAL_RELAY_ENABLED=true SANDBOX_LOCAL_RELAY_REQUIRE_AUTH=true CLOUDFLARE_TUNNEL_TOKEN_FILE=/etc/sandbox/cloudflare-tunnel.token
123456sudo docker compose \ --env-file /etc/sandbox/runtime.env \ -f deploy/compose/compose.yaml \ -f deploy/compose/compose.cloudflare.yaml \ --profile cloudflare-edge \ up --build -d