Cloudflare Operations
Operational guide for Cloudflare DNS, Tunnels, and Workers.
Quick Reference
Section titled “Quick Reference”| Property | Value |
|---|---|
| Domains | All 11 account zones under IaC (fzymgc.house, fzymgc.net, fzymgc.dev, fzymgc.email, plus bwcgroup.com, fovea-review.ai, fuzzymagic.com, holomush.dev, muxserver.net, seanb4t.dev, specgraph.io) |
| Terraform Module | tf/cloudflare/ |
| API Token Path | secret/fzymgc-house/infrastructure/cloudflare/* |
| Tunnel Name | fzymgc-house-main |
| Worker | hcp-terraform-discord |
Architecture
Section titled “Architecture”+----------------------------------------------------------------+| Cloudflare Account |+-----------------+-----------------+-----------------------------+| fzymgc.house | fzymgc.net | Workers || (internal DNS) | (webhooks) | (serverless functions) |+-----------------+-----------------+-----------------------------+| Zero Trust Tunnel || fzymgc-house-main |+----------------------------------------------------------------+API Token Pattern
Section titled “API Token Pattern”Uses a single hand-created bootstrap token that Terraform authenticates as.
Account-owned workload tokens (e.g. the R2 S3 token in r2.tf) are created by
Terraform via cloudflare_account_token (works on provider 5.21); the bootstrap token
cannot bootstrap itself, so it stays manual.
| Token | Purpose | Permissions | Created By |
|---|---|---|---|
| Bootstrap | Terraform auth + all operations | Account: Workers/R2/Tunnel/Access/API-Tokens; Zone (All zones in the account): DNS:Edit, Zone:Read, Zone Settings:Edit | Manual; 1-year expiry |
Blast radius: since Terraform is the source of truth for every zone’s DNS + settings, this token can edit DNS on all account domains (including personal ones sharing the account). It lives only in Vault, never in git.
Vault Paths
Section titled “Vault Paths”| Path | Content |
|---|---|
.../cloudflare/bootstrap-token | Bootstrap token for Terraform |
.../cloudflare/discord-webhook | Discord webhook URL |
.../cloudflare/hcp-terraform-hmac | HMAC secret for webhook validation |
DNS Management
Section titled “DNS Management”Terraform is the source of truth for every Cloudflare DNS record and per-zone
setting across all 11 account zones (adopted in hl-vzfh via cf-terraforming).
| File | Contents |
|---|---|
tunnel.tf | Tunnel CNAMEs driven by for_each variables (*-wh.fzymgc.net, auth/id.fzymgc.house, status.fzymgc.net) |
dns-<zone>.tf | All other records per zone (one file per zone), one cloudflare_dns_record resource + import{} block each |
zone-settings.tf | Per-zone cloudflare_zone_setting resources for settings explicitly changed from Cloudflare defaults |
Records NOT managed by Terraform (by design)
Section titled “Records NOT managed by Terraform (by design)”Do not import these — a controller owns them and TF management would conflict:
| Pattern | Owner |
|---|---|
_acme-challenge.* TXT | cert-manager DNS-01 solver (argocd/app-configs/cert-manager-config/cloudflare-acme-issuer.yaml) |
cf<year>-<n>._domainkey TXT | Cloudflare Email Routing (auto-generated DKIM) |
Deprecated minify and transient development_mode zone settings are also left unmanaged.
Add DNS Record
Section titled “Add DNS Record”- Tunnel-backed service: add to the relevant
for_eachvariable invariables.tf(seewebhook_services/internal_services). - Any other record: add a
cloudflare_dns_recordresource to the matchingdns-<zone>.tf(noimport{}block — that’s only for adopting pre-existing records). - Submit PR; the
main-cluster-cloudflareHCP workspace plans, then applies on merge.
Adopting an existing (dashboard-created) record
Section titled “Adopting an existing (dashboard-created) record”Generate faithful HCL + import blocks with cf-terraforming (provider v5), pointed at a
throwaway provider-schema dir so you don’t need HCP auth to init:
export CLOUDFLARE_API_TOKEN=$(vault kv get -mount=secret -field=token \ fzymgc-house/infrastructure/cloudflare/bootstrap-token)cf-terraforming generate --zone <zone_id> --resource-type cloudflare_dns_record \ --terraform-install-path <schema_dir> --modern-import-blockVerify zero-diff before merging by running terraform plan against live state with an
empty local state — it must report N to import, 0 to add, 0 to change, 0 to destroy.
Add a New Zone / Domain
Section titled “Add a New Zone / Domain”When a new domain is onboarded to the Cloudflare account:
- Token: no change needed. The bootstrap token is scoped All zones in the account, so any domain in this Cloudflare account is covered automatically. (Only relevant if the token were ever re-scoped to Specified Domains — then you’d add the new domain to its policy first.)
- Add records: create
dns-<zone-slug>.tf(./-→_). For an existing zone full of dashboard records, adopt them withcf-terraforming+import{}blocks as above; for a greenfield zone, writecloudflare_dns_recordresources directly. - Add changed settings to
zone-settings.tf(only settings that differ from Cloudflare defaults — checkmodified_on != nullviaGET /zones/<id>/settings). - Open a PR; confirm the HCP plan is zero-diff (imports) or intended (new records), then merge.
The zone itself is referenced as a
data.cloudflare_zone(looked up by name), not managed as acloudflare_zoneresource — Terraform owns the records and settings, not zone creation/registration.
Common Record Types
Section titled “Common Record Types”| Type | Use Case |
|---|---|
| A / AAAA | Direct IP mapping |
| CNAME | Alias / tunnel target (<id>.cfargotunnel.com) |
| MX / TXT | Mail routing, SPF / DKIM / DMARC |
| CAA | Certificate authority authorization |
Cloudflare Tunnel
Section titled “Cloudflare Tunnel”The tunnel provides secure external access without exposing IPs.
Architecture
Section titled “Architecture”Internet -> Cloudflare -> Tunnel -> Traefik -> ServicesManaged Services
Section titled “Managed Services”The primary tunnel fzymgc-house-main is defined in tunnel.tf
(cloudflare_zero_trust_tunnel_cloudflared.main). Its ingress rules and the
matching DNS CNAMEs (<id>.cfargotunnel.com) are generated from for_each
variables in variables.tf — add a service by adding a map entry, not a raw record:
| Variable | Hostname pattern | Protection |
|---|---|---|
webhook_services | <name>-wh.fzymgc.net | Public (webhook receivers) |
internal_services | <name>.fzymgc.net | Cloudflare Access |
ssh_services | <name>.fzymgc.net | Access browser-rendered SSH |
The tunnel token is written to Vault (.../cloudflared/tunnels/<name>) and consumed
by the cloudflared connector in argocd/app-configs/cloudflared-main/.
Add a New Tunnel
Section titled “Add a New Tunnel”Adding a service to the existing tunnel is the common case (above). A new tunnel is only needed for a separate connector / isolation boundary:
- Add a
cloudflare_zero_trust_tunnel_cloudflaredresource + acloudflare_zero_trust_tunnel_cloudflared_config(ingress) intunnel.tf, and adata.cloudflare_zero_trust_tunnel_cloudflared_tokento fetch its run token. - Persist the token to Vault via a
vault_kv_secret_v2(mirror the existingtunnel_credentialsresource). - Point DNS
cloudflare_dns_recordCNAMEs at<new-tunnel-id>.cfargotunnel.com. - Deploy a
cloudflaredconnector (newargocd/app-configs/cloudflared-*/) that reads the Vault token.
There is currently a second, un-IaC’d tunnel
d7ed1f27…backingwh-argowf/wh-events.fzymgc.house(its DNS records are managed; the tunnel object is not — tracked in hl-j4cv). Adopt it withcf-terraforming --resource-type cloudflare_zero_trust_tunnel_cloudflaredwhen consolidating.
Workers
Section titled “Workers”HCP Terraform Discord Worker
Section titled “HCP Terraform Discord Worker”Transforms HCP Terraform notification webhooks into Discord embeds.
| Property | Value |
|---|---|
| Code | cloudflare/workers/hcp-terraform-discord/worker.js |
| Terraform | tf/cloudflare/workers.tf |
| Secrets | DISCORD_WEBHOOK_URL, HMAC_SECRET |
HMAC Validation: When HMAC_SECRET is configured, validates X-TFE-Notification-Signature header. Invalid signatures rejected with 401.
Common Operations
Section titled “Common Operations”Bootstrap Token Setup (One-Time)
Section titled “Bootstrap Token Setup (One-Time)”-
Create token in Cloudflare Dashboard:
- API Tokens > Create Token > Create Custom Token
- Permissions:
- Account > Workers Scripts > Edit
- Account > Cloudflare Tunnel > Edit
- Account > Account Settings > Read
- Account > Account API Tokens > Edit (workload tokens in
r2.tf) - Account > Workers R2 Storage > Edit
- Account > Access: Apps and Policies > Edit; Access: Organizations, IdPs, and Groups > Edit
- Zone > DNS > Edit
- Zone > Zone > Read
- Zone > Zone Settings > Edit (for
zone-settings.tf)
- Zone Resources: All zones from an account → (the fzymgc account). Do not
use All Domains — an account-owned token rejects the all-zones wildcard with
Account tag in access policy must match tag in request uri. - Expiration: set a 1-year expiry (not never-expire) and a rotation reminder.
-
Store in Vault:
Terminal window vault kv put secret/fzymgc-house/infrastructure/cloudflare/bootstrap-token \token="YOUR_BOOTSTRAP_TOKEN" -
Apply Terraform:
Terminal window terraform -chdir=tf/cloudflare apply
Apply Order
Section titled “Apply Order”tf/vault -> tf/cloudflare -> Configure HCP TF webhook- tf/vault: Creates HMAC secret
- tf/cloudflare: Deploys Worker with HMAC binding, creates workload token
- HCP TF UI: Add HMAC token to notification webhook
Troubleshooting
Section titled “Troubleshooting”DNS Not Resolving
Section titled “DNS Not Resolving”- Check propagation:
dig @1.1.1.1 service.fzymgc.house - Verify Terraform state
- Check Cloudflare dashboard
Token Authentication Errors
Section titled “Token Authentication Errors”Error: 403 Forbidden - Authentication errorCause: API token missing required permissions, or the target zone is outside the token’s zone scope.
Fix:
-
Check bootstrap token has all required permissions (see Bootstrap Token Setup)
-
If a specific zone fails while others work, the token’s Specified Domains list is missing that zone — add it in the dashboard.
-
Verify token is stored correctly in Vault:
Terminal window vault kv get secret/fzymgc-house/infrastructure/cloudflare/bootstrap-token
Account tag in access policy must match tag in request uri
Section titled “Account tag in access policy must match tag in request uri”Cause: Editing this account-owned token with an All Domains (all-zones wildcard) zone policy — Cloudflare requires the zone policy be pinned to the token’s account.
Fix: Use Specified Domains and list every zone explicitly instead of All Domains.
Tunnel Not Connecting
Section titled “Tunnel Not Connecting”# Check tunnel statuskubectl -n cloudflared get podskubectl -n cloudflared logs -l app.kubernetes.io/name=cloudflared
# Verify credentials in Vaultvault kv get secret/fzymgc-house/cluster/cloudflared/tunnels/fzymgc-house-mainWorker Deployment Fails
Section titled “Worker Deployment Fails”# Check Worker existscurl -X GET "https://api.cloudflare.com/client/v4/accounts/ACCOUNT_ID/workers/scripts" \ -H "Authorization: Bearer $(vault kv get -field=token secret/fzymgc-house/infrastructure/cloudflare/bootstrap-token)"See Also
Section titled “See Also”- HCP Terraform Operations - Webhook notifications
- Tunnel connector:
argocd/app-configs/cloudflared-main/ - Worker code:
cloudflare/workers/hcp-terraform-discord/