Newsletter 365tipů můžete číst zdarma. Placené předplatné je dobrovolná podpora, která pomáhá webu pokračovat.
Podpořit 365tipů

TIP#3294: Terraform coby cesta k rychlému zakládání a konfigurování Cloudflare

Nastavit Cloudflare není jen o tom, že to “zapnete”. Věcí je tam víc. A většinu z nich budete nastavovat pořád dokola stejně. Navíc jich je docela hodně. Ale nezoufejte.

Úplně nejlepší je, že existuje Terraform – nástroj (aplikace ve vašem počítači), kde máte definici toho, jak mají vypadat parametry vaší CF zóny. A tahle aplikace umí zónu založit, ale také aktualizovat nastavení podle nové definice (třeba i u staršího webu). Bez ručního klikání.

POZNÁMKA: Přesun jmenných serverů (nameserverů) Terraform samozřejmě neumí – to děláte ručně. A aktivaci nové zóny musíte taky udělat ručně.

Vstupem jsou mu .tf soubory s parametry. A budete potřebovat API token ke svému CF – ten ale musí mít práva zápisu (Edit).

API token a jeho oprávnění

Oprávnění (většina Edit, jen u „Zone“ stačí Read):

  1. Zone → Zone → Read (stačí Read, jen potřebuje vidět, že zóna existuje)
  2. Zone → Zone Settings → Edit
  3. Zone → DNS → Edit
  4. Zone → Zone WAF → Edit 
  5. Zone → Page Rules → Edit 
  6. Zone → Single Redirect → Edit
  7. Zone → Cache Rules → Edit

Zone Resources:

  • Include → All zones (pokud to chcete používat na cokoliv co tam máte), nebo pokud chcete být opatrnější, klidně nejdřív jen konkrétní doménu, na které to zkoušíte poprvé.

Založte zónu ještě před terraformováním

Pozor na to, že novou zónu je nutné založit ručně ještě předtím, než začnete terraformovat. Projděte založení až po moment, kdy je zóna aktivní a „čeká na změnu DNS“ – ale nedávejte import DNS!

Instalace Terraformu (macOS)

Na Macu přidáte Terraform ideálně přes Homebrew:

brew tap hashicorp/tap
brew install hashicorp/tap/terraform

Po instalaci si někde v projektu založte složku kde budete terraformovat – jednu pokud máte jenom jednu teraformaci, více složek pro každý web pokud jich máte víc. 

V složce teraformovaného webu musí existovat .tf soubor definující parametry a .tfvsars soubor s určením webu a API klíčem. 

terraform.tfvars

Může vypadat například takto (a pokud gitujete, nedávejte to nikam):

cloudflare_api_token = „API KLÍČ“
domain               = „kocicitipy.cz“
origin_ip            = „IP adresa vašeho webu, vašeho hostingu“
# Nepovinné – vlastní cesta pro monitoring (UptimeKuma apod.)
monitoring_path = „“

main.tf

je o dost komplikovanější a uplně nejlejednodušší je prostě spustit Claude nebo ChatGPT a nechat si to vytvořit od nich (včetně případného dospání skriptů pro sahání do API  tak podobně). 

Příklad jak to vypadalo pro kolotipy.cz

terraform {
  required_providers {
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~> 4.0"
    }
  }
}

provider "cloudflare" {
  api_token = var.cloudflare_api_token
}

# ============================================
# PROMĚNNÉ - uprav v terraform.tfvars pro každý web
# ============================================

variable "cloudflare_api_token" {
  description = "Cloudflare API token s Edit právy (Zone Settings, DNS, Cache Rules, Zone WAF)"
  type        = string
  sensitive   = true
}

variable "domain" {
  description = "Doména, na kterou se konfigurace nasazuje (např. kocicitipy.cz). Zóna musí už existovat a být aktivní (nameservery přepnuté)."
  type        = string
}

variable "origin_ip" {
  description = "IP adresa serveru (originu), kam Cloudflare posílá provoz"
  type        = string
}

variable "monitoring_path" {
  description = "Volitelná unikátní cesta pro externí monitoring dostupnosti (např. UptimeKuma), která má mít bypass cache. Prázdný řetězec = pravidlo se nevytvoří."
  type        = string
  default     = ""
}

# ============================================
# ZÓNA (musí už existovat - Terraform ji nezakládá,
# jen k ní najde ID podle jména)
# ============================================

data "cloudflare_zone" "this" {
  name = var.domain
}

# ============================================
# ZONE SETTINGS
# ============================================

resource "cloudflare_zone_settings_override" "this" {
  zone_id = data.cloudflare_zone.this.id

  settings {
    # SSL/TLS
    ssl              = "full"
    always_use_https = "on"
    min_tls_version  = "1.2"
    tls_1_3          = "on"

    # Cache (obecná nastavení, konkrétní pravidla jsou v cache_rules níže)
    cache_level       = "aggressive"
    browser_cache_ttl = 14400

    # Výkon
    brotli                    = "on"
    automatic_https_rewrites  = "on"
    always_online              = "on"
    http3                      = "off" # záměrně vypnuto - ERR_QUIC_PROTOCOL_ERROR na některých hostinzích

    # Bezpečnost (WAF managed ruleset běží na Free automaticky, netřeba nastavovat)
    security_level = "medium"

    # Ostatní - drženo stejné napříč weby
    ip_geolocation      = "on"
    email_obfuscation   = "on"
    server_side_exclude = "on"
    websockets          = "on"
    replace_insecure_js = "on"
    ipv6                = "on"
  }
}

# ============================================
# DNS ZÁZNAMY
# ============================================

resource "cloudflare_record" "root" {
  zone_id = data.cloudflare_zone.this.id
  name    = "@"
  type    = "A"
  content = var.origin_ip
  proxied = true
  ttl     = 1
}

