28 - Subnets & Private Networking
Working Code:
terraform/exercise-28-subnet/
The Problem: Giving every server a public IP is a security risk. Database servers should never be directly accessible from the internet.
The Solution: Create a private network. Use a Gateway (Jump Host) for access.
Objective
Deploy network (10.0.0.0/8) and subnet (10.0.1.0/24) with:
- Gateway: Public IP + Private IP (
10.0.1.2) - Internal: Private IP only (
10.0.1.3)
Network Architecture
Internet
|
v
[ Gateway ] (Public + 10.0.1.2)
|
+--- [ Internal ] (10.0.1.3 only)How-to
1. Define Network
hcl
resource "hcloud_network" "net" {
name = "private-net"
ip_range = "10.0.0.0/8"
}
resource "hcloud_network_subnet" "subnet" {
network_id = hcloud_network.net.id
type = "cloud"
network_zone = "eu-central"
ip_range = "10.0.1.0/24"
}2. Internal Server (No Public IP)
hcl
resource "hcloud_server" "internal" {
name = "internal"
public_net {
ipv4_enabled = false # Isolated!
ipv6_enabled = false
}
network {
network_id = hcloud_network.net.id
ip = "10.0.1.3"
}
}3. Access via Gateway
bash
# ProxyJump (recommended)
ssh -J devops@<gateway-ip> devops@10.0.1.3
# Manual hop
ssh devops@<gateway-ip>
ssh devops@10.0.1.34. Cloud-Init
Both servers use cloud-init for:
- SSH hardening (no passwords, no root login)
- UFW firewall configuration
- Custom
/etc/hoststemplate for private DNS resolution (gatewayandinternhostnames)
The gateway additionally installs fail2ban for intrusion prevention.
Internal Server Limitations
The intern server has package_update: false because it has no internet access. Package management requires the application gateway from Exercise 29.
Verification
- Apply configuration:
terraform apply - SSH to gateway with agent forwarding so your key is available for the second hop:bash
ssh -A devops@$(terraform output -raw gateway_ipv4_address) - From gateway, SSH to intern via hostname:bash
ssh devops@intern - Verify intern has no internet:bash
ping -c 3 1.1.1.1 # Should fail (no route to internet) - Verify private connectivity:bash
ping -c 3 gateway # Should succeed from intern
Problems & Learnings
Common Issues
Permission denied (publickey)when SSHing from gateway to intern: The gateway doesn't hold your private key. Connect withssh -A(agent forwarding) so the gateway can use your local key for the second hop. On macOS the agent is empty by default — runssh-add --apple-use-keychain ~/.ssh/id_ed25519first.- The intern server cannot install packages or run updates — this is by design and is resolved in Exercise 29
- Ensure proper
depends_onordering: subnet must exist before servers, gateway must exist before intern - Hetzner Cloud requires a network route (
0.0.0.0/0 → gateway) for internal traffic forwarding
Key Takeaways
- Use separate firewalls per role to enforce least-privilege network access
- Cloud-init
manage_etc_hosts: templateenables custom hostname resolution across the private network - Disabling public interfaces (
ipv4_enabled = false) provides true network isolation