Terraform module for managing Hetzner Cloud servers with full support for all provider features including private networks, firewalls, placement groups, and advanced configuration options.
- Multi-server management: Manage multiple servers with a single module invocation.
- Private networking: Attach servers to Hetzner Cloud private networks with static IPs.
- Firewall integration: Apply firewalls at server creation time.
- Placement groups: Control server placement for high availability or performance.
- Flexible configuration: Support for all hcloud_server resource attributes.
- Common settings: Apply SSH keys, labels, and firewalls to all servers.
- Rich outputs: Detailed server information grouped by location, type, and more.
module "server" {
source = "danylomikula/server/hcloud"
version = "~> 3.0"
servers = {
web-1 = {
server_type = "cx23"
location = "nbg1"
image = "ubuntu-24.04"
ssh_keys = [12345]
labels = {
role = "webserver"
}
}
}
}module "network" {
source = "danylomikula/network/hcloud"
version = "~> 1.0"
create_network = true
name = "app-network"
ip_range = "10.0.0.0/16"
subnets = {
main = {
type = "cloud"
network_zone = "eu-central"
ip_range = "10.0.1.0/24"
}
}
}
module "servers" {
source = "danylomikula/server/hcloud"
version = "~> 3.0"
servers = {
app-01 = {
server_type = "cx23"
location = "nbg1"
image = "ubuntu-24.04"
networks = [{
subnet_id = module.network.subnets["main"].id
ip = "10.0.1.10"
}]
}
app-02 = {
server_type = "cx23"
location = "fsn1"
image = "ubuntu-24.04"
networks = [{
subnet_id = module.network.subnets["main"].id
ip = "10.0.1.11"
}]
}
}
common_ssh_keys = [12345]
common_labels = {
cluster = "app"
managed_by = "terraform"
}
}# Generate SSH key.
module "ssh_key" {
source = "danylomikula/ssh-key/hcloud"
version = "~> 1.0"
create_key = true
name = "cluster-key"
algorithm = "ED25519"
}
# Create network.
module "network" {
source = "danylomikula/network/hcloud"
version = "~> 1.0"
create_network = true
name = "web-network"
ip_range = "10.100.0.0/16"
subnets = {
web = {
type = "cloud"
network_zone = "eu-central"
ip_range = "10.100.1.0/24"
}
}
}
# Create servers.
module "server" {
source = "danylomikula/server/hcloud"
version = "~> 3.0"
servers = {
web-1 = {
server_type = "cx23"
location = "nbg1"
image = "ubuntu-24.04"
networks = [{
subnet_id = module.network.subnets["web"].id
ip = "10.100.1.10"
}]
labels = {
role = "webserver"
}
}
}
common_ssh_keys = [module.ssh_key.ssh_key_id]
common_labels = {
managed_by = "terraform"
}
}See complete example for a full working configuration.
Install hcloud CLI:
brew install hcloudSetup guide: https://github.com/hetznercloud/cli/blob/main/docs/tutorials/setup-hcloud-cli.md
List server types:
hcloud server-type listList locations:
hcloud location listDocumentation: https://docs.hetzner.com/cloud/general/locations/
List available images:
# List all system images.
hcloud image list --type system
# Filter for specific OS.
hcloud image list --type system | grep ubuntu
hcloud image list --type system | grep rockyUse hcloud_image data source to automatically select the latest image:
# Find latest Rocky Linux 9 for x86 architecture.
data "hcloud_image" "rocky9" {
with_selector = "os_flavor=rocky"
with_architecture = "x86"
most_recent = true
}
# Find latest Ubuntu for ARM64.
data "hcloud_image" "ubuntu_arm" {
name = "ubuntu-24.04"
with_architecture = "arm"
}
# Use in server configuration.
module "servers" {
source = "danylomikula/server/hcloud"
version = "~> 3.0"
servers = {
app-01 = {
server_type = "cx23"
image = data.hcloud_image.rocky9.name # Uses latest Rocky 9.
location = "nbg1"
}
app-arm = {
server_type = "cax11" # ARM64 server.
image = data.hcloud_image.ubuntu_arm.name
location = "fsn1"
}
}
}Data source reference: https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs/data-sources/image
Available selectors:
os_flavor=ubuntu/rocky/debian/fedoraos_version=24.04/9/12
Architectures:
x86- Standard Intel/AMD (cx, ccx series)arm- ARM64 (cax series)
- Use private networks for inter-server communication.
- Enable backups for production servers.
- Apply firewalls to restrict access.
- Use placement groups for high availability.
- Set labels for organization and management.
- Use
common_*variables to reduce duplication. - Generate SSH keys via the ssh-key module.
Servers created outside Terraform (e.g. via the Hetzner Cloud Console) can be imported into this module. One upstream provider limitation matters here: terraform import of hcloud_server does not populate the inline network and public_net blocks in state (hetznercloud/terraform-provider-hcloud#944). If an imported server declares these blocks, the first plan shows phantom network / public_net changes, and applying a public_net change power-cycles the server.
The clean pattern is to omit networks and public_net for imported servers and manage attachments outside the server resource. This is a per-server decision, so imported and newly created servers can coexist in one module call.
module "servers" {
source = "danylomikula/server/hcloud"
version = "~> 3.0"
servers = {
# Imported server: networks and public_net are deliberately omitted,
# so the module emits no inline attachment blocks and the provider
# leaves existing attachments untouched.
legacy-web = {
server_type = "cx23"
location = "nbg1"
image = "ubuntu-24.04"
}
}
}
# Import the server by its ID.
import {
to = module.servers.hcloud_server.this["legacy-web"]
id = "4711"
}
# Manage the private network attachment via a separate resource.
resource "hcloud_server_network" "legacy_web" {
server_id = module.servers.server_ids["legacy-web"]
network_id = 98765 # or module.network.network_id from terraform-hcloud-network
ip = "10.0.1.10"
}
# Import the existing attachment as "<server_id>-<network_id>".
import {
to = hcloud_server_network.legacy_web
id = "4711-98765"
}Primary IPs need no extra configuration: with public_net omitted they stay attached and unmanaged. To bring them under Terraform as well, import hcloud_primary_ip resources by ID and bind them with assignee_type = "server" / assignee_id.
Import checklist:
- Use the real server name as the key in
servers— the module setsnamefrom the key, so a different key renames the server. - Match
server_type,locationandimageto the live server (check withhcloud server describe <name>); a mismatchedimageforces replacement. Verify the first plan shows no destroy/replace actions. - Mirror other live settings that differ from module defaults (e.g.
backups = true).
Trade-offs: without inline blocks the private_network_ips and private_network_mac_addresses outputs are empty for imported servers, and ipv4_enabled / ipv6_enabled cannot be toggled through the module. Switching an imported server to inline networks / public_net later re-manages attachments through the server resource — plan carefully, as public_net changes power-cycle the server.
The module uses ignore_changes for:
ssh_keys: Prevents recreation when keys are added manually.user_data: Prevents recreation when cloud-init data changes.
| Name | Version |
|---|---|
| terraform | >= 1.14.0 |
| hcloud | >= 1.66.0 |
| Name | Version |
|---|---|
| hcloud | >= 1.66.0 |
No modules.
| Name | Type |
|---|---|
| hcloud_server.this | resource |
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| common_firewall_ids | List of firewall IDs to apply to all servers in addition to per-server firewalls. | list(number) |
[] |
no |
| common_labels | Labels to apply to all servers in addition to per-server labels. | map(string) |
{} |
no |
| common_ssh_keys | List of SSH key IDs to add to all servers in addition to per-server keys. | list(number) |
[] |
no |
| servers | Map of server configurations keyed by friendly name. | map(object({ |
{} |
no |
| Name | Description |
|---|---|
| private_network_ips | Map of server names to their private network IP addresses. |
| private_network_mac_addresses | Map of server names to their private network interface MAC addresses. |
| server_ids | Map of server names to their IDs. |
| server_ipv4_addresses | Map of server names to their IPv4 addresses. |
| server_ipv6_addresses | Map of server names to their IPv6 addresses. |
| servers | Map of all server resources with complete attributes. |
| servers_by_location | Map of locations to lists of server IDs in each location. |
| servers_by_type | Map of server types to lists of server IDs of each type. |
| Module | Description | GitHub | Terraform Registry |
|---|---|---|---|
| terraform-hcloud-network | Manage Hetzner Cloud networks and subnets | GitHub | Registry |
| terraform-hcloud-firewall | Manage Hetzner Cloud firewalls | GitHub | Registry |
| terraform-hcloud-ssh-key | Manage Hetzner Cloud SSH keys | GitHub | Registry |
Module managed by Danylo Mikula.
Contributions are welcome! Please read the Contributing Guide for details on the process and commit conventions.
Apache 2.0 Licensed. See LICENSE for full details.