Provider Setup
terraform { required_providers { cloudflare = { source = "cloudflare/cloudflare" version = "~> 4.0" } } }
provider "cloudflare" {
Uses CLOUDFLARE_API_TOKEN env var
}
Authentication
export CLOUDFLARE_API_TOKEN="your-api-token"
Or with 1Password
CLOUDFLARE_API_TOKEN=op://Infrastructure/Cloudflare/api_token
See references/cloudflare-api-permissions.md for token permissions and legacy API key setup.
Data Sources
Get Zone by Name
data "cloudflare_zone" "main" { name = "example.com" }
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
resource "cloudflare_record" "root" { zone_id = data.cloudflare_zone.main.id name = "@" type = "A" content = var.server_ip ttl = 1 # 1 = automatic proxied = true }
resource "cloudflare_record" "www" { zone_id = data.cloudflare_zone.main.id name = "www" type = "CNAME" content = "@" ttl = 1 proxied = true }
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 }
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 = "strict" min_tls_version = "1.2" always_use_https = "on" automatic_https_rewrites = "on" tls_1_3 = "on" opportunistic_encryption = "on"
security_header {
enabled = true
max_age = 31536000
include_subdomains = true
preload = true
nosniff = true
}
} }
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) }
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 }
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"
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 }
rules { action = "set_cache_settings" action_parameters { cache = false } expression = "(starts_with(http.request.uri.path, "/api/"))" description = "Bypass cache for API" enabled = 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"
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 }
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 { brotli = "on" early_hints = "on" http2 = "on" http3 = "on" zero_rtt = "on" rocket_loader = "on"
minify {
css = "on"
html = "on"
js = "on"
}
} }
Security Settings
resource "cloudflare_zone_settings_override" "security" { zone_id = data.cloudflare_zone.main.id
settings { security_level = "medium" # off, essentially_off, low, medium, high, under_attack challenge_ttl = 1800 browser_check = "on" email_obfuscation = "on" server_side_exclude = "on" hotlink_protection = "on" ip_geolocation = "on" } }
Workers Routes
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 } }
References
-
references/cloudflare-api-permissions.md - Token permissions and auth setup
-
references/waf.md - IP access rules, WAF custom rules, managed rulesets
-
references/load-balancer.md - Argo Smart Routing, load balancer config
-
references/production-zone.md - Complete production Terraform configuration