skills/cloudflare-dns/SKILL.md
Comprehensive guide for managing Cloudflare DNS with Azure integration. Use when configuring Cloudflare as authoritative DNS provider for Azure-hosted applications, managing DNS records via API, setting up API tokens, configuring proxy settings, troubleshooting DNS issues, implementing DNS security best practices, or integrating External-DNS with Cloudflare for Kubernetes workloads.
npx skillsauth add julianobarbosa/claude-code-skills cloudflare-dnsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Complete Cloudflare DNS operations via REST API with focus on Azure integration.
This skill covers Cloudflare DNS management for Azure-hosted workloads, including:
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:
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}'
# Verify token is valid
curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer $CF_API_TOKEN"
# 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[]'
# 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:[email protected]",
"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
}'
# 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}'
# 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"
| 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 |
| 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 |
# 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}'
kubectl create namespace external-dns
kubectl create secret generic cloudflare-api-token \
--namespace external-dns \
--from-literal=cloudflare_api_token="$CF_API_TOKEN"
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
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"
curl -s "https://api.cloudflare.com/client/v4/zones" \
-H "Authorization: Bearer $CF_API_TOKEN" | jq '.result[] | {name, id, status, plan: .plan.name}'
curl -s "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID" \
-H "Authorization: Bearer $CF_API_TOKEN" | jq '.result'
# 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"}'
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
curl -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records/import" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-F "[email protected]"
# 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
| 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 |
# 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"
# 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
Cloudflare API Limits:
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
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-cloudflare
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-cloudflare-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
selector:
dnsZones:
- example.com
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/api-reference.md - Complete Cloudflare DNS API documentationreferences/azure-integration.md - Azure-specific patterns and configurationsscripts/cloudflare-dns.sh - Helper script for common operationsdig +short app.example.com showing 104.x.x.x is correct, not broken. Origin reachability must be tested via Host header or directly against the Azure origin IP.txtOwnerId collisions corrupt records across clusters: Two clusters sharing the same txtOwnerId will fight over ownership TXT records and silently overwrite each other's A records. Always use a unique cluster identifier.policy: sync deletes records External-DNS didn't create: If a manual A record matches a managed hostname pattern, sync mode will delete it during reconciliation. Production must use upsert-only./user/tokens/verify confirms the token exists, not that it has Zone:Read or DNS:Edit. Test by listing actual zones to confirm permissions are scoped correctly.development
End-to-end branch delivery: commit (no AI attribution) → push → open a pull request → ensure a Board work item exists (create one per task, assigned to the configured user, if none) and link it → after merge, clean up branch and worktree. Auto-detects the platform from the remote — Azure Repos + Boards (azure-devops-node-api SDK; OAuth Bearer push fallback via `az`) or GitHub (Octokit; `gh` for auth). Scripts are TypeScript, run via `bun`. Use whenever asked to "ship", "ship it", "ship this branch", "open a PR", "push and open a PR", "raise a PR", "deliver this", "send this for review", or "create a PR and link the work item" — and when a direct push to main is blocked and the change needs to go through a PR instead.
testing
Brief description of what this skill does. Include specific triggers - when should Claude use this skill? Example triggers, file types, or keywords that indicate this skill applies.
tools
Manage and troubleshoot PATH configuration in zsh. Use when adding tools to PATH (bun, nvm, Python venv, cargo, go), diagnosing "command not found" errors, validating PATH entries, or organizing shell configuration in .zshrc and .zshrc.local files.
tools
Zabbix monitoring system automation via API and Python. Use when: (1) Managing hosts, templates, items, triggers, or host groups, (2) Automating monitoring configuration, (3) Sending data via Zabbix trapper/sender, (4) Querying historical data or events, (5) Bulk operations on Zabbix objects, (6) Maintenance window management, (7) User/permission management