The Cloudflare Terraform provider enables you to manage your Cloudflare resources using Infrastructure as Code principles. By storing your configuration in version control, you gain auditable changes, easy rollbacks, and the ability to use the same refined processes you already have for your cloud infrastructure.
Authentication and Setup
Modern authentication with the Cloudflare provider uses API tokens instead of the legacy API keys. API tokens are more secure and can be scoped to specific resources and permissions.
Creating an API Token
- Go to your Cloudflare dashboard → My Profile → API Tokens
- Click “Create Token”
- Use a preset template or create a custom token with specific permissions
- Copy the generated token securely
Provider Configuration
Configure the Terraform provider to authenticate with your Cloudflare account. Using environment variables prevents credentials from being committed to version control or stored in state files:
export CLOUDFLARE_API_TOKEN="your-api-token-here"
terraform {
required_version = "1.10.6"
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 5.0"
}
}
}
provider "cloudflare" {
# API token will be read from CLOUDFLARE_API_TOKEN environment variable
}
Managing DNS and Zones
Rather than creating individual resource blocks for each DNS record or zone setting, we define our configuration as variables and use for_each to create resources. Throughout the examples below, you’ll see this pattern repeatedly used. This approach helps keep configurations consistent, reduce duplication, and make scaling changes easier. While you can split variables into dedicated resources when it makes sense, managing them in this way provides a cleaner, more maintainable infrastructure-as-code workflow.
Zones
A zone represents a domain in Cloudflare. Once created, it becomes the container for all your domain’s DNS records, security settings, and performance features like caching and WAF rules.
resource "cloudflare_zone" "example" {
account = {
id = var.cloudflare_account_id
}
zone = "example.com"
type = "full"
}
Zone Settings
Zone settings control how Cloudflare handles traffic for your domain, including SSL/TLS modes, HTTPS enforcement, compression, and performance optimisations. These settings apply globally to the entire zone.
variable "zone_settings" {
description = "Cloudflare zone settings to configure"
type = map(any)
default = {
"automatic_https_rewrites" = "on"
"ssl" = "full"
"always_use_https" = "on"
"min_tls_version" = "1.2"
"browser_check" = "on"
"brotli" = "on"
"rocket_loader" = "on"
}
}
resource "cloudflare_zone_setting" "settings" {
for_each = var.zone_settings
zone_id = cloudflare_zone.example.id
setting_id = each.key
value = each.value
}
DNS Records
DNS records map your domain names to IP addresses or other destinations. Cloudflare supports all standard record types (A, AAAA, CNAME, MX, TXT) and allows you to toggle proxying to route traffic through Cloudflare’s network.
variable "cloudflare_dns_records" {
description = "DNS records to create in Cloudflare"
type = map(object({
type = string
name = string
content = string
ttl = optional(number, 1)
proxied = optional(bool, false)
priority = optional(number)
}))
default = {
"root" = {
type = "CNAME"
name = "example.com"
content = "site.pages.dev"
ttl = 1
proxied = true
}
"www" = {
type = "CNAME"
name = "www"
content = "site.pages.dev"
ttl = 1
proxied = true
}
"blog" = {
type = "CNAME"
name = "blog"
content = "blog.pages.dev"
ttl = 3600
proxied = false
}
}
}
resource "cloudflare_dns_record" "dns_records" {
for_each = var.cloudflare_dns_records
zone_id = cloudflare_zone.example.id
type = each.value.type
name = each.value.name
content = each.value.content
ttl = each.value.ttl
proxied = each.value.proxied
priority = each.value.priority
}
Page Rules
Page Rules allow you to configure caching, redirects, and other behaviours based on URL patterns. However, Cloudflare is phasing these out in favour of the more powerful Rulesets system below.
resource "cloudflare_page_rule" "static_cache" {
zone_id = cloudflare_zone.example.id
target = "static.example.com/*"
priority = 1
status = "active"
actions {
cache_level = "cache_everything"
edge_cache_ttl = 86400
}
}
Rulesets
Rulesets replace Page Rules with a more flexible expression-based system. Instead of simple URL patterns, you can write complex logic for redirects, caching, transforms, and security rules. For new infrastructure, use Rulesets rather than Page Rules.
Dynamic Redirects
Dynamic redirects allow you to create URL redirects based on patterns and expressions. Common uses include enforcing HTTPS, removing www subdomains, or redirecting old URLs to new locations.
resource "cloudflare_ruleset" "redirects" {
zone_id = cloudflare_zone.example.id
kind = "zone"
phase = "http_request_dynamic_redirect"
name = "redirects"
rules = [
{
action = "redirect"
action_parameters = {
from_value = {
preserve_query_string = false
status_code = 301
target_url = {
expression = "wildcard_replace(http.request.full_uri, r\"http://*\", r\"https://$${1}\")"
}
}
}
description = "Redirect from HTTP to HTTPS"
enabled = true
expression = "(http.request.full_uri wildcard r\"http://*\")"
},
{
action = "redirect"
action_parameters = {
from_value = {
preserve_query_string = true
status_code = 301
target_url = {
expression = "wildcard_replace(http.request.full_uri, r\"https://www.*\", r\"https://$${1}\")"
}
}
}
description = "Redirect www subdomain to apex domain"
enabled = true
expression = "(http.request.full_uri wildcard r\"https://www.*\")"
}
]
}
Using R2 as a Terraform Backend
R2 is Cloudflare’s S3-compatible object storage with zero egress fees, making it significantly cheaper than S3.
Create the R2 Bucket
Define the bucket where your Terraform state will be stored.
resource "cloudflare_r2_bucket" "terraform_state" {
account_id = var.cloudflare_account_id
name = "terraform-state-bucket"
location = "WEUR" # or other R2 locations
}
Configure Backend
Configure Terraform to use R2 for remote state storage. The S3 backend type works seamlessly with R2’s S3-compatible API:
terraform {
backend "s3" {
region = "WEUR"
bucket = "terraform-state-bucket"
key = "example/terraform.tfstate"
endpoint = "https://xxx.r2.cloudflarestorage.com"
# Configured locally, in CI, or through environment variables:
# access_key = "your-r2-access-key"
# secret_key = "your-r2-secret-key"
}
}
Generate R2 Access Credentials
R2 uses separate access credentials from your Cloudflare API token. Generate these through the R2 dashboard:
- Go to Cloudflare dashboard → R2 → Manage R2 API Tokens
- Create API token with read/write permissions for your bucket
- Copy the Access Key ID and Secret Access Key
Common Pitfalls and Gotchas
Default Zone Ruleset
When you encounter the error 'zone' is not a valid value for kind because exceeded maximum number of zone rulesets..., it indicates that Cloudflare has reached the limit for the number of zone-level rulesets in a specific phase; to manage this, you should import and modify the existing default zone ruleset for that phase.
Solution: Import the Existing Ruleset
-
Get your zone ID from the Cloudflare dashboard
-
Get the ruleset ID using the Cloudflare API, for example, if you’re trying to deploy a http_request_dynamic_redirect phase:
curl -X GET \ -H "Authorization: Bearer $API_TOKEN" \ -H "Content-Type: application/json" \ "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/phases/http_request_dynamic_redirect/entrypoint" -
Import the ruleset before applying:
tofu import cloudflare_ruleset.redirects <zone-id>/<ruleset-id>
Version 5.x Resource Name Changes
If you’re migrating from provider v4 to v5, be aware of resource name changes:
cloudflare_record→cloudflare_dns_record- Check the provider documentation for the complete migration guide and up-to-date resource names
Implementation Best Practices
- Use API tokens instead of legacy API keys for better security
- Store credentials securely and use environment variables if possible to prevent sensitive data getting into your backend configuration as this can leak sensitive credentials if improperly configured.
- Version your provider to ensure consistent behaviour
- Use variable-driven configuration for managing multiple similar resources
- Consider R2 for state storage to keep everything within the Cloudflare ecosystem
Conclusion
The Cloudflare Terraform provider offers comprehensive management of your Cloudflare infrastructure. With modern API token authentication, extensive resource coverage, and excellent integration with existing Terraform workflows, you can confidently manage DNS, security, performance, and edge computing resources as code. Whether you’re just getting started or optimising existing infrastructure, the Cloudflare Terraform provider provides the tools you need to manage your edge infrastructure effectively.