Skip to content

danylomikula/terraform-hcloud-network

Repository files navigation

Hetzner Cloud Terraform Module

Release Pre-Commit License

Terraform module that provisions and manages Hetzner Cloud networks, subnets, and routes.

Features

  • Optional creation of a new Hetzner Cloud network or reuse of an existing one
  • Ability to reference existing networks by either ID or name when reusing infrastructure
  • Support for multiple subnets across network zones and subnet types (server, cloud, or vswitch)
  • Optional custom routes for advanced scenarios (VPN gateways, proxy servers)
  • Optional delete protection and vSwitch route exposure on the managed network
  • Helper outputs with aggregated subnet IDs (per zone and for vSwitch subnets) to simplify downstream wiring
  • Consistent outputs that expose identifiers and computed attributes for downstream modules

Important Limitations

Network Zone Constraint: All subnets within a single Hetzner Cloud network must be in the same network zone (e.g., eu-central, us-east, us-west, ap-southeast).

What you CAN do:

  • ✅ Create network with subnets in any single zone (eu-central, us-east, us-west, ap-southeast)
  • ✅ Connect servers from different locations within that zone (e.g., fsn1, nbg1, hel1 all in eu-central)
  • ✅ All servers in the network can communicate via private IPs regardless of their specific location

What you CANNOT do:

  • ❌ Mix network zones in one network (e.g., eu-central + us-east subnets in same network)
  • ❌ Create a single network spanning Europe and USA

If you need resources in multiple network zones: Create separate networks for each zone:

  • Network A in eu-central for European resources
  • Network B in us-east for USA resources
  • Servers in different networks cannot communicate via private IPs

This is documented in Hetzner Cloud Networks FAQ:

"all locations within a Network have to be from the same network zone as the subnet. All subnets within a Network also have to be from the same network zone."

vSwitch Limitation: vSwitch subnets (for connecting dedicated servers) only work in eu-central network zone. You must create vSwitches in Hetzner Robot first, then reference their IDs in your configuration.

Usage

module "hcloud_network" {
  source  = "danylomikula/network/hcloud"
  version = "~> 2.1"

  create_network = true
  name           = "lab-core"
  ip_range       = "10.10.0.0/16"

  labels = {
    environment = "lab"
    owner       = "platform"
  }

  subnets = {
    edge = {
      type         = "cloud"
      network_zone = "eu-central"
      ip_range     = "10.10.10.0/24"
    }

    # vSwitch subnet for connecting dedicated servers
    # Note: vSwitch ID must be created in Hetzner Robot first
    # Only works in eu-central network zone
    compute = {
      type         = "vswitch"
      network_zone = "eu-central"
      ip_range     = "10.10.20.0/24"
      vswitch_id   = 12345  # from Robot console
    }
  }
}

To reuse an existing network by name and let the module manage only subnets/routes:

module "hcloud_network_existing" {
  source  = "danylomikula/network/hcloud"
  version = "~> 2.0"

  create_network        = false
  existing_network_name = "lab-core-shared"

  subnets = {
    workers = {
      type         = "server"
      network_zone = "eu-central"
      ip_range     = "10.10.30.0/24"
    }
  }
}

Review the examples directory for a fully working configuration that you can copy into your environment.

Example scenarios

Directory Description
examples/basic Creates a new network with both standard and vSwitch subnets.
examples/reuse-existing Reuses an existing network by name and only adds new subnets. Outputs aggregated subnet IDs for quick consumption.
examples/vswitch-subnets Focuses on vSwitch-backed subnets that attach to pre-existing vSwitch IDs supplied via variables.
examples/routes-only Demonstrates managing custom routes on an existing network (e.g., for VPN gateway or proxy server scenarios).

Importing Existing Networks

Networks created outside Terraform (e.g. via the Hetzner Cloud Console) can be imported into this module with an import block. If the network itself should stay unmanaged, skip the import entirely and set create_network = false with existing_network_id or existing_network_name — the module then only manages the subnets and routes it creates.

To adopt the network fully, inspect it with hcloud network describe <network-id> -o json, mirror it in the module input, then verify the first plan shows no changes:

module "network" {
  source  = "danylomikula/network/hcloud"
  version = "~> 2.1"

  name     = "legacy-core"
  ip_range = "10.0.0.0/16"

  # Match the live protection flag — otherwise the first plan
  # wants to disable it.
  delete_protection = true

