How to Set Up IPv6 on a Home Server (Addresses, Firewall, Docker and the Reverse Proxy)

IPv6 on the home server: every device gets a public address — so the firewall matters more, not less. Here's the safe setup.

Most UK and EU ISPs now hand out IPv6 whether you asked or not. It changes one big thing: every device gets a globally routable address, so "behind NAT" stops being a defence and the firewall is the only wall. Done right, IPv6 gives you direct connections with no port forwarding (and an escape from CGNAT); done carelessly, it exposes services you thought were private. Here's how to tell what you have, set the server up properly, and when to just switch it off.

Quick reference

TaskCommand
Do I have it?ip -6 a — a 2xxx: address on the LAN interface means yes
Test from the servercurl -6 https://ifconfig.co
FirewallUFW handles v6 if IPV6=yes in /etc/default/ufw (default)
Stable addressNetplan ipv6-address-generation: stable-privacy or a static suffix
Routerenable the IPv6 firewall (block inbound by default)
Docker"ipv6": true + a ULA subnet in daemon.json
Turn it offnet.ipv6.conf.all.disable_ipv6=1 in sysctl

1. Do you have it?

ip -6 a show enp3s0

curl -6 https://ifconfig.co

Your public IPv6. Note the prefix — the first four groups (2a00:1234:5678:abcd::/64) — is your home's; the rest is the device.

2. The mental model

3. A stable address for the server

In Netplan:

enp3s0:
dhcp4: true
dhcp6: false
accept-ra: true
ipv6-address-generation: stable-privacy
ipv6-privacy: false

stable-privacy gives the server the same suffix every boot, derived from a secret, without exposing the MAC address. If your prefix is stable too, the full address is stable; put it in DNS as an AAAA record alongside the A record. If the prefix rotates, dynamic DNS (tutorial 48) handles AAAA updates the same way (DuckDNS: add &ipv6= to the update URL).

sudo netplan try, then ip -6 a.

4. Firewall — this is the important part

UFW writes both v4 and v6 rules when IPV6=yes in /etc/default/ufw (the default). sudo ufw status verbose shows (v6) entries. Every ufw allow you've made applies to both, so the server's posture is the same on IPv6 as IPv4: deny in, allow SSH/80/443/WireGuard.

Now the rest of the house: the router's IPv6 firewall must default to deny inbound, or every camera and plug is on the internet. Look for "IPv6 firewall", "IPv6 security" or "Block inbound IPv6" and make sure it's on. To expose only the server's web ports, add a pinhole rule on the router for its address, ports 80/443 — the IPv6 equivalent of a port forward.

5. Nginx, Caddy and Docker

Nginx: add listen [::]:80; and listen [::]:443 ssl; beside the IPv4 listen lines (Caddy listens on both by itself). Certbot's challenge works over v6 once the AAAA record and pinhole exist.

Docker doesn't give containers IPv6 by default. If you want it (so the reverse proxy sees real v6 client addresses, say):

sudo nano /etc/docker/daemon.json

{
"ipv6": true,
"fixed-cidr-v6": "fd00:dead:beef::/48",
"experimental": true,
"ip6tables": true
}

sudo systemctl restart docker. Containers get ULA addresses and Docker NATs v6 like v4 (ip6tables: true). For most home setups, leaving Docker on v4 internally and letting Nginx on the host take v6 is simpler.

6. Pi-hole and DNS

Clients with IPv6 may use the router's advertised v6 DNS and bypass Pi-hole. In Pi-hole's DHCP settings enable IPv6 (RA) so it advertises itself, or on the router set the IPv6 DNS to the server's address. Pi-hole handles AAAA queries and blocklists over v6 the same way.

7. Or turn it off

Legitimate choice if you don't need it and would rather not audit every device: on the router, disable IPv6; and on the server:

echo "net.ipv6.conf.all.disable_ipv6 = 1" | sudo tee /etc/sysctl.d/99-no-ipv6.conf
sudo sysctl --system

But if you're behind CGNAT (tutorial 47), IPv6 may be your only path to direct inbound connections, which makes it worth the care.

Troubleshooting

curl -6 fails but the address exists. Router isn't routing v6 (ISP not delivering it), or the default route is missing: ip -6 route.

Services unreachable over v6 from outside. No AAAA record, no pinhole on the router, or Nginx not listening on [::].

A device is reachable from the internet that shouldn't be. The router's v6 firewall is off. Turn it on now.

Intermittent slowness on some sites. Broken v6 path to that site; sysctl net.ipv6.conf.all.use_tempaddr=2 for clients, or prefer v4 via /etc/gai.conf on the server as a workaround.

Wrapping up

Check you have it, give the server a stable suffix, confirm UFW covers v6 and the router blocks inbound by default, pinhole only the web ports, add AAAA records. Every device is public on IPv6, so the firewall is the whole story — set that first, enjoy the direct connections after.

Call us