A few months ago I set up my own selfhosted WireGuard VPN to be able to access my LAN and use my ad-blocking PiHole from anywhere. The basic IPv4 setup was very straightforward as I used the Proxmox Community Script, which, very conveniently, also installed WGDashboard for me. Thanks to that I haven’t had any problems with WireGuard and it has proved to be a priceless tool time and time again.
There was just one catch, it was only set up for IPv4 traffic.
The problem with no IPv6
You might think: "What’s the problem with only having IPv4 in your VPN? It’s not like you alone will run out of
addresses in your /16 subnet". And that’s right, however, having IPv6 addresses in a VPN is not about capacity. It’s
mainly about IPv6 traffic. Without IPv6 I couldn’t access anything over IPv6 while connected to my VPN.
The solution
I started thinking how I’d add IPv6 into my VPN. The approach I used with my home LAN wouldn’t work, since my WireGuard server is not a router and is not connected directly to the internet. That means that it can’t give out global IPV6 addresses. So I turned to ULAs.
ULA is a local type of IPv6 address, but unlike a LL address it lives in the
fd00::/8 subnet and is supposed to be globally unique (this is not enforced or guaranteed, just statistically very
likely). Each network using ULAs needs to generate its own fdxx:xxxx:xxxx::/48 prefix by using 40 bits of the hash of
a MAC address and the current time (or just a random value). That prefix then leaves 16 more bits for use as routable
blocks, which result in fdxx:xxxx:xxxx:yyyy::/64 subnets, leaving the other 64 bits to the host.
There was one more problem, since ULAs are not routable, I couldn’t just generate them, add them into the WireGuard configs and call it a day. I had to set up NAT on my WireGuard server to be able to access the IPv6 internet (analogous to the standard IPv4 NAT on any home LAN). Fortunately that was easy enough and now I have a working IPv6-enabled VPN.
How did I actually do that
First I needed to get my own ULA prefix. I used this generator and for the MAC address I
used the one of the WireGuard server’s virtual interface, but that doesn’t matter that much. Then I decided to use the
fdxx:xxxx:xxxx::/64 subnet (block 0000) by default since I wouldn’t need more blocks on my VPN anyway. The next
step was to actually get some IPv6 addresses. Since I hadn’t set up RA
or DHCPv6, I had to choose the peers' addresses from the subnet manually.
Once I had all the addresses, it was finally time to play with WireGuard. First I added an IPv6 address with the /64
mask to the server. Then I did the same for all the other peers, except of course different addresses and the /128
mask. After reloading the new configs into my client devices I could already use IPv6 within my VPN.
Yeah, I also needed to set up NAT to be able to communicate with the IPv6 internet. That was surprisingly easy too,
just a few ip6tables calls away. And conveniently enough, WireGuard has all the necessary tools for calling commands
on VPN startup and shutdown, so I didn’t even have to write my own scripts. All I had to do was add one command to the
PostUp section and one to PostDown.
The new PostUp command (not dissimilar to the preexisting iptables calls for the IPv4 NAT) creates a NAT rule for
the outer network interface and the new PostDown command just deletes that exact rule to clean up. If your network
interface is called eth0, the commands would look like this:
PostUp = ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;
PostDown = ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE;And that’s it! Just for completeness, here’s the whole server config with the new parts highlighted:
[Interface]
Address = 10.x.y.z/16
Address = fdxx:xxxx:xxxx:0000:zzzz:zzzz:zzzz:zzzz/64
SaveConfig = true
PreUp =
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;
PostUp = ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;
PreDown =
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE;
PostDown = ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE;
ListenPort = 51820
PrivateKey = [REDACTED]
[Peer]
PublicKey = [REDACTED]
AllowedIPs = 10.x.y.z/32, fdxx:xxxx:xxxx:0000:zzzz:zzzz:zzzz:zzzz/128
Endpoint = [REDACTED]
[Peer]
PublicKey = [REDACTED]
AllowedIPs = 10.x.y.z/32, fdxx:xxxx:xxxx:0000:zzzz:zzzz:zzzz:zzzz/128
Endpoint = [REDACTED]and config of one of the peers:
[Interface]
PrivateKey = [REDACTED]
Address = 10.x.y.z/32, fdxx:xxxx:xxxx:0000:zzzz:zzzz:zzzz:zzzz/128
MTU = 1420
[Peer]
PublicKey = [REDACTED]
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = [REDACTED]:51820
PersistentKeepalive = 21That’s all for today, thanks for reading, don’t forget to subscribe and if this blog post gets 10 likes, I will post a part 2 about RA and SLAAC.
fortunately there’s no like system here