WireGuard VPN lab: build a tunnel and see what it hides
0 / 8 steps
The goal
Build two sites, “home” and “office”, on a pretend internet inside one Linux machine, then join them with a WireGuard VPN. You will make the key pairs, bring the tunnel up, and capture the same web request twice: once inside the tunnel, where you can read it, and once on the internet side, where it is scrambled. Then you will break the tunnel with a wrong key and see how WireGuard reacts.
Everything stays inside your own machine. Both sites are network namespaces in your own Ubuntu VM or WSL; the “internet” between them is 203.0.113.0/24, a range reserved for documentation. The keys you make are throwaway lab keys and step 8 deletes them. Your real network and any VPN you already use are not touched.
Why this lab. “The VPN is connected but I can’t reach anything” is a classic ticket, and “what does a VPN actually protect?” is a classic exam question. WireGuard is small enough that you can see every moving part: two key pairs, one UDP port, and a list of which addresses belong to which peer.
A VPN is a sealed envelope inside a normal envelope. The postman sees the address. He does not get to read your diary. — Nyx
Before you start. Use the Ubuntu VM from Lab 00 or Ubuntu in WSL2 (WireGuard is built into both kernels). Install the tools with sudo apt install wireguard-tools tcpdump curl, then open a root shell with sudo -i (prompt #).
Steps
01Build two sites on a pretend internet
home is 203.0.113.10 and office is 203.0.113.20, joined by one virtual cable called net0. Think of these as each site’s public internet address.
# ip netns add home; ip netns add office # ip link add net0 netns home type veth peer name net0 netns office # ip -n home addr add 203.0.113.10/24 dev net0 # ip -n office addr add 203.0.113.20/24 dev net0 # ip -n home link set lo up; ip -n home link set net0 up # ip -n office link set lo up; ip -n office link set net0 up # ip netns exec home ping -c 1 -W 1 203.0.113.20
The ping should report 1 received, 0% packet loss. The two sites can reach each other, but anything they send is in the clear.
02Make a key pair for each site
Each side gets a private key (never leaves that machine) and a public key (shared with the other side).
# mkdir -p /tmp/wglab && cd /tmp/wglab && umask 077 # wg genkey | tee home.key | wg pubkey > home.pub # wg genkey | tee office.key | wg pubkey > office.pub # ls -l
-rw------- 1 root root 45 Oct 27 12:26 home.key -rw------- 1 root root 45 Oct 27 12:26 home.pub -rw------- 1 root root 45 Oct 27 12:26 office.key -rw------- 1 root root 45 Oct 27 12:26 office.pub
Why umask 077? It makes every new file readable only by its owner (-rw-------). A private key that other users can read is a stolen key waiting to happen. Each key is 32 bytes written as 44 characters of base64, plus a newline: that is the 45.
03Create the tunnel on both sides
Each site gets a wg0 interface. You tell it its own private key, the other side’s public key, where to find the other side, and which tunnel addresses live over there.
# ip -n home link add wg0 type wireguard # ip -n office link add wg0 type wireguard # ip netns exec home wg set wg0 private-key home.key listen-port 51820 peer $(cat office.pub) endpoint 203.0.113.20:51820 allowed-ips 10.66.0.2/32 # ip netns exec office wg set wg0 private-key office.key listen-port 51820 peer $(cat home.pub) endpoint 203.0.113.10:51820 allowed-ips 10.66.0.1/32 # ip -n home addr add 10.66.0.1/24 dev wg0; ip -n home link set wg0 up # ip -n office addr add 10.66.0.2/24 dev wg0; ip -n office link set wg0 up # ip netns exec home wg show
interface: wg0 public key: Ag3O/iWoqnhGP5qV8cNgMHtypDCYM5FLIvRlof0G+gc= private key: (hidden) listening port: 51820 peer: vV4m2WGKw3T0kBSjKlol0y/yaW2mkTrUtYV2uux3G2g= endpoint: 203.0.113.20:51820 allowed ips: 10.66.0.2/32
Reading it. endpoint is the other site’s public address and UDP port. allowed ips does two jobs: traffic to 10.66.0.2 goes into the tunnel towards this peer, and traffic arriving from this peer is only accepted if it comes from 10.66.0.2. Getting allowed-ips wrong is the most common reason a WireGuard tunnel is “up” but nothing works. Your keys will be different from ours.
04Send traffic through the tunnel
Ping the office’s tunnel address, then ask WireGuard what happened.
# ip netns exec home ping -c 3 10.66.0.2 # ip netns exec home wg show wg0 latest-handshakes # ip netns exec home wg show wg0 transfer
64 bytes from 10.66.0.2: icmp_seq=1 ttl=64 time=1.16 ms 64 bytes from 10.66.0.2: icmp_seq=2 ttl=64 time=0.322 ms 64 bytes from 10.66.0.2: icmp_seq=3 ttl=64 time=0.301 ms ... 3 packets transmitted, 3 received, 0% packet loss, time 2001ms vV4m2WGKw3T0kBSjKlol0y/yaW2mkTrUtYV2uux3G2g= 1793118365 vV4m2WGKw3T0kBSjKlol0y/yaW2mkTrUtYV2uux3G2g= 476 532
Reading it. The first ping is slower (1.16 ms) because WireGuard does its handshake on demand, the moment there is something to send. latest-handshakes shows when that happened as a Unix timestamp (seconds since 1970; 0 would mean never). transfer is bytes received, then bytes sent, for this peer. When a user says “the VPN is on but nothing works,” check these two first: no handshake means the sides never agreed (keys, endpoint, or UDP 51820 blocked); a handshake with traffic one way only points at allowed-ips or routing.
05Capture inside and outside the tunnel
Start a web page on the office side, capture on the internet cable (net0) and on the tunnel (wg0) at the same time, fetch the page from home, then search both captures for the page’s text. Give the two captures a second to start before the curl.
# cd /tmp && echo "secret office page" > sg-wg.html # ip netns exec office python3 -m http.server 8080 --bind 10.66.0.2 >/dev/null 2>&1 & # ip netns exec office tcpdump -n -l -A -i net0 'udp port 51820' > /tmp/wglab/outer.txt 2>/dev/null & # ip netns exec office tcpdump -n -l -A -i wg0 'tcp port 8080' > /tmp/wglab/inner.txt 2>/dev/null & # ip netns exec home curl -s http://10.66.0.2:8080/sg-wg.html # pkill -f "tcpdump -n -l -A" # grep " IP " /tmp/wglab/outer.txt # grep -c "secret office page" /tmp/wglab/outer.txt # grep -c "secret office page" /tmp/wglab/inner.txt
secret office page 12:26:38.510969 IP 203.0.113.10.51820 > 203.0.113.20.51820: UDP, length 96 12:26:38.511036 IP 203.0.113.20.51820 > 203.0.113.10.51820: UDP, length 96 12:26:38.511120 IP 203.0.113.10.51820 > 203.0.113.20.51820: UDP, length 96 12:26:38.511209 IP 203.0.113.10.51820 > 203.0.113.20.51820: UDP, length 176 12:26:38.511284 IP 203.0.113.20.51820 > 203.0.113.10.51820: UDP, length 96 12:26:38.513509 IP 203.0.113.20.51820 > 203.0.113.10.51820: UDP, length 272 ... 0 1
Reading it. On the internet side, an eavesdropper sees only UDP packets between the two public addresses on port 51820, and the page text appears 0 times. Inside the tunnel (wg0) the same request is an ordinary TCP handshake and HTTP request, and the text appears 1 time. Open /tmp/wglab/inner.txt and you will find GET /sg-wg.html in plain text. That is the honest answer to “what does a VPN hide?”: the contents and the inner addresses. The fact that two sites are talking, when, and roughly how much, is still visible.
06Reach the service only through the tunnel
The web server listens on the tunnel address only. Try it on the office’s public address instead.
# ip netns exec home curl -s -m 3 http://203.0.113.20:8080/sg-wg.html; echo "exit $?"
exit 7
Why it matters. Exit code 7 is “connection refused”: nothing listens on the public side. Binding internal services to the VPN address (or firewalling them to it) is how a site-to-site VPN keeps an office app off the open internet. If you have done the TCP vs UDP lab, you can capture the reset yourself.
07Break it with the wrong key
Give home a brand-new private key the office has never heard of, try to ping, then put the right key back. Wait about five seconds before the last ping so WireGuard retries its handshake.
# wg genkey > /tmp/wglab/wrong.key # ip netns exec home wg set wg0 private-key /tmp/wglab/wrong.key # ip netns exec home ping -c 2 -W 1 10.66.0.2 # ip netns exec home wg set wg0 private-key /tmp/wglab/home.key # ip netns exec home ping -c 2 -W 2 10.66.0.2
2 packets transmitted, 0 received, 100% packet loss, time 1000ms ... 2 packets transmitted, 2 received, 0% packet loss, time 999ms
Reading it. With the wrong key, home’s public key changed, so the office didn’t recognise the handshake and silently ignored it: no error, no reply, just 100% loss. That silence is deliberate. WireGuard doesn’t answer anyone it doesn’t know, so a scanner can’t even tell a WireGuard port is there. The flip side for support: the only clue is a missing or old latest-handshakes time, so check that first.
08Clean up
Stop the web server, delete both sites (this also removes the tunnels), and delete the lab keys.
# pkill -f "http.server 8080" # ip netns del home; ip netns del office # rm -rf /tmp/wglab /tmp/sg-wg.html # exit
What you learned
- wg genkey | wg pubkey — each side keeps its private key and shares only the public key
- endpoint and allowed-ips — where the peer lives, and which addresses go to it and may come from it
- wg show — latest-handshakes and transfer are the first things to check when a tunnel misbehaves
- What a VPN hides — contents and inner addresses; not that two endpoints are talking, or when
- Silent by design — a wrong key gets no reply at all, which keeps the port invisible to scanners
Tried it? Tell us
Nova built this lab from Steven’s ideas, and we ran every step once before publishing. If a step broke, behaved differently for you, or you know a better way, tell us. Ideas for the next lab are welcome too — no promises on timing.
NextSyslog and SNMP: build a monitoring server→
Go deeper
- NAT and firewall lab — put a home router in front of one side and forward UDP 51820 to it
- TCP vs UDP lab — why WireGuard picked UDP
- WireGuard quick start — the official guide, including wg-quick config files for real machines
Original walkthrough. Every command was run end to end, exactly as shown, on Ubuntu 24.04 (WSL2, kernel 6.6.87.2, wireguard-tools 1.0.20210914) on 27 Oct 2026; outputs are from that run, trimmed where marked with .... Your keys, timestamps and byte counts will differ. If something doesn’t match what your machine does, tell me and I will fix it.