IPv6 on Linux — A Practical Guide
Configure IPv6 on Linux from scratch: address types, SLAAC with privacy extensions, DHCPv6, static addressing, routing, nftables firewall rules, and dual-stack operation.
Before you start
- ▸Root or sudo access on the target host
- ▸A network interface with an upstream router or ISP that provides IPv6
- ▸Basic familiarity with Linux networking concepts (interfaces, routes, CIDR)
- ▸systemd-based distribution (systemd-networkd or NetworkManager present)
IPv6 adoption has crossed 40 % of global internet traffic, yet many sysadmins still treat it as optional. It is not — modern cloud providers, ISPs, and container runtimes assign IPv6 by default, and understanding it prevents outages and security gaps. This guide covers address types, stateless and stateful autoconfiguration, routing, firewall rules, and running dual-stack, all using current tools.
IPv6 Address Fundamentals
IPv6 addresses are 128 bits written as eight colon-separated groups of four hex digits. Consecutive all-zero groups collapse to :: (once per address). Key types you will encounter daily:
- Link-local (fe80::/10) — auto-assigned on every interface; used for neighbor discovery and routing protocols. Never routable beyond the local link.
- Global unicast (2000::/3) — the public IPv6 equivalent of a routable IPv4 address.
- Unique local (fc00::/7) — private, non-routable across the internet; analogous to RFC 1918 space.
- Loopback (::1/128) — equivalent to 127.0.0.1.
- Multicast (ff00::/8) — replaces IPv4 broadcast; neighbor solicitation uses ff02::1 and ff02::2.
Prefix length works identically to CIDR. A typical home or VPS gets a /64 for a single subnet. ISPs often delegate a /48 or /56 to customers for further subdivision.
Checking Your Current IPv6 State
Use ip — never the deprecated ifconfig.
ip -6 addr show
Look for scope global entries. A link-local address (scope link) is always present if the interface is up; it does not mean you have internet connectivity.
ip -6 route show
A working setup shows a default route via fe80::... on your uplink interface, plus connected routes for each assigned prefix.
SLAAC — Stateless Address Autoconfiguration
SLAAC (RFC 4862) lets a host generate its own global address without a DHCPv6 server. The router sends a Router Advertisement (RA) containing the network prefix; the host appends a 64-bit Interface Identifier derived from either the MAC address (EUI-64) or a random value (Privacy Extensions, RFC 4941).
Enable Privacy Extensions (Recommended)
Without privacy extensions, your MAC address is embedded in your public IPv6 address — a tracking risk. Enable them persistently via sysctl:
cat >> /etc/sysctl.d/99-ipv6-privacy.conf << 'EOF'
net.ipv6.conf.all.use_tempaddr = 2
net.ipv6.conf.default.use_tempaddr = 2
EOF
sysctl --system
Value 2 means prefer temporary addresses for outgoing connections. Value 1 generates them but does not prefer them.
Verify SLAAC Is Working
ip -6 addr show dev eth0
You should see a scope global temporary dynamic address alongside a scope global dynamic address once an RA arrives. If neither appears, the router is not sending RAs or IPv6 forwarding is blocking them.
DHCPv6 — Stateful Address Assignment
DHCPv6 assigns addresses and options (DNS, NTP) explicitly, like DHCPv4. It is common in enterprise environments and with ISPs that use prefix delegation. The RA still plays a role: the Managed (M) and Other (O) flags in the RA tell the client whether to use DHCPv6 for addresses and/or options.
Install a DHCPv6 Client
Debian/Ubuntu — wide-dhcpv6-client or the ISC client bundled with isc-dhcp-client:
apt install wide-dhcpv6-client
Fedora/RHEL/Rocky:
dnf install dhcp-client
Arch:
pacman -S dhclient
In practice, on modern systems NetworkManager or systemd-networkd handles DHCPv6 automatically when the RA M flag is set. Manual client invocation is mainly useful for debugging:
dhclient -6 -v eth0
Prefix Delegation (PD)
If your router receives a /48 or /56 via prefix delegation from your ISP, it subdivides it into /64 subnets for downstream interfaces. Configure this in systemd-networkd by setting DHCPPrefixDelegation=yes on the WAN interface and referencing the delegated prefix on LAN interfaces. This is router-specific configuration beyond a single host.
Static IPv6 with systemd-networkd
For servers, static assignment is more predictable. Create a network file:
cat > /etc/systemd/network/20-eth0.network << 'EOF'
[Match]
Name=eth0
[Network]
Address=2001:db8:cafe:1::10/64
Gateway=2001:db8:cafe:1::1
DNS=2606:4700:4700::1111
DNS=2001:4860:4860::8888
IPv6AcceptRA=no
EOF
systemctl enable --now systemd-networkd
systemctl restart systemd-networkd
Set IPv6AcceptRA=no on servers to avoid the static address being overwritten or supplemented by unwanted SLAAC addresses. Verify with ip -6 addr show dev eth0 and ping6 2606:4700:4700::1111.
Routing
Add a static IPv6 route:
ip -6 route add 2001:db8:dead::/48 via 2001:db8:cafe:1::1 dev eth0
Make it persistent by adding a [Route] section to the .network file in systemd-networkd, or via NetworkManager:
nmcli connection modify eth0 +ipv6.routes "2001:db8:dead::/48 2001:db8:cafe:1::1"
To turn a Linux host into a router, enable forwarding — this is the IPv6 equivalent of ip_forward:
echo 'net.ipv6.conf.all.forwarding = 1' > /etc/sysctl.d/99-ipv6-forward.conf
sysctl --system
Warning: enabling forwarding suppresses SLAAC on the host itself unless you also configure radvd or a router daemon to send RAs on downstream interfaces.
Firewall Configuration
IPv6 requires explicit firewall rules. Do not assume NAT hides you — with IPv6, hosts are often globally reachable by design.
nftables (Recommended)
A minimal dual-stack ruleset accepting established traffic and ICMPv6 (which is mandatory for IPv6 to function — do not block it entirely):
cat > /etc/nftables.conf << 'EOF'
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif lo accept
ip6 nexthdr icmpv6 icmpv6 type {
nd-neighbor-solicit, nd-neighbor-advert,
nd-router-advert, nd-router-solicit,
echo-request, destination-unreachable,
packet-too-big, time-exceeded, parameter-problem
} accept
ip protocol icmp accept
tcp dport 22 accept
# add your services here
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
EOF
systemctl enable --now nftables
firewalld (Fedora/RHEL/Rocky)
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=dhcpv6-client
firewall-cmd --reload
firewalld handles ICMPv6 neighbor discovery automatically in its default zones.
ufw (Debian/Ubuntu)
Confirm IPV6=yes is set in /etc/default/ufw, then:
ufw allow ssh
ufw enable
ufw writes parallel ip6tables rules when IPv6 is enabled in its configuration.
Dual-Stack Operation
Dual-stack means the host has both a routable IPv4 and IPv6 address and uses whichever is appropriate per connection. Modern Linux prefers IPv6 for outbound connections via the Happy Eyeballs algorithm (RFC 8305) implemented in applications. Verify dual-stack connectivity:
curl -4 https://ifconfig.me && echo
curl -6 https://ifconfig.me && echo
Both should return your respective public addresses. If -6 fails but -4 works, check your default IPv6 route and that the firewall is not blocking outbound traffic.
On dual-stack systems, services listening on :: (IPv6 any) also accept IPv4 connections via IPv4-mapped addresses (::ffff:192.0.2.1), unless the kernel parameter net.ipv6.bindv6only is set to 1. The default on Linux is 0 (dual-bind). Check per-application behavior with:
ss -tlnp6
Verification and Testing
# Ping the global IPv6 DNS resolver
ping6 -c4 2606:4700:4700::1111
# Trace the IPv6 path
traceroute6 2606:4700:4700::1111
# Check neighbor cache (replaces ARP for IPv6)
ip -6 neigh show
# Confirm DNS resolves AAAA records
dig AAAA ipv6.google.com
Troubleshooting
- No global address despite SLAAC — run
tcpdump -i eth0 icmp6and watch for Router Advertisements. If none arrive, the upstream router has IPv6 disabled or RA suppressed. - Ping6 works, TCP does not — almost always an MTU/Path-MTU issue. IPv6 requires ICMP type
packet-too-bigto pass. Check your firewall is not dropping it. Test with:ping6 -s 1400 2606:4700:4700::1111. - IPv6 DNS resolution fails — check
/etc/resolv.conforresolvectl statusfor a configured IPv6-capable nameserver.systemd-resolveduses the interface's DNS servers; ensure DHCPv6 or static config supplies one. - Address appears then disappears — privacy extension temporary addresses expire (default 1 day preferred, 7 days valid). This is normal; a new one is generated automatically. If all addresses vanish, check
journalctl -u systemd-networkd. - Duplicate Address Detection (DAD) failure — the kernel marks an address
dadfailedif another host claims the same address via neighbor solicitation. Runip -6 addr showand look for the flag. This can indicate a misconfigured static address or a subnet conflict.
Frequently asked questions
- Can I run IPv6 without a globally routable prefix — for example, just on a LAN?
- Yes. Unique-local addresses (fc00::/7, typically fd00::/8 in practice) work exactly like IPv4 private ranges. Every interface also gets a link-local address automatically, which is sufficient for neighbour discovery and local routing without any configuration.
- Should I disable IPv6 if I am not using it?
- Generally no. Disabling it can break applications that expect it and creates maintenance burden when you eventually need it. If your threat model requires it, block external IPv6 at the firewall instead of disabling the stack.
- Why does blocking ICMPv6 break IPv6 but blocking ICMP does not usually break IPv4?
- IPv6 uses ICMPv6 for critical functions: Neighbor Discovery (replacing ARP), Path MTU Discovery (required since IPv6 routers do not fragment), and router advertisement. IPv4 can fall back to ARP and has router-side fragmentation, making ICMP less critical to basic operation.
- My server has a public IPv6 address — does that mean it is exposed to the internet?
- Only if your firewall permits inbound connections. Unlike IPv4 NAT which incidentally hides hosts, IPv6 assigns routable addresses directly. This is why explicit firewall rules with a default-drop input policy are essential on dual-stack servers.
- What is the difference between SLAAC and DHCPv6, and can I use both?
- SLAAC lets hosts self-configure from router advertisement prefixes with no server required. DHCPv6 assigns addresses and options from a central server, enabling tighter control and logging. You can run both simultaneously — the RA flags (M and O bits) signal to clients which mechanisms are active.
Related guides
Build a Mesh VPN with Nebula
Build a fully self-hosted mesh VPN with Nebula: create a CA, sign node certs, configure lighthouses, enforce group-based firewall rules, and run as a systemd service.
Common Linux Network Ports Reference
Learn Linux port ranges, read /etc/services, find what's listening with ss and nmap, and apply solid firewall rules to expose or block the right ports.
How to Configure a Static IP on Linux
Configure a static IP on Linux using Netplan, NetworkManager (nmcli), or systemd-networkd across Ubuntu, Fedora, Debian, and Arch with verified steps.
Expose a Service with Cloudflare Tunnel
Expose local services to the internet without port-forwarding using Cloudflare Tunnel. Install cloudflared, create a named tunnel, configure ingress rules, and run as a systemd service.