  subnets = {
    web = {
      type         = "cloud"
      network_zone = "eu-central"
      ip_range     = "10.0.1.0/24"
    }
  }

  routes = {
    vpn-backhaul = {
      destination = "172.16.0.0/12"
      gateway     = "10.0.1.10"
    }
  }
}

# Import the network by its ID.
import {
  to = module.network.hcloud_network.this[0]
  id = "1234567"
}

# Subnets import with "<network_id>-<ip_range>".
import {
  to = module.network.hcloud_network_subnet.this["web"]
  id = "1234567-10.0.1.0/24"
}

# Routes import with "<network_id>-<destination>" — the gateway
# is not part of the import ID.
import {
  to = module.network.hcloud_network_route.this["vpn-backhaul"]
  id = "1234567-172.16.0.0/12"
}

Import checklist:

  1. Match ip_range exactly — changing it replaces the network. Verify the first plan shows no destroy/replace actions.
  2. Match delete_protection and expose_routes_to_vswitch to the live flags. Both default to false, so importing a protected network without delete_protection = true leaves a plan diff that wants to disable the protection.
  3. Subnet and route map keys are free-form Terraform addresses — resource identity comes from the import ID, so pick keys that read well and keep them stable.
  4. Match labels to the live network — a mismatch is only a harmless in-place update, but it keeps the first plan from being clean.
  5. delete_protection only guards against deletion outside of Terraform (Console/API): when Terraform itself destroys the network, the provider lifts the lock first. To protect the network from terraform destroy as well, use the prevent_destroy lifecycle attribute.

Requirements

Name Version
terraform >= 1.14.0
hcloud >= 1.66.0

Providers

Name Version
hcloud >= 1.66.0

Modules

No modules.

Resources

Name Type
hcloud_network.this resource
hcloud_network_route.this resource
hcloud_network_subnet.this resource
hcloud_network.existing_by_id data source
hcloud_network.existing_by_name data source

Inputs

Name Description Type Default Required
create_network When true, the module creates the Hetzner Cloud network; when false, an existing network ID must be provided. bool true no
delete_protection Enable delete protection on the managed network. Has no effect when create_network is false (the module does not modify protection on networks it merely references). bool false no
existing_network_id ID of an existing Hetzner Cloud network to reuse when create_network is false. number null no
existing_network_name Name of an existing Hetzner Cloud network to reuse when create_network is false. string null no
expose_routes_to_vswitch Expose the network routes to the vSwitch connection. Only takes effect when a vSwitch connection is active. Has no effect when create_network is false. bool false no
ip_range CIDR of the Hetzner Cloud network (for example 10.0.0.0/16). Required when create_network is true. string null no
labels Labels applied to the network and subnets. map(string) {} no
name Name for the Hetzner Cloud network. Required when create_network is true. string null no
routes Optional map of custom routes to configure inside the network.
map(object({
destination = string
gateway = string
}))
{} no
subnets Map of subnet definitions keyed by an arbitrary name.
map(object({
type = string
network_zone = string
ip_range = string
vswitch_id = optional(number)
}))
{} no

Outputs

Name Description
network_id ID of the Hetzner Cloud network managed (or referenced) by this module.
network_ip_range CIDR of the Hetzner Cloud network.
network_labels Labels currently set on the network object.
network_name Name of the Hetzner Cloud network.
routes Routes configured in the network through this module.
subnet_ids_by_zone Map of network zones to the IDs of subnets created in each zone.
subnets Details about the subnets created by this module.
vswitch_subnet_ids List of subnet IDs that are associated with vSwitch subnets.

Related Modules

Module Description GitHub Terraform Registry
terraform-hcloud-server Manage Hetzner Cloud servers GitHub Registry
terraform-hcloud-firewall Manage Hetzner Cloud firewalls GitHub Registry
terraform-hcloud-ssh-key Manage Hetzner Cloud SSH keys GitHub Registry

Authors

Module managed by Danylo Mikula.

Contributing

Contributions are welcome! Please read the Contributing Guide for details on the process and commit conventions.

License

Apache 2.0 Licensed. See LICENSE for full details.

About

Terraform module for managing Hetzner Cloud networks, subnets, and routes.

Topics

Resources

License

Contributing

Stars

1 star

Watchers

0 watching

Forks

Sponsor this project

Contributors

Languages