LUNAROPS · OPERATIONAL UPLINK 100% UPTIME 1,247d POSTS 893 JEFF.MOON@LUNAROPS.DEV UTC --:--:--

IPv6 Practical Guide: Addressing, Dual-Stack, and Running IPv6 in Your Homelab

networkingipv6dual-stackhomelablinux

IPv4 exhaustion isn’t a future problem — it’s been the present reality for over a decade. IPv6 adoption crossed 40% of global internet traffic in 2024, and major cloud providers, CDNs, and mobile networks are largely IPv6-first. Yet many engineers still treat IPv6 as someone else’s concern. This guide is about making it yours — understanding the addressing model, configuring dual-stack, and running IPv6 confidently from your homelab to production.

Why IPv6 Is Different (Not Just Bigger Addresses)

IPv6 isn’t IPv4 with longer addresses. The protocol was redesigned from scratch:

  • No broadcast: IPv6 uses multicast instead. No more broadcast storms.
  • No ARP: Replaced by Neighbor Discovery Protocol (NDP), which runs over ICMPv6.
  • Stateless autoconfiguration (SLAAC): Hosts can configure themselves without a DHCP server.
  • IPsec in the design: Originally mandatory (now optional), but designed in.
  • Simplified header: Fixed 40-byte header (no fragmentation fields, no checksum). Extension headers chain for optional features.
  • Mandatory ICMPv6: Never filter all ICMPv6 — it’s required for basic connectivity.
  • Jumbo frames: Extension header supports payloads up to 4GB.

Address Structure

An IPv6 address is 128 bits, written as eight groups of four hex digits:

2001:0db8:85a3:0000:0000:8a2e:0370:7334

Abbreviation Rules

  1. Leading zeros in each group can be omitted: 0db8db8, 00000
  2. One sequence of consecutive all-zero groups can be replaced with :::
2001:db8:85a3:0:0:8a2e:370:7334
2001:db8:85a3::8a2e:370:7334   # :: replaces the two zero groups

You can only use :: once per address.

Address Anatomy

A typical unicast address has two parts:

| Network Prefix (64 bits)  | Interface ID (64 bits)   |
| 2001:db8:1234:5678        | ::1a2b:3c4d:5e6f:7a8b    |

The network prefix is further divided:

| Global Routing Prefix (48 bits) | Subnet ID (16 bits) | Interface ID (64 bits) |
| 2001:db8:1234                   | :5678               | ::1a2b:3c4d:5e6f:7a8b  |

Your ISP assigns you a /48 or /56 prefix. You subnet within that. Each subnet is a /64. The interface ID is the last 64 bits — typically derived from the MAC address (EUI-64) or generated randomly for privacy.

Address Types

Global Unicast (GUA)

Routable on the public internet. Currently allocated from 2000::/3:

2001:db8::/32     # Documentation and examples (never route these)
2001::/32         # Teredo (legacy tunnel mechanism)
2002::/16         # 6to4 (legacy, deprecated)

Your real GUAs look like 2001:1234:abcd::/48 — whatever your ISP or RIR assigns.

Every IPv6 interface automatically gets a link-local address. Not routable beyond a single link. Used for:

  • NDP (neighbor discovery, router discovery)
  • DHCPv6 communication
  • Routing protocol hello messages (OSPFv3, BGP unnumbered)
1
2
3
ip -6 addr show eth0
# 2: eth0: ...
#     inet6 fe80::250:56ff:fe80:1234/64 scope link

You’ll always see these — they’re mandatory and assigned automatically.

Unique Local Addresses (ULA — fc00::/7)

The IPv6 equivalent of RFC 1918 private space. Use fd00::/8 for your own networks (the fc00::/8 half requires a central authority):

fd12:3456:789a::/48   # Your private addressing — generate randomly

ULA addresses are not routed on the public internet. Unlike RFC 1918, ULA is designed so randomly-generated prefixes from different organizations don’t collide.

Generate a random ULA prefix:

