IaC Bazaar
HetznerStatic-verified

Hetzner Server Fleet

N-server fleet with placement group, firewall, primary IPs, and cloud-init — Hetzner's price/perf with guardrails.

terraformAlt & Specialty Clouds#hetzner

Compare Virtual Machines across clouds →

hetzner-server-fleetterraform v1.7

Verification

Static-verified

Passed: validated and lint-clean (provider-schema-validated for AWS/Azure/GCP; Terraform-language lint elsewhere).

Conformance

  • Static validation (fmt · validate · tflint)
  • No applicable security policies for this provider
  • Plan tests (mocked: validation rules · outputs)

Provenance

  • SHA-256 checksum
  • Signature (pending)

Functional

  • Live test pending (no cloud run yet)

Last verified 2026-06-28 · how we verify

Documentation

hetzner-server-fleet

N-server fleet with placement group, firewall, primary IPs, and cloud-init — Hetzner's price/perf with guardrails. One module call provisions identical servers spread across physical hosts (a host failure costs you at most one server), behind a deny-by-default cloud firewall, with managed primary IPv4 addresses that survive rebuilds and replacements. Works with Terraform and OpenTofu (>= 1.6), hcloud provider >= 1.0, < 2.0.

Secure defaults:

  • Deny-by-default firewall — no inbound port is open (incl. SSH) until you allowlist sources; only ICMP is allowed (toggleable)
  • SSH-key-only access (ssh_key_ids required; no root-password e-mails)
  • Spread placement group on by default (free anti-affinity)
  • Stable, Terraform-managed primary IPv4s (auto_delete = false) so addresses outlive any individual server
  • One-flag delete/rebuild protection across servers and IPs

Usage

module "fleet" {
  source = "./hetzner-server-fleet"

  name         = "web"
  server_count = 3
  server_type  = "cx22"
  location     = "fsn1"
  ssh_key_ids  = ["ops-key"]

  ssh_source_ips = ["203.0.113.0/24"]

  firewall_rules = [
    { protocol = "tcp", port = "443", source_ips = ["0.0.0.0/0", "::/0"], description = "HTTPS" },
  ]

  user_data = file("cloud-init.yaml")
  labels    = { env = "prod" }
}

Inputs

NameTypeDefaultDescription
namestringBase name for all resources (required)
ssh_key_idslist(string)SSH key names/IDs for root access (required, non-empty)
server_countnumber2Fleet size (1–50; ≤ 10 with placement group)
server_typestringcx22Server type slug
imagestringubuntu-24.04OS image slug or snapshot ID
locationstringfsn1Location slug (servers and managed primary IPs share it)
user_datastringnullCloud-init bootstrap (changing replaces servers)
backupsboolfalseAutomated backups (+20% cost)
protectionboolfalseDelete + rebuild protection on servers/IPs
placement_group_enabledbooltrueSpread placement group
manage_primary_ipsbooltrueStable Terraform-managed primary IPv4s
enable_public_ipv4booltruePublic IPv4 per server (billed by Hetzner)
enable_ipv6booltruePublic IPv6 /64 per server (free)
ssh_source_ipslist(string)[]CIDRs allowed to reach SSH; empty = SSH closed
allow_icmpbooltrueAllow inbound ICMP
firewall_ruleslist(object)[]Extra rules {direction, protocol, port, source_ips, destination_ips, description}
labelsmap(string){}Labels applied to all resources

Outputs

server_ids, server_ipv4, server_ipv6, primary_ip_ids, firewall_id, placement_group_id — all maps are keyed by server name.

Notes

  • Managed primary IPs are scoped to the same location as the servers, so they attach correctly wherever Hetzner schedules each server within that location. (Hetzner primary IPs are location-scoped; the provider's per-datacenter pin is deprecated and removed after 2026-07-01.)
  • Outbound traffic stays open unless you add direction = "out" rules — adding any makes outbound deny-by-default per Hetzner firewall semantics.
  • With enable_public_ipv4 = false and IPv6-only, remember most package mirrors work but some registries still require IPv4 (use a NAT or proxy).

Requirements

  • Terraform or OpenTofu >= 1.6
  • hetznercloud/hcloud provider >= 1.0, < 2.0 (managed primary IPs and public_net need ≥ 1.36 — latest recommended)

Verification

Static-validated (fmt, validate, tflint). Live apply/destroy testing pending cloud sandbox availability — see catalog status.

License

Commercial — IaC Bazaar EULA. © IaC Bazaar. Original work (not derived from a third-party module).