IaC Bazaar
HetznerStatic-verified

Hetzner Private Network + NAT

Private network with subnets, routes, and a NAT gateway server for egress-only fleets.

terraformAlt & Specialty Clouds#hetzner

Compare Virtual Private Cloud (VPC) across clouds →

hetzner-private-networkterraform 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-private-network

Status: static-validated, live-test pending. Ships under live-test quarantine — validated with tofu fmt, tofu validate, and tflint. Real apply/verify/destroy testing is pending a Hetzner Cloud sandbox account.

Private network for Hetzner Cloud with cloud subnets, egress routes, and a self-managed NAT gateway so egress-only fleets can reach the internet without holding a public IPv4. One module call gives you an RFC1918 network, one or more cloud subnets, a hardened NAT gateway server, and the 0.0.0.0/0 route that funnels private servers' outbound traffic through it. Works with Terraform and OpenTofu (>= 1.6), hcloud provider >= 1.0, < 2.0.

Design & secure defaults

  • Private by default. The network and its subnets carry no public exposure. The only resource with an internet-facing address is the (optional) NAT gateway, and it gets exactly one public IPv4.
  • Deny-by-default gateway firewall. No inbound port is open on the NAT gateway — not even SSH — until you allowlist nat_gateway_ssh_source_ips. Only ICMP is allowed. Outbound stays open so MASQUERADE can work.
  • NAT is a pattern, not a managed service. Hetzner has no managed NAT gateway. This module provisions a small server with a fixed private IP, enables IPv4 forwarding, and installs a persistent iptables MASQUERADE rule via cloud-init. You operate and patch this server — keep SSH key access on it (nat_gateway_ssh_key_ids) and apply OS updates. It is a single instance, so it is also a single point of failure for egress; for HA you would front two gateways yourself.
  • Stable route target. The gateway's private IP is computed deterministically (cidrhost(subnet, nat_gateway_host_index)) and reused as the route gateway, so the egress route always points at the right host. A precondition rejects the network's reserved first IP.
  • One-flag delete protection across the network and the gateway server.

Egress-only servers you create elsewhere simply attach to one of these subnets with ipv4_enabled = false; the network route does the rest.

Usage

module "private_network" {
  source = "./hetzner-private-network"

  name         = "core"
  ip_range     = "10.0.0.0/16"
  network_zone = "eu-central"

  subnets = {
    app  = { ip_range = "10.0.1.0/24" }
    data = { ip_range = "10.0.2.0/24" }
  }

  nat_gateway_enabled        = true
  nat_gateway_location       = "fsn1"
  nat_gateway_ssh_key_ids    = ["ops-key"]
  nat_gateway_ssh_source_ips = ["203.0.113.0/24"]

  labels = { env = "prod" }
}

# An egress-only server reachable only inside the network:
resource "hcloud_server" "worker" {
  name        = "worker-1"
  server_type = "cx22"
  image       = "ubuntu-24.04"
  location    = "fsn1"

  public_net {
    ipv4_enabled = false
    ipv6_enabled = false
  }

  network {
    network_id = module.private_network.network_id
    # IP auto-assigned from the subnet; routes egress via the NAT gateway.
  }
}

Inputs

NameTypeDefaultDescription
namestringBase name for network and derived resources (required)
ip_rangestring10.0.0.0/16RFC1918 range for the whole network
network_zonestringeu-centralZone for cloud subnets (eu-central, us-east, us-west, ap-southeast)
subnetsmap(object){ private = { ip_range = "10.0.1.0/24" } }Cloud subnets keyed by logical name
expose_routes_to_vswitchboolfalseExpose routes to a connected Hetzner vSwitch
delete_protectionboolfalseDelete/rebuild protection on network + gateway
nat_gateway_enabledbooltrueProvision the NAT gateway and egress route
nat_gateway_subnet_keystringnullSubnet hosting the gateway (null = first by sorted key)
nat_gateway_host_indexnumber6Host index for the gateway's fixed private IP (2–250)
nat_gateway_server_typestringcx22Server type for the gateway
nat_gateway_imagestringubuntu-24.04OS image (Debian/Ubuntu — cloud-init assumes apt/iptables)
nat_gateway_locationstringfsn1Location for the gateway (within network_zone)
nat_gateway_ssh_key_idslist(string)[]SSH keys granted root on the gateway (recommended)
egress_destinationslist(string)["0.0.0.0/0"]Destination CIDRs routed via the gateway
nat_gateway_ssh_source_ipslist(string)[]CIDRs allowed to SSH the gateway from the internet; empty = closed
labelsmap(string){}Labels applied to all resources

Outputs

network_id, network_ip_range, subnet_ids (map), subnet_ip_ranges (map), nat_gateway_id, nat_gateway_private_ip, nat_gateway_public_ipv4, nat_gateway_firewall_id, egress_route_destinations.

Notes

  • nat_gateway_location must sit inside network_zone (e.g. fsn1/nbg1/ hel1 for eu-central, ash for us-east). Hetzner does not let you mix a location with the wrong zone.
  • The default nat_gateway_host_index = 6 keeps the gateway clear of the low reserved addresses. If you change the NAT subnet to anything smaller than a /24, make sure the index still fits.
  • The NAT gateway is stateful, single-instance infrastructure: its iptables rule is restored from iptables-persistent on reboot, but the box itself is yours to patch and monitor. Treat it like any internet-facing jump host.
  • Routes only affect destinations not already inside the network, so subnet-to- subnet traffic is never sent through the gateway.

Requirements

  • Terraform or OpenTofu >= 1.6
  • hetznercloud/hcloud provider >= 1.0, < 2.0 (private networks, subnets, routes and hcloud_server_network — latest recommended)

Verification

Static-validated (tofu fmt, tofu validate, tflint). Live apply/destroy testing pending a Hetzner Cloud sandbox account — see catalog status.

License

Commercial — LicenseRef-IaCBazaar-Commercial. See the IaC Bazaar terms.