1
2
3
4
5
6
7
python3 -c "
import random, struct
rand = random.getrandbits(40)
b = struct.pack('>Q', rand)[-5:]
print('fd{:02x}:{:02x}{:02x}:{:02x}{:02x}::/48'.format(*b))
"
# Example output: fd7a:4c91:b3a2::/48

Multicast (ff00::/8)

Used where IPv4 used broadcast. Key multicast groups:

Address Scope Purpose
ff02::1 link-local All nodes
ff02::2 link-local All routers
ff02::fb link-local mDNS
ff02::1:2 link-local All DHCPv6 servers/relays
ff02::1:ff00:0/104 link-local Solicited-node multicast (NDP)

Special Addresses

Address Meaning
::1 Loopback (equivalent to 127.0.0.1)
:: Unspecified address
::ffff:0:0/96 IPv4-mapped IPv6 addresses
64:ff9b::/96 NAT64 well-known prefix

Neighbor Discovery Protocol (NDP)

NDP replaces ARP and adds router discovery. It uses ICMPv6 messages:

Message Type ICMPv6 Code Purpose
Router Solicitation (RS) 133 Host asks: “Are there routers here?”
Router Advertisement (RA) 134 Router announces prefix, gateway, flags
Neighbor Solicitation (NS) 135 “Who has this address?” (replaces ARP request)
Neighbor Advertisement (NA) 136 “I have this address” (replaces ARP reply)
Redirect 137 Router tells host of a better next-hop

Solicited-Node Multicast

Instead of broadcasting to find a MAC address, NDP uses solicited-node multicast. For address 2001:db8::1a2b:3c4d:5e6f:7a8b, the solicited-node address is:

ff02::1:ff6f:7a8b   # ff02::1:ff + last 24 bits of address

Only the node with that address listens on this multicast group — dramatically reducing the “broadcast” scope even on large subnets.

1
2
3
4
5
# View NDP cache (equivalent of ARP table)
ip -6 neigh show

# Flush NDP cache
ip -6 neigh flush dev eth0

Address Configuration Methods

SLAAC (Stateless Address Autoconfiguration)

The default method. When an interface comes up:

  1. Host generates a link-local address from its MAC (or randomly)

  2. Host performs Duplicate Address Detection (DAD) via NS/NA

  3. Host sends a Router Solicitation

  4. Router responds with a Router Advertisement containing:

    • The /64 prefix for this link
    • Default gateway (the router’s link-local address)
    • Flags for DHCPv6
    • Lifetime timers
  5. Host combines the prefix with a self-generated interface ID → global unicast address

  6. DAD again on the new GUA

The interface ID generation method:

  • EUI-64: derived from MAC address (privacy concern — MAC is embedded in address)
  • Privacy extensions (RFC 4941): random temporary addresses, rotated periodically (default on most modern OSes)
  • Stable privacy addresses (RFC 7217): stable but not guessable from MAC
1
2
3
4
5
6
# Check SLAAC-assigned addresses
ip -6 addr show eth0

# On Linux, privacy extensions are controlled by:
sysctl net.ipv6.conf.eth0.use_tempaddr
# 0 = disabled, 1 = enabled (prefer stable), 2 = enabled (prefer temp)

DHCPv6

Used when you need centralized address management or need to hand out options SLAAC can’t provide (NTP servers, custom options). Two modes:

Stateful DHCPv6: assigns specific addresses (like DHCPv4) Stateless DHCPv6: SLAAC handles the address, DHCPv6 provides options only

The RA’s M flag (Managed) tells hosts to use stateful DHCPv6. The O flag (Other) tells hosts to get options via DHCPv6 but use SLAAC for the address.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# ISC KEA DHCPv6 config snippet
{
  "Dhcp6": {
    "interfaces-config": {
      "interfaces": ["eth0"]
    },
    "subnet6": [{
      "subnet": "2001:db8:1::/64",
      "pools": [{"pool": "2001:db8:1::100-2001:db8:1::200"}],
      "option-data": [
        {"name": "dns-servers", "data": "2001:4860:4860::8888, 2001:4860:4860::8844"}
      ]
    }]
  }
}

