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
The recommended approach is to use environment variables for security:
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
Create and manage DNS zones:
resource "cloudflare_zone" "example" {
account = {
id = var.cloudflare_account_id
}
zone = "example.com"
type = "full"
}
Zone Settings
Configure zone settings using a variable-driven approach:
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
Manage DNS records with various types:
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
}
Performance and Caching
Page Rules
Cloudflare is retiring Page Rules and moving everything over to the newer, more powerful Rules engine—so instead of relying on Page Rules, you’ll want to start using Cache Rules, Dynamic Redirects, and Configuration Rules for the same jobs (see cloudflare_ruleset
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
}
}
Security Configuration with Rulesets
Dynamic Redirects
Configure HTTP to HTTPS redirects and other dynamic redirects using rulesets:
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
Cloudflare R2 provides S3-compatible object storage that can be used as a Terraform backend. This is cost-effective and keeps your infrastructure state close to your Cloudflare resources.
Create the R2 Bucket
resource "cloudflare_r2_bucket" "terraform_state" {
account_id = var.cloudflare_account_id
name = "terraform-state-bucket"
location = "WEUR" # or other R2 locations
}
Configure Backend
In your backend.tf
:
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
- 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.