Hetzner Server Fleet
N-server fleet with placement group, firewall, primary IPs, and cloud-init — Hetzner's price/perf with guardrails.
Verification
Static-verifiedPassed: 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_idsrequired; 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
| Name | Type | Default | Description |
|---|---|---|---|
name | string | — | Base name for all resources (required) |
ssh_key_ids | list(string) | — | SSH key names/IDs for root access (required, non-empty) |
server_count | number | 2 | Fleet size (1–50; ≤ 10 with placement group) |
server_type | string | cx22 | Server type slug |
image | string | ubuntu-24.04 | OS image slug or snapshot ID |
location | string | fsn1 | Location slug (servers and managed primary IPs share it) |
user_data | string | null | Cloud-init bootstrap (changing replaces servers) |
backups | bool | false | Automated backups (+20% cost) |
protection | bool | false | Delete + rebuild protection on servers/IPs |
placement_group_enabled | bool | true | Spread placement group |
manage_primary_ips | bool | true | Stable Terraform-managed primary IPv4s |
enable_public_ipv4 | bool | true | Public IPv4 per server (billed by Hetzner) |
enable_ipv6 | bool | true | Public IPv6 /64 per server (free) |
ssh_source_ips | list(string) | [] | CIDRs allowed to reach SSH; empty = SSH closed |
allow_icmp | bool | true | Allow inbound ICMP |
firewall_rules | list(object) | [] | Extra rules {direction, protocol, port, source_ips, destination_ips, description} |
labels | map(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
locationas 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 = falseand 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/hcloudprovider>= 1.0, < 2.0(managed primary IPs andpublic_netneed ≥ 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).