Skip to main content

Prod Cloudflare tunnel routing

info

This runbook uses the cloudflare and your-org/blaster repos together. It configures the Cloudflare tunnel for blaster.public.example.com, defines Origin CA resources in Kubernetes, and shows how to restart cloudflared without surprises.

Blaster GitOps series​

  1. Blaster GitOps summary
  2. Blaster repo and branches
  3. Dockerfile & GitLab CI
  4. Blaster prep for automation
  5. Dev app k8s manifests
  6. Dev flux sources & Kustomizations
  7. Dev image automation
  8. Dev SOPS & age
  9. Dev verification & troubleshooting
  10. Prod overview
  11. Prod app k8s manifests and deployment
  12. Prod Flux GitOps and image automation
  13. Prod Cloudflare, Origin CA and tunnel routing - you are here
  14. Post development branches

1. Cloudflare repo changes​

1.1 Pull latest​

cd ~/Projects/cloudflare
git pull
Already up to date.

1.2 Update cloudflared ConfigMap​

---
# k8s/prod/40-configmap-cloudflared.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: cloudflared
namespace: cloudflare
data:
config.yaml: |
tunnel: 3183eb59-d124-483f-90a1-3409e41b1d69
credentials-file: /etc/cloudflared/creds/credentials.json
metrics: 0.0.0.0:2000

ingress:
# (optional) Block WP admin to public
- hostname: public.example.com
path: ^/(wp-login\.php|wp-admin(?:/.*)?$)
service: http_status:403

# Send to ingress-nginx over TLS and verify with Origin CA
- hostname: public.example.com
service: https://ingress-nginx-controller.ingress-nginx.svc.cluster.local:443
originRequest:
originServerName: public.example.com
caPool: /etc/cloudflared/certs/origin_ca.pem

- hostname: blaster.public.example.com
service: https://ingress-nginx-controller.ingress-nginx.svc.cluster.local:443
originRequest:
originServerName: blaster.public.example.com
caPool: /etc/cloudflared/certs/origin_ca.pem

# Catch-all
- service: http_status:404

1.3 Commit and push​

git add .
git commit -m "Added blaster to configmap"
[main 4d03596] Added blaster to configmap
1 file changed, 6 insertions(+)
git push
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 24 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 459 bytes | 459.00 KiB/s, done.
Total 5 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
To https://your-gitlab.example.com/your-org/cloudflare.git
ff25bd4..4d03596 main -> main

1.4 Reconcile Origin CA issuer components​

flux reconcile source git origin-ca-issuer-upstream -n flux-system && flux reconcile kustomization origin-ca-issuer-crds -n flux-system && flux reconcile kustomization origin-ca-issuer-rbac -n flux-system && flux reconcile kustomization origin-ca-issuer-controller -n flux-system
► annotating GitRepository origin-ca-issuer-upstream in flux-system namespace
✔ GitRepository annotated
◎ waiting for GitRepository reconciliation
✔ fetched revision v0.12.1@sha1:86d908eda6c91815557c04b7f3e871ca4f0a0cce
► annotating Kustomization origin-ca-issuer-crds in flux-system namespace
✔ Kustomization annotated
◎ waiting for Kustomization reconciliation
✔ applied revision v0.12.1@sha1:86d908eda6c91815557c04b7f3e871ca4f0a0cce
► annotating Kustomization origin-ca-issuer-rbac in flux-system namespace
✔ Kustomization annotated
◎ waiting for Kustomization reconciliation
✔ applied revision v0.12.1@sha1:86d908eda6c91815557c04b7f3e871ca4f0a0cce
► annotating Kustomization origin-ca-issuer-controller in flux-system namespace
✔ Kustomization annotated
◎ waiting for Kustomization reconciliation
✔ applied revision v0.12.1@sha1:86d908eda6c91815557c04b7f3e871ca4f0a0cce

2. Cloudflare DNS and tunnel​

2.1 Initial reachability test (expected 404)​

info

404 response is expected before blaster.public.example.com is added into the tunnel config.

curl -k https://blaster.public.example.com/ -I
HTTP/2 404

2.2 Obtain tunnel ID​

note

The existing tunnel ID is required for the DNS CNAME target in Cloudflare.

cloudflared tunnel list
You can obtain more detailed information for each tunnel with `cloudflared tunnel info <name/uuid>`
ID NAME CREATED CONNECTIONS

2.3 Cloudflare dashboard – DNS entry​

  1. Log in to https://dash.cloudflare.com
  2. Add a new DNS entry:
    1. Type: CNAME
    2. Name: blaster
    3. Target: TUNNELID.cfargotunnel.com
    4. Proxy status: Proxied

3. Origin CA resources in Kubernetes​

These resources live in the your-org/blaster repo under k8s/prod/ and are applied by Flux. They allow cloudflared to validate the origin using an Origin CA certificate.

3.1 Secret – Cloudflare API token copy​

Reuse the same Cloudflare API token value as your cloudflare app repo; this copy must live in blaster so OriginIssuer can issue a cert in this namespace.

# k8s/prod/70-secret-cfapi-token.enc.yaml
apiVersion: v1
kind: Secret
metadata:
name: cfapi-token
namespace: blaster
type: Opaque
stringData:
key: PASTE_YOUR_API_TOKEN

3.2 OriginIssuer​

# k8s/prod/80-originissuer.yaml
apiVersion: cert-manager.k8s.cloudflare.com/v1
kind: OriginIssuer
metadata:
name: cf-origin
namespace: blaster
spec:
requestType: OriginECC
auth:
tokenRef:
name: cfapi-token
key: key

3.3 Certificate for blaster.public.example.com​

Issue an Origin CA certificate in the same namespace as your Ingress (blaster).

# k8s/prod/90-certificate-blaster.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: blaster-your-domain-origin
namespace: blaster
spec:
secretName: blaster-public-tls
dnsNames:
- blaster.public.example.com
issuerRef:
group: cert-manager.k8s.cloudflare.com
kind: OriginIssuer
name: cf-origin

4. Restarting cloudflared after config change​

4.1 Why a restart is required​

  • The ConfigMap was changed in Git.
  • Flux reconciled and updated the ConfigMap object in the cluster.
  • The cloudflared Pod keeps running with the old config in memory.
  • Kubernetes does not automatically restart Pods when only a ConfigMap changes.
  • cloudflared does not re-read its config unless restarted (or run with a special watch mode).

Result:

  • Flux has synced Git → cluster.
  • The Deployment spec did not change.
  • No new ReplicaSet is created – the old Pod keeps the old config until you roll it.

4.2 Rollout restart​

Manual restart:

kubectl -n cloudflare rollout restart deploy/cloudflared
kubectl -n cloudflare rollout status deploy/cloudflared
deployment.apps/cloudflared restarted
Waiting for deployment "cloudflared" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "cloudflared" rollout to finish: 1 old replicas are pending termination...
deployment "cloudflared" successfully rolled out

5. Reachability test – success​

curl -k https://blaster.public.example.com/ -I
HTTP/2 200

6. Verification checklist​

  • cloudflared ConfigMap in cluster matches Git.
  • DNS CNAME blaster → <TUNNELID>.cfargotunnel.com is present and proxied.
  • Secret/cfapi-token present in blaster namespace and encrypted in Git.
  • OriginIssuer/cf-origin and Certificate/blaster-your-domain-origin are Ready=True.
  • blaster-public-tls Secret present and referenced by blaster-ingress.
  • curl -k https://blaster.public.example.com/ -I returns HTTP/2 200.