Configure IPv6 on a VPS: Static Address, Firewall and Dual-Stack

When you provision a VPS in 2026, the control panel likely hands you a /64 IPv6 subnet alongside your familiar IPv4. Most providers assign the IPv6 address automatically via SLAAC or DHCPv6, and it works, until it doesn't. A reboot, a provider migration, or a misconfigured router advertisement can change your address silently. If you run email, DNS, or any service that relies on a fixed PTR record (rDNS), a dynamic IPv6 address breaks things. This guide walks through assigning a static IPv6 address with systemd-networkd, building a practical nftables firewall for dual-stack traffic, and verifying both protocols serve content. Everything runs on Ubuntu 24.04 LTS and Debian 12, the two most common VPS operating systems in 2026.
静态 IPv6 地址确保邮件和 DNS 服务不因地址变化而中断。
A static IPv6 address ensures email and DNS services do not break when address prefixes change.
Prerequisites
- A Linux VPS running Ubuntu 24.04 LTS or Debian 12, with root or sudo access.
- An IPv6 subnet assigned by your provider (typically a /64, sometimes a /56 or /48).
- The network interface name (shown by
ip link show; common names:eth0,ens3,enp1s0). - The provider-gateway IPv6 address (usually the first address in the subnet, e.g.
2001:db8:1::1). - Backup your current network config before starting:
cp /etc/netplan/*.yaml ~/netplan-backup/(Ubuntu) or back up/etc/network/interfaces(Debian with ifupdown).
Why a static IPv6 address matters
Several VPS providers, including reputable ones like DigitalOcean and Hetzner, have used SLAAC for IPv6 assignment for years. SLAAC works reliably for general connectivity, but it gives you an address derived from the interface MAC address and provider prefix. If your provider changes the prefix, or you reinstall, the address changes. That breaks:
- PTR (rDNS) records: once configured for a specific IPv6, they break when the address changes.
- SPF, DKIM, DMARC: many mail servers now check the sending IPv6 against SPF records. A dynamic address fails the check.
- Monitoring systems: alert rules based on an IPv6 address stop matching after a reboot.
- Firewall rules: if you whitelisted a specific IPv6 in nftables, it stops working after an address change.
A static IPv6 assignment solves all of these. You configure the address once, and it sticks through reboots and provider-side changes, as long as the prefix stays the same. For most VPS users, the provider assigns a static /64 subnet; you pick one address from it and pin it.
Step 1, Identify your current IPv6 configuration
First, check what your VPS already received. Run:
ip -6 addr show
ip -6 route show default
You will see something like:
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
inet6 2001:db8:1::5054:ff:fe12:3456/64 scope global temporary dynamic
inet6 2001:db8:1::1/64 scope global mngtmpaddr dynamic
inet6 fe80::5054:ff:fe12:3456/64 scope link
Note the gateway address from the default route output:
default via fe80::1 dev eth0 proto ra metric 1024 expires 1799sec
If your provider uses a link-local gateway (fe80::/10), that is common. If the provider gave you a global gateway (e.g. 2a01:4f8:c17:b::1), note it separately.
Also record your provider-assigned subnet. In the example above, the prefix is 2001:db8:1::/64. Never use a privacy (temporary) address for a static assignment, those rotate every few hours.
Step 2, Configure a static IPv6 address with systemd-networkd
systemd-networkd is the default network manager on most modern Linux distributions, including both Ubuntu 24.04 and Debian 12. It replaces the older ifupdown and netplan stack on many VPS images.
If your VPS uses netplan (Ubuntu default), you can configure IPv6 in the /etc/netplan/01-netcfg.yaml file. But the more robust, daemon-agnostic way is to use systemd-networkd directly. Many providers (including Hetzner, Vultr, and others) ship systemd-networkd config out of the box.
2a, Disable SLAAC and set the static address
Create or edit a network unit file for your interface. The naming convention is /etc/systemd/network/10-eth0.network (replace eth0 with your actual interface name from Step 1).
[Match]
Name=eth0
[Network]
Address=2001:db8:1::2/64 # Pick an address from your /64 subnet
Gateway=fe80::1 # Link-local gateway from Step 1
DNS=2001:4860:4860::8888 # Google DNS (or your provider's)
DNS=2001:4860:4860::8844
[IPv6AcceptRA]
AcceptRA=false # Disable router advertisements, we set static
[Link]
RequiredForOnline=yes
Explanation of key directives:
Address: the static IPv6. Pick any address in your /64 that is not the gateway (e.g.::1is often reserved; use::2,::10, or something easy to remember).Gateway: your provider's IPv6 gateway. Many use a link-local address (fe80::/10). If yours uses a global address, put that instead.RequiredForOnline=yes: waits for IPv6 connectivity before the system is considered online (useful for services that depend on IPv6).
AcceptRA=false: this is the critical line. It tells the kernel not to accept router advertisements, which would otherwise overwrite your static address with a SLAAC-derived one.
If your provider assigned a larger subnet (e.g. /56 or /48), you can use multiple addresses in the same Address line or add separate lines. For a /64, a single address is sufficient.
2b, Apply and verify
systemctl restart systemd-networkd
networkctl status eth0
Look for the Addresses line to confirm your static IPv6. Then verify connectivity:
ip -6 addr show dev eth0 | grep "2001:db8:1::2" # your chosen address
ping6 -c 4 google.com
If ping fails, check the gateway:
ip -6 route show default
ping6 -c 4 <your_gateway>
If the gateway is unreachable, you either mis-typed it, or the provider uses a global gateway instead of link-local. In that case, edit the .network file to correct it and restart systemd-networkd.
Step 3, Configure nftables for IPv6 (dual-stack firewall)
nftables has been the default firewall in Debian and Ubuntu since their respective 2022 releases. The old iptables still exists as a compatibility layer, but writing new rules in nftables is the correct approach in 2026.
Create a file at /etc/nftables.conf that handles both IPv4 and IPv6 in a single inet table:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
# Allow all outbound traffic
chain output {
type filter hook output priority 0; policy accept;
}
# Inbound traffic
chain input {
type filter hook input priority 0; policy drop;
# Allow established/related
ct state established,related accept
# Allow loopback
iifname lo accept
# Allow ICMP (both IPv4 and IPv6)
meta l4proto icmp accept
meta l4proto ipv6-icmp accept
# Allow SSH on port 22
tcp dport 22 accept
# Allow HTTP/HTTPS
tcp dport { 80, 443 } accept
# Allow NTP (UDP 123)
udp dport 123 accept
# Allow specific services, add your own
# tcp dport 25 accept # SMTP
# tcp dport 465 accept # SMTPS
# tcp dport 993 accept # IMAPS
# Drop everything else (implicit)
}
# Forward chain, empty, drop by default
chain forward {
type filter hook forward priority 0; policy drop;
}
}
Key IPv6 considerations:
- The
inetfamily applies the same rule set to both IPv4 and IPv6. This keeps your firewall simple and avoids maintaining two separate rule sets. meta l4proto ipv6-icmpis the correct way to allow ICMPv6. Blocking ICMPv6 is dangerous, it breaks Path MTU discovery, neighbor discovery, and SLAAC. Unlike IPv4, ICMPv6 is essential for protocol operation.- The
ct state established,related acceptrule ensures that replies to outbound connections (e.g. DNS responses) are allowed in.
Apply the rules:
nft -f /etc/nftables.conf
nft list ruleset
Verify that the ruleset shows both IPv4 and IPv6 entries. The output should include table inet filter with your chains.
3a, Allow ICMPv6 specifically (important)
Many administrators copy their IPv4 iptables rules to IPv6 and block all ICMP. For IPv6, that breaks essential protocol functions. The correct approach is to allow the ICMPv6 types your server needs:
meta l4proto ipv6-icmp accept
If you want stricter control, restrict to specific types:
meta l4proto ipv6-icmp icmpv6 type { echo-request, destination-unreachable, packet-too-big, time-exceeded, parameter-problem } accept
meta l4proto ipv6-icmp icmpv6 type { neighbour-solicit, neighbour-advert } accept
The second set, neighbor solicitation and advertisement, is required for IPv6 address resolution (equivalent to ARP in IPv4). Block them and your VPS cannot reach other hosts on the subnet.
Step 4, Configure rDNS (PTR) for your IPv6
Once your static address is confirmed, set the PTR record. Most VPS control panels let you do this. For thueVPS, the process is straightforward: in the customer portal, under "DNS" or "Reverse DNS", enter your static IPv6 address and the desired hostname (e.g. mail.yourdomain.com).
Why rDNS matters for IPv6: Email servers increasingly verify the sending IP or IPv6 against a PTR record. If your outbound IPv6 does not have a matching PTR, your mail may be flagged or rejected, especially by Gmail and Outlook. For a Linux VPS running your own SMTP server, configuring rDNS for both IPv4 and IPv6 is not optional, it is a deliverability requirement.
After setting the PTR, verify it resolves correctly:
host 2001:db8:1::2
dig -x 2001:db8:1::2 +short
The output should show your configured hostname. If not, wait a few minutes (TTL can delay propagation) and check the provider's DNS panel for any errors.
Step 5, Verify dual-stack operation
Dual-stack means the server serves content over both IPv4 and IPv6. A simple way to test:
curl -6 https://checkip.amazonaws.com
curl -4 https://checkip.amazonaws.com
The first command returns your IPv6 address; the second returns your IPv4 address. Both should succeed. If the IPv4 curl fails, ensure IPv4 is still configured (check ip -4 addr). If the IPv6 curl fails, the static address or firewall is likely blocking IPv6.
For a web server test, assuming you run Nginx or Apache, visit your site with ipv6-test.com or run:
curl -6 -I https://yourserver.com
If Nginx returns a response, IPv6 is serving correctly.
For a more comprehensive dual-stack test, use the ipv6-test.com API from your server:
curl https://ipv6-test.com/api/myip.php
This returns your public IPv6 address, confirming that your VPS is reachable over IPv6 from the internet.
Troubleshooting
Ping to gateway works, but ping to external IPv6 fails
This usually means your provider does not route your subnet correctly, or the firewall rules are wrong. Check:
ip -6 route show
ping6 -c 4 2001:4860:4860::8888 # Google DNS
If ping to the DNS fails but the gateway is reachable, the issue is likely provider-side (prefix not routed to your VPS). Contact support with the output of ip -6 addr and ip -6 route and ask them to verify the routing.
After disabling SLAAC, IPv6 vanishes
You disabled AcceptRA but also removed the static address. Move the Address line from the systemd-networkd config and ensure it includes the correct prefix and gateway. Restart the daemon. If the address still does not appear, check for a duplicate .network file (e.g. /etc/systemd/network/*.network) that may match the interface and override your config.
nftables blocks everything after applying the ruleset
You locked yourself out of SSH. This can happen if:
- You forgot to include the
tcp dport 22 acceptrule. - The rule was written but SSH listens on a non-default port.
- The
inet tabledoes not exist in the kernel.
Recovery method for most VPS providers: reboot the server from the control panel. During boot, nftables may not load if you put the rule in an init script that runs after SSH is up. The safe approach is to add a persistent cron job that flushes the ruleset every 5 minutes during the first hour, or keep the VNC / serial console available. On thueVPS, each VPS comes with a KVM-over-IP console (IPKVM) for out-of-band access, so you can log in even if the firewall is misconfigured.
IPv4 works, IPv6 does not after nftables reload
The inet table covers both protocols. Verify the ruleset includes both: nft list ruleset | grep -E 'inet|ip6'. If only IPv4 rules appear, the kernel may be missing IPv6 support. Check:
sysctl net.ipv6.conf.all.disable_ipv6
If it returns 1, enable IPv6: sysctl -w net.ipv6.conf.all.disable_ipv6=0 and add net.ipv6.conf.all.disable_ipv6 = 0 to /etc/sysctl.conf to make it permanent.
FAQ
Can I keep SLAAC enabled and still set a static IPv6?
Yes, but it is messy. You can configure systemd-networkd with both the static Address and AcceptRA=yes. The kernel will then have two addresses, your static one and a dynamic one. For services that bind to a specific address (e.g. Nginx listen [2001:db8:1::2]:443), the static one works. But for outgoing connections, the kernel will randomly pick the dynamic or static address, which can break rDNS. The clean solution is to disable SLAAC entirely.
Does nftables handle IPv6 differently than iptables?
Yes, in one important way: nftables uses a single inet table family to cover both IPv4 and IPv6, while iptables required separate iptables and ip6tables commands. This is the reason nftables is now preferred, fewer duplicate rules, less human error.
How do I block a specific IPv6 address with nftables?
nft add rule inet filter input ip6 saddr 2001:db8:bad::1 drop
Does a static IPv6 affect my VPS performance?
No measurable impact. The kernel handles static and dynamic addresses the same way for routing and packet processing. Static assignment may actually reduce overhead slightly by removing the need for SLAAC processing (router solicitation and advertisement messages).
What if my provider does not support IPv6?
In 2026, almost every VPS provider supports IPv6. If yours does not, it is a red flag. You can still configure a tunnel via HE Tunnelbroker, but native IPv6 is always preferred for lower latency. For customers of thueVPS, each VPS comes with a dedicated IPv4 and a /64 IPv6 subnet, dual-stack is the default.
Related articles
- Set up nftables firewall on a VPS to replace iptables
- Hardening SSH on a new VPS: keys, ports, fail2ban and CrowdSec
- User and sudo permissions management on Linux VPS
- Configure DNS records for your domain: A, CNAME, MX, TXT
VPS 双栈配置:静态 IPv6 与防火墙要点
本教程指导在 Ubuntu 24.04 或 Debian 12 的 VPS 上配置静态 IPv6 地址,设置 nftables 防火墙同时覆盖 IPv4 和 IPv6,并验证双栈运行。关键操作包括:禁用 SLAAC 自动配置以防止地址漂移、在 nftables 中允许 ICMPv6(避免破坏路径 MTU 发现和邻居发现)、以及配置 rDNS 记录以保障邮件投递。静态 IPv6 确保 PTR 记录稳定,防火墙统一管理两个协议栈,降低了运维复杂性。对于需要本地 IPv4 和可靠 IPv6 的业务场景,选择提供双栈基础设施的越南 VPS 服务商是一个务实的选择。


