Managing DNS and network infrastructure using an Infrastructure as Code (IaC) approach brings massive benefits. Utilizing Terraform to configure Cloudflare allows you to:
- Track Changes: Every modification to a DNS record or firewall rule goes through your version control system (Git).
- Speed of Changes & Easier Maintainability: Rapidly deploy and easily maintain identical security and optimization standards across dozens of zones (domains).
- Enable Auditability: Easily trace who made a change, when it was made, and the exact reasoning behind it.
Adding a New Zone
In Terraform, we register a new domain in Cloudflare using the cloudflare_zone resource. This requires your Cloudflare account ID (account_id) and the domain name (zone).
Code Example:
resource "cloudflare_zone" "new_zone" {
account_id = "09dd5ff9f189eb15e69faa14bed61ed3"
zone = "example.com"
}
Once added, Cloudflare will generate dedicated Nameservers for the zone. You must update your domain registrar (e.g., OVH) to point to these new nameservers.
Enhancing Security: Hiding A Records (Proxying)
One of Cloudflare’s core strengths is its reverse proxy feature. In Terraform, this is enabled by setting proxied = true on your A, AAAA, or CNAME records.
resource "cloudflare_record" "root_a" {
zone_id = cloudflare_zone.new_zone.id
name = "@"
content = "185.56.209.219"
type = "A"
proxied = true # Enable Cloudflare Proxying
}
Benefits of Proxying (Hiding the A Record):
- Shielding the Origin Server IP: Public DNS queries (such as
pingordig) return Cloudflare’s Anycast IPs instead of your raw server IP. This prevents attackers from bypassing Cloudflare to launch direct DDoS attacks on your origin infrastructure. - DDoS Protection: Volumetric traffic spikes and malicious layers are absorbed and mitigated at Cloudflare’s edge before ever reaching your server.
- Web Application Firewall (WAF) & SSL: Cloudflare handles SSL/TLS termination at the edge and intercepts malicious patterns (SQL Injection, XSS) seamlessly.
Enhancing Performance: Cache Rules
To optimize loading times and reduce origin server load, we can enforce custom caching policies (Cache Rules) using the cloudflare_ruleset resource with the http_request_cache_settings phase.
Advanced Caching Configuration Example:
resource "cloudflare_ruleset" "cache_rules" {
zone_id = cloudflare_zone.new_zone.id
name = "Cache Rules"
kind = "zone"
phase = "http_request_cache_settings"
# 1. Cache static assets for 30 days
rules {
description = "Cache static assets"
expression = "(http.host eq \"example.com\") and (http.request.uri.path wildcard \"*.css\" or http.request.uri.path wildcard \"*.js\" or http.request.uri.path wildcard \"*.webp\" or http.request.uri.path wildcard \"*.woff2\")"
action = "set_cache_settings"
enabled = true
action_parameters {
cache = true
edge_ttl {
mode = "override_origin"
default = 2592000 # 30 days in Cloudflare edge cache
}
browser_ttl {
mode = "override_origin"
default = 86400 # 1 day in visitor's browser
}
}
}
# 2. Bypass Cache for Administration Panels (e.g., WordPress / Ghost)
rules {
description = "Bypass cache for admin panel"
expression = "(http.host eq \"example.com\") and (http.request.uri.path contains \"/wp-admin\" or http.request.uri.path contains \"wp-login.php\")"
action = "set_cache_settings"
enabled = true
action_parameters {
cache = false
}
}
}
This setups delivers static files from the closest geographic edge server, ensuring lightning-fast load times, while dynamic pages are fetched fresh from the origin.
Enhancing Maintainability
Rather than writing duplicated code blocks for every domain, Terraform allows you to use the for_each loop with a structured map in locals. This keeps your configuration clean and strictly DRY (Don’t Repeat Yourself).
Clean DRY Architecture Example:
locals {
zones = {
zone_a = { id = cloudflare_zone.zone_a.id, domain = "zone-a.com" }
zone_b = { id = cloudflare_zone.zone_b.id, domain = "zone-b.com" }
}
}
# A single code block configures DNS records across ALL defined zones
resource "cloudflare_record" "root_a" {
for_each = local.zones
zone_id = each.value.id
name = "@"
content = "185.56.209.219"
type = "A"
proxied = true
}