resource "cloudflare_record" "www" {
  zone_id = data.cloudflare_zone.this.id
  name    = "www"
  type    = "A"
  content = var.origin_ip
  proxied = true
  ttl     = 1
}

resource "cloudflare_record" "wildcard" {
  zone_id = data.cloudflare_zone.this.id
  name    = "*"
  type    = "A"
  content = var.origin_ip
  proxied = true
  ttl     = 1
}

# ============================================
# REDIRECT www -> root
# ============================================

resource "cloudflare_ruleset" "www_redirect" {
  zone_id     = data.cloudflare_zone.this.id
  name        = "www to root redirect"
  description = "Přesměrování www.${var.domain} na ${var.domain}"
  kind        = "zone"
  phase       = "http_request_dynamic_redirect"

  rules {
    action      = "redirect"
    description = "www to root"
    enabled     = true
    expression  = "(http.host eq \"www.${var.domain}\")"

    action_parameters {
      from_value {
        status_code = 301
        target_url {
          expression = "concat(\"https://${var.domain}\", http.request.uri.path)"
        }
        preserve_query_string = true
      }
    }
  }
}

# ============================================
# CACHE RULES (WordPress)
# Pořadí důležité jen kvůli přehlednosti - Cloudflare stejně
# aplikuje "poslední odpovídající pravidlo vyhrává" u kolizí.
# ============================================

resource "cloudflare_ruleset" "cache_rules" {
  zone_id     = data.cloudflare_zone.this.id
  name        = "default"
  description = "WordPress cache pravidla"
  kind        = "zone"
  phase       = "http_request_cache_settings"

  rules {
    action      = "set_cache_settings"
    description = "Cache Everything"
    enabled     = true
    expression  = "true"

    action_parameters {
      cache = true
      edge_ttl {
        mode    = "override_origin"
        default = 28800 # 8 hodin
      }
    }
  }

  rules {
    action      = "set_cache_settings"
    description = "wordpress_logged_in no cache"
    enabled     = true
    expression  = "(http.cookie contains \"wordpress_logged_in\")"

    action_parameters {
      cache = false
    }
  }

  rules {
    action      = "set_cache_settings"
    description = "WP-Admin or wp-login.php exclude"
    enabled     = true
    expression  = "(http.request.uri.path wildcard r\"/wp-admin/*\") or (http.request.uri.path wildcard r\"/wp-login.php*\")"

    action_parameters {
      cache = false
    }
  }

  rules {
    action      = "set_cache_settings"
    description = "XML Feed"
    enabled     = true
    expression  = "(http.request.uri.path wildcard r\"/feed*\")"

    action_parameters {
      cache = true
      edge_ttl {
        mode    = "override_origin"
        default = 7200 # 2 hodiny - minimum na Free plánu
      }
    }
  }

  rules {
    action      = "set_cache_settings"
    description = "WP-CRON no-cache"
    enabled     = true
    expression  = "(http.request.uri.path wildcard r\"/wp-cron.php*\")"

    action_parameters {
      cache = false
    }
  }

  # Volitelné pravidlo pro monitoring (UptimeKuma apod.) - vytvoří se
  # jen pokud je monitoring_path vyplněný.
  dynamic "rules" {
    for_each = var.monitoring_path != "" ? [1] : []
    content {
      action      = "set_cache_settings"
      description = "Monitoring bypass"
      enabled     = true
      expression  = "(http.request.uri.path wildcard r\"${var.monitoring_path}*\")"

      action_parameters {
        cache = false
      }
    }
  }
}

# ============================================
# RATE LIMITING - ochrana feedu
# Free plán: pevné okno 10s, jediná akce Block
# ============================================

resource "cloudflare_ruleset" "rate_limit" {
  zone_id     = data.cloudflare_zone.this.id
  name        = "default"
  description = "Rate limiting - ochrana feedu"
  kind        = "zone"
  phase       = "http_ratelimit"

  rules {
    action      = "block"
    description = "Feed rate limit"
    enabled     = true
    expression  = "(http.request.uri.path wildcard r\"/feed*\")"

    ratelimit {
      characteristics     = ["ip.src", "cf.colo.id"]
      period              = 10
      requests_per_period = 20
      mitigation_timeout  = 10
    }
  }
}

# ============================================
# VÝSTUPY
# ============================================

output "zone_id" {
  value = data.cloudflare_zone.this.id
}

output "name_servers" {
  value = data.cloudflare_zone.this.name_servers
}

Spuštění: init, plan, apply

No a pak ve složce se soubory (main.tf, který vše definuje, a terraform.tfvars, kde je API token a jméno domény) pustíte nejprve:

terraform init

a poté:

terraform plan

To vám ukáže, co se bude zakládat – nejspíš toho ale bude hodně a stejně se v tom nevyznáte. Pokud tohle děláte s pomocí AI, klidně ji požádejte o kontrolu. Jakmile budete chtít změny „aplikovat“:

terraform apply

Jakmile tohle proběhne bez chyb (opět je dobré vzít výystup a nechat si od AI poradit), je zóna v CF kompletně nastavená a připravená na změnu vašeho DNS.

Změna DNS a ověření

Změnu DNS děláte přes NSSET, jak už bylo popsáno dříve. A až tohle uděláte, nezbývá než čekat.

Případné ověření, zda „už“ to jede (a zda to jede správně) už je věc samostatná.

Migrujete víc domén? Nová složka pro každou

POZOR! Budete-li migrovat víc domén, zakládejte si na to nové složky, do kterých dáte výše uvedené soubory. Ve složce totiž zůstává „paměť“ migrace (její stav) – dá se k tomu nejen vracet, ale hlavně nestačí prostě jen změnit soubor terraform.tfvars a pustit to na novou doménu.