home / skills / julianobarbosa / claude-code-skills / cloudflare-dns-skill

cloudflare-dns-skill skill

/skills/cloudflare-dns-skill

This skill guides Cloudflare DNS management for Azure-hosted apps via REST API, simplifying token setup, records, proxy, and Kubernetes integration.

npx playbooks add skill julianobarbosa/claude-code-skills --skill cloudflare-dns-skill

Review the files below or copy the command above to add this skill to your agents.

Files (4)
SKILL.md
12.7 KB
---
name: cloudflare-dns
description: 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.
---

# 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:**

1. Cloudflare Dashboard > My Profile > API Tokens
2. Create Token > Custom token
3. Add permissions above
4. Zone Resources: Specific zones only
5. (Optional) IP filtering for extra security

**Environment Setup:**

```bash
# 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

```bash
# 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

```bash
# 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

```bash
# 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
  }'
```

### Update DNS Records

```bash
# 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

```bash
# 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

```bash
# 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

```bash
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)

```yaml
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

```yaml
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

```bash
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

```bash
curl -s "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID" \
  -H "Authorization: Bearer $CF_API_TOKEN" | jq '.result'
```

### Zone Settings

```bash
# 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)

```bash
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)

```bash
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]"
```

## Troubleshooting

### DNS Verification

```bash
# 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

```bash
# 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

1. **Scope tokens** - Use specific zones, not "All zones"
2. **IP filtering** - Restrict to known IPs when possible
3. **Rotate regularly** - Every 90 days for production
4. **Store securely** - Kubernetes Secrets or Azure Key Vault
5. **Audit usage** - Check Cloudflare audit logs

### Token Rotation

```bash
# 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:**

```yaml
# 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

```yaml
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
```

### AKS Ingress Configuration

```yaml
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](https://developers.cloudflare.com/api/)
- [External-DNS Cloudflare Tutorial](https://kubernetes-sigs.github.io/external-dns/latest/tutorials/cloudflare/)

Overview

This skill is a practical guide for managing Cloudflare DNS with Azure and Kubernetes integration. It focuses on secure API token use, DNS record lifecycle (create, update, delete), proxy settings, External-DNS integration, and common troubleshooting steps. It delivers concrete commands, configuration snippets, and operational patterns for production use.

How this skill works

The skill explains how to create scoped Cloudflare API tokens, verify tokens, and call the Cloudflare REST API to list, create, update, patch, and delete DNS records. It covers toggling Cloudflare proxy (orange/gray cloud), exporting/importing DNS in BIND format, and integrating External-DNS with Kubernetes via secrets, Helm values, and ingress annotations. It also covers cert-manager DNS-01 automation for Azure workloads and rate-limit mitigation patterns.

When to use it

  • Configuring Cloudflare as the authoritative DNS for Azure-hosted applications
  • Automating DNS record management via the Cloudflare API
  • Integrating External-DNS with AKS or other Kubernetes clusters
  • Setting up cert-manager DNS-01 challenges with Cloudflare for Let’s Encrypt
  • Troubleshooting DNS resolution, proxy behavior, or External-DNS sync issues

Best practices

  • Use scoped API tokens with Zone:Read and DNS:Edit instead of global keys
  • Restrict token usage with zone-specific resources and optional IP filtering
  • Store tokens in Azure Key Vault or Kubernetes Secrets and rotate every ~90 days
  • Disable proxy for non-HTTP services (mail, SSH, FTP, custom TCP/UDP) and enable for web apps and APIs
  • Throttle External-DNS polling interval and use pagination/zone filters to avoid rate limits

Example use cases

  • Create proxied A/CNAME records for a web application hosted in Azure using the Cloudflare API
  • Deploy External-DNS in AKS with a Kubernetes secret holding the Cloudflare API token and Helm values for production
  • Automate certificate issuance using cert-manager with Cloudflare DNS-01 solver for wildcard or domain certificates
  • Switch proxy state for a record via API patch to troubleshoot connectivity or bypass Cloudflare
  • Export zone records as a BIND file for backup and import them into another zone or account

FAQ

What permissions should an API token have?

Give the token Zone:Read and DNS:Edit for the specific zones it manages; avoid global access.

When should I disable Cloudflare proxy?

Disable proxy for services that require direct IP access or non-HTTP protocols such as MX, SSH, FTP, VPN, and custom TCP/UDP endpoints.

How do I avoid Cloudflare API rate limits?

Increase External-DNS interval, use pagination settings, filter to specific zones, and reduce request frequency for bulk operations.