Adding Additional IPv6 Addresses on Hetzner Cloud Servers (Debian 12/13)
Configuring additional IPv6 addresses on Hetzner Cloud servers running Debian 12/13 requires a careful approach to work harmoniously with cloud-init. This guide is an updated version of my previous tutorial, reflecting lessons learned from production deployments and providing a more reliable method that avoids common pitfalls.
What’s changed since the original guide:
- New approach using
post-up
hooks instead of multiple static interface definitions - Better compatibility with cloud-init and DHCP
- Avoids “File exists” errors during network operations
- More production-safe workflow that doesn’t require restarting networking services
Note: These instructions are specifically for Debian 12/13 systems using /etc/network/interfaces
. Ubuntu users should refer to Netplan documentation.
Understanding the Challenge
Hetzner Cloud servers use cloud-init to manage the primary network configuration in /etc/network/interfaces.d/50-cloud-init
. Each server receives a /64
IPv6 subnet (e.g., 2001:db8:1234:5678::/64
), allowing you to use any address within this range.
The challenge is adding additional IPv6 addresses without:
- Interfering with cloud-init’s management
- Causing “File exists” errors during network restarts
- Losing configuration after reboots
The Recommended Method: Post-Up Hooks
The most reliable approach uses post-up
hooks with manual mode, which adds IP addresses after the interface is brought up by cloud-init.
Step 1: Check Your Existing Configuration
First, verify your current setup:
cat /etc/network/interfaces.d/50-cloud-init
You should see something like:
auto eth0
iface eth0 inet dhcp
iface eth0 inet6 static
address 2001:db8:1234:5678::1/64
dns-nameservers 2a01:4ff:ff00::add:1 2a01:4ff:ff00::add:2
gateway fe80::1
Note your subnet prefix – you’ll use this for additional addresses.
Step 2: Add IPv6 Addresses Immediately
For immediate availability without waiting for a reboot, add the addresses directly:
sudo ip -6 addr add 2001:db8:1234:5678::2/128 dev eth0
sudo ip -6 addr add 2001:db8:1234:5678::3/128 dev eth0
sudo ip -6 addr add 2001:db8:1234:5678::4/128 dev eth0
Important: Use /128
prefix for additional addresses, not /64
.
Verify they’re active:
ip -6 addr show dev eth0
ping6 -c 3 2001:db8:1234:5678::2
Step 3: Make Configuration Persistent
Create a new configuration file that loads after cloud-init:
sudo nano /etc/network/interfaces.d/99-custom
Add the following content (replace with your actual IPv6 subnet):
# Additional IPv6 addresses
iface eth0 inet6 manual
post-up ip -6 addr add 2001:db8:1234:5678::2/128 dev $IFACE || true
post-up ip -6 addr add 2001:db8:1234:5678::3/128 dev $IFACE || true
post-up ip -6 addr add 2001:db8:1234:5678::4/128 dev $IFACE || true
pre-down ip -6 addr del 2001:db8:1234:5678::2/128 dev $IFACE || true
pre-down ip -6 addr del 2001:db8:1234:5678::3/128 dev $IFACE || true
pre-down ip -6 addr del 2001:db8:1234:5678::4/128 dev $IFACE || true
Key points about this configuration:
iface eth0 inet6 manual
– Prevents interference with cloud-init’s static configurationpost-up
– Adds addresses after interface comes uppre-down
– Removes addresses before interface goes down|| true
– Prevents errors if address already exists (important for reliability)$IFACE
– Automatically expands to the interface name
Step 4: Verify Configuration
Check that your file is syntactically correct:
cat /etc/network/interfaces.d/99-custom
Verify your addresses are active:
ip -6 addr show dev eth0
You should see all your addresses listed with “scope global”.
Step 5: Test Persistence (Optional)
The safest way to test that your configuration survives a reboot is to actually reboot during a maintenance window:
sudo reboot
After reboot, verify:
ip -6 addr show dev eth0
All your additional IPv6 addresses should be present automatically.
Why Not Restart Networking?
You might be tempted to run systemctl restart networking
to apply changes, but this is not recommended on production systems because:
- It can drop active SSH connections
- It may cause “File exists” errors with DHCP routes (cosmetic but alarming)
- It’s unnecessary – adding IPs directly works immediately
The workflow of “add now + configure for persistence” is safer and more reliable.
Alternative: Subnet-Wide Accessibility (Advanced)
If you want your server to respond to all IP addresses within your /64
subnet without explicitly configuring each one, add these lines to your 99-custom
file:
iface eth0 inet6 manual
post-up ip -6 route add local 2001:db8:1234:5678::/64 dev eth0 || true
pre-down ip -6 route del local 2001:db8:1234:5678::/64 dev eth0 || true
This creates a local route for the entire subnet. Use this carefully and only if you understand the implications for your security and firewall rules.
Troubleshooting
Q: My addresses disappear after reboot
- A: Check that
/etc/network/interfaces.d/99-custom
exists and has correct syntax - Verify the file is readable:
ls -la /etc/network/interfaces.d/
Q: I get “RTNETLINK answers: File exists” errors
- A: This is normal when restarting networking on a live system. The
|| true
in the config prevents this from being a problem. Your network is working fine.
Q: Can I use a different numbering scheme?
- A: Yes! You can use any address in your
/64
subnet. Examples:- Sequential:
::2
,::3
,::4
- Organized:
::100
,::200
,::300
- Meaningful:
::web
,::mail
,::db
- Random:
::1a2b:3c4d
,::cafe:babe
,::dead:beef
- Sequential:
Q: Should I modify the 50-cloud-init file?
- A: No! Cloud-init regenerates this file. Always use separate numbered files like
99-custom
.
Adding More Addresses Later
To add additional IPv6 addresses in the future:
- Add immediately:
sudo ip -6 addr add 2001:db8:1234:5678::5/128 dev eth0
- Edit
/etc/network/interfaces.d/99-custom
and add anotherpost-up
line - Test during next reboot
Conclusion
This method provides a production-safe way to add multiple IPv6 addresses to Debian 12/13 systems on Hetzner Cloud while respecting cloud-init’s management. The key advantages are:
- Non-invasive to existing configuration
- Works reliably with DHCP
- Survives reboots automatically
- Easy to maintain and extend
- No service restarts required
By using post-up
hooks instead of multiple static interface definitions, you avoid conflicts with cloud-init and ensure your configuration remains stable across system updates and reboots.