Static Configuration

For servers, routers, and infrastructure — always use static addresses:

1
2
3
4
5
6
7
8
9
# /etc/network/interfaces (Debian/Ubuntu)
iface eth0 inet6 static
    address 2001:db8:1::10/64
    gateway fe80::1    # Link-local gateway from RA
    dns-nameservers 2001:4860:4860::8888

# Or with ip command
ip -6 addr add 2001:db8:1::10/64 dev eth0
ip -6 route add default via fe80::1 dev eth0

With systemd-networkd:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# /etc/systemd/network/20-eth0.network
[Match]
Name=eth0

[Network]
IPv6AcceptRA=no
Address=2001:db8:1::10/64

[Route]
Gateway=fe80::1

Dual-Stack Configuration

Dual-stack means running IPv4 and IPv6 simultaneously. The goal: both protocols work independently, hosts can communicate over either.

Linux Host (systemd-networkd)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# /etc/systemd/network/20-eth0.network
[Match]
Name=eth0

[Network]
DHCP=ipv4
IPv6AcceptRA=yes

[DHCP]
UseDNS=yes
UseRoutes=yes

Nginx Dual-Stack

1
2
3
4
5
6
7
8
9
server {
    listen 80;
    listen [::]:80;        # IPv6
    listen 443 ssl;
    listen [::]:443 ssl;   # IPv6

    server_name example.com;
    # ...
}

Docker Dual-Stack

By default Docker only uses IPv4 internally. Enable IPv6:

1
2
3
4
5
6
7
// /etc/docker/daemon.json
{
  "ipv6": true,
  "fixed-cidr-v6": "fd00::/80",
  "experimental": true,
  "ip6tables": true
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
sudo systemctl restart docker

# Create a dual-stack network
docker network create \
  --ipv6 \
  --subnet 172.28.0.0/16 \
  --subnet fd00:1::/64 \
  dual-stack-net

# Run a container on it
docker run -d \
  --network dual-stack-net \
  --name web nginx

# Verify
docker inspect web | grep -A 10 GlobalIPv6

Docker Compose Dual-Stack

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# docker-compose.yml
networks:
  app-net:
    enable_ipv6: true
    ipam:
      config:
        - subnet: 172.28.0.0/16
        - subnet: fd00:1::/64

services:
  web:
    image: nginx
    networks:
      - app-net
    ports:
      - "80:80"
      - "443:443"

Kubernetes Dual-Stack

Enable in kubeadm:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
networking:
  podSubnet: "10.244.0.0/16,fd00:10:244::/56"
  serviceSubnet: "10.96.0.0/12,fd00:10:96::/112"
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: "192.168.1.10"
nodeRegistration:
  kubeletExtraArgs:
    node-ip: "192.168.1.10,fd00:192:168:1::10"

Services get both addresses:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  ipFamilyPolicy: PreferDualStack
  ipFamilies:
    - IPv4
    - IPv6
  selector:
    app: my-app
  ports:
    - port: 80

Prefix Delegation (DHCPv6-PD)

If your ISP supports DHCPv6-PD (most do now), they’ll hand your router a /48 or /56 prefix, and your router subdivides it into /64 subnets for each LAN segment. This gives every device in your home or office a real, globally routable address.

With systemd-networkd as the WAN client

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# /etc/systemd/network/10-wan.network
[Match]
Name=eth0          # WAN interface

[Network]
DHCP=yes           # DHCPv4 for IPv4
IPv6AcceptRA=yes   # Accept RAs from ISP

[DHCPv6]
PrefixDelegationHint=::/56   # Request a /56 delegation
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# /etc/systemd/network/20-lan.network
[Match]
Name=eth1          # LAN interface

[Network]
IPv6SendRA=yes
DHCPv6PrefixDelegation=yes

[IPv6SendRA]
Managed=no
OtherInformation=yes

[DHCPv6PrefixDelegation]
SubnetId=0x1       # Use the first /64 from the delegated prefix

With dhcpcd (common on Raspberry Pi / Arch)

# /etc/dhcpcd.conf
interface eth0            # WAN
ipv6rs                    # Enable router solicitation
ia_na 1                   # Request address
ia_pd 2 eth1/0            # Request prefix, delegate to eth1 subnet 0

With radvd for RA on LAN

# /etc/radvd.conf
interface eth1 {
    AdvSendAdvert on;
    AdvManagedFlag off;
    AdvOtherConfigFlag on;     # Use DHCPv6 for options

    prefix ::/64 {             # ::/64 = use delegated prefix
        AdvOnLink on;
        AdvAutonomous on;
        AdvRouterAddr on;
    };

    RDNSS 2001:4860:4860::8888 2001:4860:4860::8844 {};
};

IPv6 Firewalling

Critical difference from IPv4: NAT is not a security mechanism in IPv6. Every device gets a routable address. You must run a proper stateful firewall.

Also critical: never block all ICMPv6. This breaks connectivity. Specific ICMPv6 types are required.

#!/usr/sbin/nft -f

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;

        # Allow established/related connections
        ct state established,related accept

        # Allow loopback
        iif lo accept

        # ICMPv6 — required for IPv6 to function
        ip6 nexthdr icmpv6 icmpv6 type {
            destination-unreachable,
            packet-too-big,
            time-exceeded,
            parameter-problem,
            nd-router-solicit,
            nd-router-advert,
            nd-neighbor-solicit,
            nd-neighbor-advert,
            mld-listener-query,
            mld-listener-report
        } accept

        # Allow ping
        ip6 nexthdr icmpv6 icmpv6 type echo-request accept

        # Allow SSH
        tcp dport 22 ct state new accept

        # Allow HTTP/HTTPS
        tcp dport { 80, 443 } ct state new accept

        # Drop everything else
        drop
    }

    chain forward {
        type filter hook forward priority 0; policy drop;

        ct state established,related accept

        # ICMPv6 forwarding
        ip6 nexthdr icmpv6 accept

        # Allow LAN → WAN
        iif eth1 oif eth0 accept

        drop
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}

