Why I Need WireGuard
After obtaining my own ASN and IPv6 address space, I wanted to use those addresses from my home network. A normal residential connection cannot provide me with a BGP session, however, and my ISP, Vocus Retail, does not currently assign IPv6 addresses on this service. It does at least provide a static IPv4 address, which gives me a stable endpoint for a tunnel. The practical solution is an IPv6-over-IPv4 tunnel between my home network and a server that can announce and route my prefix. After comparing several Layer 2 and Layer 3 tunnelling protocols, I chose WireGuard. WireGuard is a small, modern VPN built around public-key cryptography. It is often used to secure traffic from laptops and phones on untrusted hotel or café networks, but the same peer-to-peer model also works well as a clean transport for routed IPv6. Compared with OpenVPN or IPsec deployments based on certificate infrastructure, WireGuard has a deliberately small configuration surface. Each peer has a private key, a public key, a set of permitted addresses, and an endpoint when one is needed. That simplicity makes the tunnel easier to understand, automate, and recover when something goes wrong.
Setting Up the Server
The following steps assume that the server already has working Internet access and that the routing preparation described in the previous article has been completed.
Install WireGuard and generate a key pair
First update the package index and install WireGuard:
sudo apt-get update
sudo apt-get install wireguard
Create the WireGuard directory with restrictive permissions, then generate the server key pair:
sudo install -m 0700 -d /etc/wireguard
umask 077
wg genkey | sudo tee /etc/wireguard/server_private_key | wg pubkey | sudo tee /etc/wireguard/server_public_key
The private key must remain on the server. The public key is the value that will later be copied into each peer configuration.
Choose the tunnel address ranges
Before writing the server configuration, the tunnel needs an internal IPv4 range and an internal IPv6 range. These addresses identify the WireGuard interfaces themselves; they are separate from the public IPv6 prefix that the server routes through the tunnel.
Choose a private IPv4 range
RFC 1918 reserves three IPv4 blocks for private networks:
10.0.0.0/8172.16.0.0/12192.168.0.0/16For this example I use10.8.0.0/24. It provides more addresses than this tunnel is likely to need and is less likely to overlap with the common192.168.0.0/24networks found in homes and hotels. The server uses10.8.0.1/24. Peers can use10.8.0.2/24,10.8.0.3/24, and so on. If10.8.0.0/24already exists anywhere the tunnel must reach, choose a different private block and record it before continuing.
Generate a private IPv6 range
For the internal IPv6 side of the tunnel, use a Unique Local Address prefix from fd00::/8. RFC 4193 describes how locally assigned prefixes can be generated with enough entropy to make accidental collisions very unlikely.
One simple input is a high-resolution timestamp:
date +%s%N
Example output:
1628101352127592197
Combine that value with a stable, machine-specific identifier:
cat /var/lib/dbus/machine-id
Example output:
20086c25853947c7aeee2ca1ea849d7d
Hash the combined values:
printf '%s' '162810135212759219720086c25853947c7aeee2ca1ea849d7d' | sha1sum
Then take the final 40 bits—the last ten hexadecimal characters—of the hash:
printf '%s' '4f267c51857d6dc93a0bca107bca2f0d86fac3bc' | cut -c 31-
Example output:
0d86fac3bc
Group those bytes beneath the fd prefix to produce a /48, then choose a /64 subnet for the WireGuard link. With the example bytes, the tunnel prefix is:
fd0d:86fa:c3bc::/64
The server can use fd0d:86fa:c3bc::1/64, with peers incrementing the final value: fd0d:86fa:c3bc::2/64, fd0d:86fa:c3bc::3/64, and so forth.
These are private tunnel addresses. My public IPv6 allocation is routed separately through the WireGuard peer and should not be confused with this ULA range.
Create the server configuration
Create /etc/wireguard/wg0.conf:
[Interface]
PrivateKey = <SERVER_PRIVATE_KEY>
Address = 10.8.0.1/24
Address = fd0d:86fa:c3bc::1/64
ListenPort = 51820
SaveConfig = true
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 -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -j ACCEPT; ip6tables -t nat -A POSTROUTING -j SNAT --to-source <YOUR_PUBLIC_IPV6_ADDRESS>
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 -D FORWARD -i wg0 -j ACCEPT; ip6tables -D FORWARD -o wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -j SNAT --to-source <YOUR_PUBLIC_IPV6_ADDRESS>
This file defines:
- The server private key.
- The internal IPv4 and IPv6 addresses assigned to the tunnel.
- The UDP port used for incoming WireGuard handshakes.
- Forwarding and NAT rules for IPv4 and IPv6 traffic.
- Cleanup commands that remove those rules when the interface stops.
The
PostUpcommands run whenwg0starts. They permit forwarding between the tunnel and the rest of the server, then apply the required source-address translation. The matchingPostDowncommands remove the same rules when the interface is stopped. Replace<SERVER_PRIVATE_KEY>with the server's actual private key and<YOUR_PUBLIC_IPV6_ADDRESS>with the public IPv6 source address appropriate for the routed prefix. Also replaceeth0if the server's public network interface has a different name. Before enabling the tunnel, review these firewall rules against the server's existing nftables or iptables policy. A production deployment should also enable kernel forwarding explicitly, restrict each peer'sAllowedIPs, and verify that the public prefix is routed to the WireGuard server correctly. To be continued…