cloudflare-coder
This skill guides provisioning Cloudflare infrastructure with OpenTofu/Terraform. Use when managing zones, DNS records, WAF rules, SSL settings, Page Rules, or cache configuration.
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
Cloudflare Coder
Overview
Cloudflare provides CDN, DNS, security, and edge computing services. This skill covers OpenTofu/Terraform patterns for Cloudflare resources.
Provider Setup
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
# 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
data "cloudflare_zone" "main" {
name = "example.com"
}
# Use in resources
resource "cloudflare_record" "www" {
zone_id = data.cloudflare_zone.main.id
# ...
}
Get Account ID
data "cloudflare_accounts" "main" {
name = "My Account"
}
output "account_id" {
value = data.cloudflare_accounts.main.accounts[0].id
}
DNS Records
Basic Records
# 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
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
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
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 for IP access rules, WAF custom rules with rate limiting, and managed rulesets (Cloudflare + OWASP).
Cache Rules
Modern Cache Rules (Rulesets)
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
# 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
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
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
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 for Argo Smart Routing and Load Balancer configuration with origin pools, health checks, and session affinity.
Workers Routes
# 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 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
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-01-20
- Index updated
- 2026-02-04
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!