ip6tables (legacy but still common)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Flush existing rules
ip6tables -F
ip6tables -X

# Default policies
ip6tables -P INPUT DROP
ip6tables -P FORWARD DROP
ip6tables -P OUTPUT ACCEPT

# Loopback
ip6tables -A INPUT -i lo -j ACCEPT

# Established/related
ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# ICMPv6 — required types
ip6tables -A INPUT -p ipv6-icmp --icmpv6-type destination-unreachable -j ACCEPT
ip6tables -A INPUT -p ipv6-icmp --icmpv6-type packet-too-big -j ACCEPT
ip6tables -A INPUT -p ipv6-icmp --icmpv6-type time-exceeded -j ACCEPT
ip6tables -A INPUT -p ipv6-icmp --icmpv6-type parameter-problem -j ACCEPT
ip6tables -A INPUT -p ipv6-icmp --icmpv6-type echo-request -j ACCEPT
ip6tables -A INPUT -p ipv6-icmp --icmpv6-type router-advertisement -j ACCEPT
ip6tables -A INPUT -p ipv6-icmp --icmpv6-type neighbor-solicitation -j ACCEPT
ip6tables -A INPUT -p ipv6-icmp --icmpv6-type neighbor-advertisement -j ACCEPT

# Services
ip6tables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
ip6tables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
ip6tables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT

# Save rules
ip6tables-save > /etc/iptables/rules.v6

UFW with IPv6

Ensure IPv6 is enabled in UFW config:

# /etc/default/ufw
IPV6=yes
1
2
3
4
5
6
7
sudo ufw enable
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https

# UFW automatically creates both IPv4 and IPv6 rules
sudo ufw status verbose

Running IPv6 in a Homelab

If your ISP doesn’t provide IPv6 (or if you want isolated testing), several tunneling options exist.

Hurricane Electric Tunnel Broker (Free)

tunnelbroker.net gives you a free /48 and a 6in4 tunnel:

