Cloudflare DNS Skill
Complete Cloudflare DNS operations via REST API with focus on Azure integration.
Overview
This skill covers Cloudflare DNS management for Azure-hosted workloads, including:
-
API token configuration and security
-
DNS record management (A, AAAA, CNAME, TXT, MX)
-
Proxy settings (orange/gray cloud)
-
External-DNS integration for Kubernetes
-
Troubleshooting and monitoring
Authentication
API Token (Recommended)
Create scoped API tokens instead of using Global API Key:
Required Permissions:
Permission Access Purpose
Zone > Zone Read List zones
Zone > DNS Edit Manage DNS records
Create Token:
-
Cloudflare Dashboard > My Profile > API Tokens
-
Create Token > Custom token
-
Add permissions above
-
Zone Resources: Specific zones only
-
(Optional) IP filtering for extra security
Environment Setup:
Export for API calls
export CF_API_TOKEN="your-api-token" export CF_ZONE_ID="your-zone-id"
Get zone ID
curl -s -X GET "https://api.cloudflare.com/client/v4/zones"
-H "Authorization: Bearer $CF_API_TOKEN" | jq '.result[] | {name, id}'
Token Verification
Verify token is valid
curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify"
-H "Authorization: Bearer $CF_API_TOKEN"
Quick Reference
List DNS Records
All records
curl -s "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records"
-H "Authorization: Bearer $CF_API_TOKEN" | jq '.result[] | {name, type, content, proxied}'
Filter by type
curl -s "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?type=A"
-H "Authorization: Bearer $CF_API_TOKEN" | jq '.result[]'
Search by name
curl -s "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?name=app.example.com"
-H "Authorization: Bearer $CF_API_TOKEN" | jq '.result[]'
Create DNS Records
A Record (proxied)
curl -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records"
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{
"type": "A",
"name": "app",
"content": "20.185.100.50",
"ttl": 1,
"proxied": true
}'
A Record (DNS-only)
curl -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records"
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{
"type": "A",
"name": "mail",
"content": "20.185.100.51",
"ttl": 3600,
"proxied": false
}'
CNAME Record
curl -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records"
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{
"type": "CNAME",
"name": "www",
"content": "app.example.com",
"ttl": 1,
"proxied": true
}'
TXT Record
curl -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records"
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{
"type": "TXT",
"name": "_dmarc",
"content": "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com",
"ttl": 3600
}'
MX Record
curl -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records"
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{
"type": "MX",
"name": "@",
"content": "mail.example.com",
"priority": 10,
"ttl": 3600
}'
Update DNS Records
Get record ID first
RECORD_ID=$(curl -s "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?name=app.example.com&type=A"
-H "Authorization: Bearer $CF_API_TOKEN" | jq -r '.result[0].id')
Update record
curl -X PUT "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records/$RECORD_ID"
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{
"type": "A",
"name": "app",
"content": "20.185.100.60",
"ttl": 1,
"proxied": true
}'
Patch (partial update)
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records/$RECORD_ID"
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{"proxied": false}'
Delete DNS Records
Get record ID
RECORD_ID=$(curl -s "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?name=old.example.com"
-H "Authorization: Bearer $CF_API_TOKEN" | jq -r '.result[0].id')
Delete
curl -X DELETE "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records/$RECORD_ID"
-H "Authorization: Bearer $CF_API_TOKEN"
Proxy Settings (Orange/Gray Cloud)
When to Enable Proxy (Orange Cloud)
Use Case Proxy Reason
Web applications Yes CDN, DDoS protection
REST APIs Yes Performance, security
Static websites Yes Caching, optimization
WebSockets Yes Supported with config
When to Disable Proxy (Gray Cloud)
Use Case Proxy Reason
Mail servers (MX) No SMTP not supported
SSH access No Non-HTTP protocol
FTP servers No Non-HTTP protocol
Custom TCP/UDP No Only HTTP/HTTPS proxied
VPN endpoints No Direct connection needed
Toggle Proxy via API
Enable proxy
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records/$RECORD_ID"
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{"proxied": true}'
Disable proxy
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records/$RECORD_ID"
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{"proxied": false}'
External-DNS Integration
Kubernetes Secret
kubectl create namespace external-dns
kubectl create secret generic cloudflare-api-token
--namespace external-dns
--from-literal=cloudflare_api_token="$CF_API_TOKEN"
Helm Values (kubernetes-sigs/external-dns)
fullnameOverride: external-dns
provider: name: cloudflare
env:
- name: CF_API_TOKEN valueFrom: secretKeyRef: name: cloudflare-api-token key: cloudflare_api_token
extraArgs: cloudflare-proxied: true cloudflare-dns-records-per-page: 5000
sources:
- service
- ingress
domainFilters:
- example.com
txtOwnerId: "aks-cluster-name" # MUST be unique per cluster txtPrefix: "_externaldns." policy: upsert-only # Production: NEVER use sync interval: "5m"
logLevel: info logFormat: json
resources: requests: memory: "64Mi" cpu: "25m" limits: memory: "128Mi"
serviceMonitor: enabled: true interval: 30s
Ingress Annotations
metadata: annotations: # Hostname for External-DNS external-dns.alpha.kubernetes.io/hostname: "app.example.com"
# Custom TTL
external-dns.alpha.kubernetes.io/ttl: "300"
# Override proxy setting
external-dns.alpha.kubernetes.io/cloudflare-proxied: "true"
# Multiple hostnames
external-dns.alpha.kubernetes.io/hostname: "app.example.com,www.example.com"
Zone Management
List Zones
curl -s "https://api.cloudflare.com/client/v4/zones"
-H "Authorization: Bearer $CF_API_TOKEN" | jq '.result[] | {name, id, status, plan: .plan.name}'
Get Zone Details
curl -s "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID"
-H "Authorization: Bearer $CF_API_TOKEN" | jq '.result'
Zone Settings
Get all settings
curl -s "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings"
-H "Authorization: Bearer $CF_API_TOKEN" | jq '.result[] | {id, value}'
Get specific setting
curl -s "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/ssl"
-H "Authorization: Bearer $CF_API_TOKEN" | jq '.result'
Update SSL mode
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/ssl"
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{"value": "full"}'
Export/Import DNS Records
Export (BIND Format)
curl -s "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records/export"
-H "Authorization: Bearer $CF_API_TOKEN" > dns-backup-$(date +%Y%m%d).txt
Import (BIND Format)
curl -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records/import"
-H "Authorization: Bearer $CF_API_TOKEN"
-F "file=@dns-backup.txt"
Troubleshooting
DNS Verification
Query Cloudflare DNS (1.1.1.1)
dig @1.1.1.1 app.example.com A dig @1.1.1.1 app.example.com AAAA
Check if proxied (returns Cloudflare IP)
dig +short app.example.com
Proxied: 104.x.x.x or 172.64.x.x
DNS-only: Your actual IP
Check TXT records (External-DNS ownership)
dig @1.1.1.1 TXT _externaldns.app.example.com
Full trace
dig +trace app.example.com
Check nameservers
dig NS example.com +short
Common Errors
Error Cause Solution
401 Unauthorized Invalid token Regenerate API token
403 Forbidden Insufficient permissions Add Zone:Read, DNS:Edit
429 Rate Limited Too many requests Increase interval, use pagination
Record exists Duplicate Delete or update existing record
External-DNS Logs
Watch logs
kubectl logs -n external-dns deployment/external-dns -f
Check for Cloudflare errors
kubectl logs -n external-dns deployment/external-dns | grep -i cloudflare
Check sync status
kubectl logs -n external-dns deployment/external-dns | grep -i "All records are already up to date"
Security Best Practices
API Token Security
-
Scope tokens - Use specific zones, not "All zones"
-
IP filtering - Restrict to known IPs when possible
-
Rotate regularly - Every 90 days for production
-
Store securely - Kubernetes Secrets or Azure Key Vault
-
Audit usage - Check Cloudflare audit logs
Token Rotation
1. Create new token in Cloudflare dashboard
2. Update Kubernetes secret
kubectl create secret generic cloudflare-api-token
--namespace external-dns
--from-literal=cloudflare_api_token="NEW_TOKEN"
--dry-run=client -o yaml | kubectl apply -f -
3. Restart External-DNS
kubectl rollout restart deployment external-dns -n external-dns
4. Verify
kubectl logs -n external-dns deployment/external-dns | head -20
5. Revoke old token in Cloudflare dashboard
Rate Limits
Cloudflare API Limits:
-
1,200 requests per 5 minutes (per account)
-
100 requests per 5 minutes (per zone, for some endpoints)
Mitigation:
External-DNS optimizations
extraArgs: cloudflare-dns-records-per-page: 5000 # Max pagination zone-id-filter: "specific-zone-id" # Reduce API calls
interval: "10m" # Less frequent polling
Azure Integration
cert-manager with Cloudflare DNS-01
apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-cloudflare spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: admin@example.com privateKeySecretRef: name: letsencrypt-cloudflare-key solvers: - dns01: cloudflare: apiTokenSecretRef: name: cloudflare-api-token key: api-token selector: dnsZones: - example.com
AKS Ingress Configuration
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: myapp annotations: cert-manager.io/cluster-issuer: letsencrypt-cloudflare external-dns.alpha.kubernetes.io/cloudflare-proxied: "true" spec: ingressClassName: nginx tls: - hosts: - app.example.com secretName: app-tls rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: myapp port: number: 80
References
-
references/api-reference.md
-
Complete Cloudflare DNS API documentation
-
references/azure-integration.md
-
Azure-specific patterns and configurations
-
scripts/cloudflare-dns.sh
-
Helper script for common operations
-
Cloudflare API Documentation
-
External-DNS Cloudflare Tutorial