Skip to content
Back to skills

Cloudflare Coder

ASecurity

This skill guides provisioning Cloudflare infrastructure with OpenTofu/Terraform. Use when managing zones, DNS records, WAF rules, SSL settings, Page Rules, or cache configuration.

  • 666 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added February 8, 2026
devopsgobashexpressterraformapisecurityperformance

Works with

  • api

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill cloudflare-coder --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Cloudflare Coder?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Cloudflare Coder
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-cloudflare-coder/badge)](https://www.skillsdirectory.com/skills/majiayu000-cloudflare-coder)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: cloudflare-coder
description: This skill guides provisioning Cloudflare infrastructure with OpenTofu/Terraform. Use when managing zones, DNS records, WAF rules, SSL settings, Page Rules, or cache configuration.
allowed-tools: Read, Write, Edit, Grep, Glob, Bash
---

# Cloudflare Coder

## Overview

Cloudflare provides CDN, DNS, security, and edge computing services. This skill covers OpenTofu/Terraform patterns for Cloudflare resources.

## Provider Setup

```hcl
terraform {
  required_providers {
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~> 4.0"
    }
  }
}

provider "cloudflare" {
  # API Token from environment: CLOUDFLARE_API_TOKEN
  # Or explicitly (not recommended):
  # api_token = var.cloudflare_api_token
}
```

### Authentication

```bash
# Preferred: API Token (scoped permissions)
export CLOUDFLARE_API_TOKEN="your-api-token"

# Or with 1Password
CLOUDFLARE_API_TOKEN=op://Infrastructure/Cloudflare/api_token

# Legacy: Global API Key (full access - avoid if possible)
export CLOUDFLARE_API_KEY="your-global-api-key"
export CLOUDFLARE_EMAIL="your-email@example.com"
```

**API Token Permissions (minimum required):**

| Resource | Permission | Use Case |
|----------|------------|----------|
| Zone | Read | List zones, read settings |
| Zone | Edit | Modify zone settings |
| DNS | Edit | Manage DNS records |
| Firewall | Edit | WAF, firewall rules |
| SSL | Edit | Certificate management |
| Cache | Purge | Cache invalidation |

## Data Sources

### Get Zone by Name

```hcl
data "cloudflare_zone" "main" {
  name = "example.com"
}

# Use in resources
resource "cloudflare_record" "www" {
  zone_id = data.cloudflare_zone.main.id
  # ...
}
```

### Get Account ID

```hcl
data "cloudflare_accounts" "main" {
  name = "My Account"
}

output "account_id" {
  value = data.cloudflare_accounts.main.accounts[0].id
}
```

## DNS Records

### Basic Records

```hcl
# A Record
resource "cloudflare_record" "root" {
  zone_id = data.cloudflare_zone.main.id
  name    = "@"
  type    = "A"
  content = var.server_ip
  ttl     = 1  # 1 = automatic
  proxied = true
}

# CNAME Record
resource "cloudflare_record" "www" {
  zone_id = data.cloudflare_zone.main.id
  name    = "www"
  type    = "CNAME"
  content = "@"
  ttl     = 1
  proxied = true
}

# MX Records
resource "cloudflare_record" "mx_primary" {
  zone_id  = data.cloudflare_zone.main.id
  name     = "@"
  type     = "MX"
  content  = "mail.example.com"
  ttl      = 3600
  priority = 10
  proxied  = false  # MX cannot be proxied
}

# TXT Record (SPF)
resource "cloudflare_record" "spf" {
  zone_id = data.cloudflare_zone.main.id
  name    = "@"
  type    = "TXT"
  content = "v=spf1 include:_spf.google.com ~all"
  ttl     = 3600
}
```

### DNS Records with for_each

```hcl
locals {
  dns_records = {
    api = {
      type    = "A"
      content = var.api_server_ip
      proxied = true
    }
    staging = {
      type    = "A"
      content = var.staging_server_ip
      proxied = true
    }
    mail = {
      type    = "A"
      content = var.mail_server_ip
      proxied = false
    }
  }
}

resource "cloudflare_record" "subdomain" {
  for_each = local.dns_records

  zone_id = data.cloudflare_zone.main.id
  name    = each.key
  type    = each.value.type
  content = each.value.content
  ttl     = 1
  proxied = each.value.proxied
}
```

## SSL/TLS Settings

### Zone SSL Settings

```hcl
resource "cloudflare_zone_settings_override" "ssl" {
  zone_id = data.cloudflare_zone.main.id

  settings {
    # SSL mode: off, flexible, full, strict (recommended)
    ssl = "strict"

    # Minimum TLS version
    min_tls_version = "1.2"

    # Always use HTTPS
    always_use_https = "on"

    # Automatic HTTPS Rewrites
    automatic_https_rewrites = "on"

    # HTTP Strict Transport Security
    security_header {
      enabled            = true
      max_age            = 31536000  # 1 year
      include_subdomains = true
      preload            = true
      nosniff            = true
    }

    # TLS 1.3
    tls_1_3 = "on"

    # Opportunistic Encryption
    opportunistic_encryption = "on"
  }
}
```

### Origin Certificates

```hcl
resource "cloudflare_origin_ca_certificate" "origin" {
  csr                = tls_cert_request.origin.cert_request_pem
  hostnames          = ["example.com", "*.example.com"]
  request_type       = "origin-rsa"
  requested_validity = 5475  # 15 years (max)
}

# Generate private key and CSR
resource "tls_private_key" "origin" {
  algorithm = "RSA"
  rsa_bits  = 2048
}

resource "tls_cert_request" "origin" {
  private_key_pem = tls_private_key.origin.private_key_pem

  subject {
    common_name  = "example.com"
    organization = "Example Inc"
  }
}

output "origin_certificate" {
  value     = cloudflare_origin_ca_certificate.origin.certificate
  sensitive = true
}
```

## Firewall & WAF

See [resources/waf.md](resources/waf.md) for IP access rules, WAF custom rules with rate limiting, and managed rulesets (Cloudflare + OWASP).

## Cache Rules

### Modern Cache Rules (Rulesets)

```hcl
resource "cloudflare_ruleset" "cache" {
  zone_id     = data.cloudflare_zone.main.id
  name        = "Cache Rules"
  description = "Caching configuration"
  kind        = "zone"
  phase       = "http_request_cache_settings"

  # Cache static assets aggressively
  rules {
    action = "set_cache_settings"
    action_parameters {
      cache = true
      edge_ttl {
        mode    = "override_origin"
        default = 2592000  # 30 days
      }
      browser_ttl {
        mode    = "override_origin"
        default = 86400  # 1 day
      }
    }
    expression  = "(http.request.uri.path.extension in {\"css\" \"js\" \"jpg\" \"jpeg\" \"png\" \"gif\" \"svg\" \"woff\" \"woff2\"})"
    description = "Cache static assets"
    enabled     = true
  }

  # Bypass cache for API
  rules {
    action = "set_cache_settings"
    action_parameters {
      cache = false
    }
    expression  = "(starts_with(http.request.uri.path, \"/api/\"))"
    description = "Bypass cache for API"
    enabled     = true
  }

  # Bypass cache for admin
  rules {
    action = "set_cache_settings"
    action_parameters {
      cache = false
    }
    expression  = "(starts_with(http.request.uri.path, \"/admin\"))"
    description = "Bypass cache for admin"
    enabled     = true
  }
}
```

### Legacy Page Rules

```hcl
# Cache everything for static paths
resource "cloudflare_page_rule" "cache_static" {
  zone_id  = data.cloudflare_zone.main.id
  target   = "example.com/assets/*"
  priority = 1

  actions {
    cache_level = "cache_everything"
    edge_cache_ttl = 2592000  # 30 days
  }
}

# Bypass cache for API
resource "cloudflare_page_rule" "bypass_api" {
  zone_id  = data.cloudflare_zone.main.id
  target   = "example.com/api/*"
  priority = 2

  actions {
    cache_level = "bypass"
  }
}

# Force HTTPS
resource "cloudflare_page_rule" "force_https" {
  zone_id  = data.cloudflare_zone.main.id
  target   = "http://example.com/*"
  priority = 3

  actions {
    always_use_https = true
  }
}
```

## Redirect Rules

```hcl
resource "cloudflare_ruleset" "redirects" {
  zone_id     = data.cloudflare_zone.main.id
  name        = "Redirects"
  description = "URL redirects"
  kind        = "zone"
  phase       = "http_request_dynamic_redirect"

  # www to apex redirect
  rules {
    action = "redirect"
    action_parameters {
      from_value {
        status_code = 301
        target_url {
          expression = "concat(\"https://example.com\", http.request.uri.path)"
        }
        preserve_query_string = true
      }
    }
    expression  = "(http.host eq \"www.example.com\")"
    description = "Redirect www to apex"
    enabled     = true
  }

  # Old URL to new URL
  rules {
    action = "redirect"
    action_parameters {
      from_value {
        status_code = 301
        target_url {
          value = "https://example.com/new-page"
        }
      }
    }
    expression  = "(http.request.uri.path eq \"/old-page\")"
    description = "Redirect old page to new"
    enabled     = true
  }
}
```

## Zone Settings

### Performance Settings

```hcl
resource "cloudflare_zone_settings_override" "performance" {
  zone_id = data.cloudflare_zone.main.id

  settings {
    # Compression
    brotli = "on"

    # Speed optimizations
    minify {
      css  = "on"
      html = "on"
      js   = "on"
    }

    # Early Hints
    early_hints = "on"

    # HTTP/2 and HTTP/3
    http2 = "on"
    http3 = "on"

    # 0-RTT
    zero_rtt = "on"

    # Rocket Loader (async JS loading)
    rocket_loader = "on"

    # Image optimization (requires Pro+)
    # polish = "lossless"
    # mirage = "on"

    # Mobile redirect (if needed)
    # mobile_redirect {
    #   mobile_subdomain = "m"
    #   status           = "on"
    #   strip_uri        = false
    # }
  }
}
```

### Security Settings

```hcl
resource "cloudflare_zone_settings_override" "security" {
  zone_id = data.cloudflare_zone.main.id

  settings {
    # Security level: off, essentially_off, low, medium, high, under_attack
    security_level = "medium"

    # Challenge Passage TTL
    challenge_ttl = 1800

    # Browser Integrity Check
    browser_check = "on"

    # Email Obfuscation
    email_obfuscation = "on"

    # Server Side Excludes
    server_side_exclude = "on"

    # Hotlink Protection
    hotlink_protection = "on"

    # IP Geolocation
    ip_geolocation = "on"
  }
}
```

## Argo & Load Balancing

See [resources/load-balancer.md](resources/load-balancer.md) for Argo Smart Routing and Load Balancer configuration with origin pools, health checks, and session affinity.

## Workers Routes

```hcl
# Deploy Worker via Terraform (see wrangler-coder for full Worker management)
resource "cloudflare_worker_route" "api" {
  zone_id     = data.cloudflare_zone.main.id
  pattern     = "example.com/api/*"
  script_name = cloudflare_worker_script.api.name
}

resource "cloudflare_worker_script" "api" {
  account_id = var.cloudflare_account_id
  name       = "api-worker"
  content    = file("${path.module}/workers/api.js")
  module     = true

  plain_text_binding {
    name = "ENVIRONMENT"
    text = var.environment
  }

  secret_text_binding {
    name = "API_KEY"
    text = var.api_key
  }
}
```

## Complete Production Zone

See [resources/production-zone.md](resources/production-zone.md) for a complete Terraform configuration with DNS, SSL/TLS, WAF, caching, redirects, and variables template.

## Best Practices

- **Use API Tokens** - Scoped permissions over global API key
- **Enable Strict SSL** - Always use Full (Strict) mode with valid origin certs
- **Use Rulesets** - Modern rulesets over legacy Page Rules
- **Cache Strategically** - Static assets cached, dynamic bypassed
- **Rate Limit APIs** - Protect endpoints from abuse
- **Enable WAF** - Deploy managed rulesets as baseline
- **HSTS Preload** - Add to browser preload list for max security
- **Terraform State** - Store state securely, not in version control

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.