1
2
3
4
5
6
# After creating a tunnel at tunnelbroker.net, configure it on Linux
sudo ip tunnel add he-ipv6 mode sit remote <HE_SERVER_IPv4> local <YOUR_IPv4> ttl 255
sudo ip link set he-ipv6 up
sudo ip addr add <YOUR_IPv6_CLIENT>/64 dev he-ipv6
sudo ip route add ::/0 dev he-ipv6
sudo ip -6 route show

With systemd-networkd:

1
2
3
4
5
6
7
8
9
# /etc/systemd/network/10-he-tunnel.netdev
[NetDev]
Name=he-ipv6
Kind=sit

[Tunnel]
Local=<YOUR_IPv4>
Remote=<HE_SERVER_IPv4>
TTL=255
1
2
3
4
5
6
7
# /etc/systemd/network/10-he-tunnel.network
[Match]
Name=he-ipv6

[Network]
Address=<YOUR_IPv6_CLIENT>/64
Gateway=<HE_SERVER_IPv6>

WireGuard with IPv6

WireGuard handles IPv6 natively. Add an IPv6 address to the tunnel interface alongside IPv4:

1
2
3
4
5
6
7
8
9
# /etc/wireguard/wg0.conf — server
[Interface]
PrivateKey = <SERVER_PRIVATE_KEY>
Address = 10.0.0.1/24, fd00:wg::1/64
ListenPort = 51820

[Peer]
PublicKey = <CLIENT_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32, fd00:wg::2/128
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# /etc/wireguard/wg0.conf — client
[Interface]
PrivateKey = <CLIENT_PRIVATE_KEY>
Address = 10.0.0.2/24, fd00:wg::2/64
DNS = 2606:4700:4700::1111

[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = <SERVER_IP>:51820
AllowedIPs = 0.0.0.0/0, ::/0   # Route all IPv4 and IPv6 through tunnel

Testing Connectivity

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Basic ping
ping6 ::1                          # loopback
ping6 ff02::1%eth0                 # all nodes on link (note: %interface)
ping6 2606:4700:4700::1111         # Cloudflare DNS

# Traceroute
traceroute6 2606:4700:4700::1111
mtr -6 google.com

# DNS resolution over IPv6
dig AAAA google.com
dig AAAA google.com @2606:4700:4700::1111

# Check what address you're using to reach the internet
curl -6 https://ifconfig.co

# Test dual-stack
curl -4 https://ifconfig.co   # Force IPv4
curl -6 https://ifconfig.co   # Force IPv6

# NDP table
ip -6 neigh show

# Routes
ip -6 route show
ip -6 route get 2606:4700:4700::1111    # Which route would be used?

# Ports listening on IPv6
ss -tlnp6

DNS for IPv6

AAAA Records

IPv6 addresses go in AAAA (quad-A) records:

example.com.    300  IN  AAAA  2001:db8::1

PTR Records (Reverse DNS)

IPv6 reverse DNS is in ip6.arpa. The address is written in reverse nibble format:

For 2001:db8::1:

1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.

In BIND/NSD zone files:

$ORIGIN 0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.
1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0  PTR  host.example.com.

Split-Horizon DNS for Dual-Stack

When you have both A and AAAA records, clients will prefer IPv6 (RFC 6724 default behavior). If your IPv6 connectivity is broken, applications may time out waiting for IPv6 before falling back to IPv4. This is “happy eyeballs” territory — modern browsers and curl implement RFC 6555 to race both connections.

If you’re troubleshooting and want to force IPv4:

1
2
curl -4 https://example.com
ssh -4 user@host

Common Pitfalls

“My host has an IPv6 address but can’t reach the internet”

Check the default route:

1
2
3
4
ip -6 route show default
# If missing, check that RA is being accepted:
sysctl net.ipv6.conf.eth0.accept_ra
# Should be 1 (or 2 if forwarding is enabled)

Forwarding breaks RA acceptance

By default, Linux ignores RAs on interfaces with forwarding enabled:

1
2
3
4
5
6
# Check
sysctl net.ipv6.conf.eth0.forwarding
sysctl net.ipv6.conf.eth0.accept_ra

# If you need both forwarding AND RA acceptance (router getting prefix from ISP)
sysctl -w net.ipv6.conf.eth0.accept_ra=2

“I enabled IPv6 in Docker but containers can’t reach IPv6”

Verify ip6tables is installed and that the Docker daemon restarted:

1
2
docker network inspect bridge | grep -i ipv6
# EnableIPv6 should be true

Also check the host’s forwarding:

1
2
sysctl net.ipv6.conf.all.forwarding
# Should be 1

When pinging or connecting to link-local addresses, you must specify the interface:

1
2
ping6 fe80::1%eth0    # Correct
ping6 fe80::1         # Will fail — ambiguous which link

In URLs: http://[fe80::1%25eth0]/ (the %25 is URL-encoded %)

Applications not binding to IPv6

Check if the application is configured to listen on both addresses:

1
2
3
4
5
6
7
8
# Binds to IPv4 only:
0.0.0.0:80

# Binds to IPv4 only (common on some distros):
ss -tlnp | grep :80

# Should see:
*:80   or   :::80   (IPv6 wildcard, which may cover IPv4 too depending on IPV6_V6ONLY)

For Python servers:

1
2
3
4
5
# IPv4 only
server = HTTPServer(('0.0.0.0', 8080), handler)

# IPv6 (and IPv4 via dual-stack socket on Linux)
server = HTTPServer(('::', 8080), handler)

Quick Reference

Address Types Cheat Sheet

Prefix Type Use
::1/128 Loopback localhost
fe80::/10 Link-local On-link only, auto-assigned
fc00::/7 (fd::/8) ULA Private, not routed
2000::/3 GUA Public internet
ff00::/8 Multicast Groups, not unicast
::ffff:0:0/96 IPv4-mapped IPv4 in IPv6 socket

Useful Commands Cheat Sheet

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Show all IPv6 addresses
ip -6 addr show

# Show IPv6 routes
ip -6 route show

# Show NDP cache (like ARP table)
ip -6 neigh show

# Ping IPv6
ping6 <addr>
ping6 <link-local>%<interface>

# Traceroute
traceroute6 <addr>

# DNS lookup
dig AAAA <hostname>

# Listen on both IPv4 and IPv6
ss -tlnp

# Test your IPv6 address
curl -6 https://ifconfig.co

# Enable IPv6 forwarding (router)
sysctl -w net.ipv6.conf.all.forwarding=1
echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.d/99-ipv6.conf

# Accept RA even with forwarding (router getting prefix from ISP)
sysctl -w net.ipv6.conf.eth0.accept_ra=2

sysctl Reference

Key Value Effect
net.ipv6.conf.all.forwarding 1 Enable IPv6 routing
net.ipv6.conf.<if>.accept_ra 2 Accept RA even when forwarding
net.ipv6.conf.<if>.use_tempaddr 2 Use privacy extensions
net.ipv6.conf.<if>.autoconf 1 Enable SLAAC
net.ipv6.conf.all.disable_ipv6 1 Disable IPv6 entirely

Where to Go Next

  • Test your setup: test-ipv6.com tells you exactly which IPv6 features work
  • IPv6 subnetting calculator: subnetcalc.it
  • RIPE NCC IPv6 training: excellent free course covering the theory in depth
  • RFC 8200: the current IPv6 specification
  • RFC 4861: Neighbor Discovery Protocol
  • RFC 4862: Stateless Address Autoconfiguration
  • RFC 4941: Privacy Extensions (temporary addresses)
  • RFC 7217: Stable, Opaque Interface Identifiers

IPv6 has a reputation for complexity, but most of that complexity lives in the transition period we’re still in. A network that runs native IPv6 end-to-end, with proper firewall rules and prefix delegation, is actually simpler to operate than the NAT gymnastics required for IPv4. The sooner you build the muscle memory, the better positioned you’ll be.

Comments