A VPN Server
Raspberry Pi Projects
Project 4 — Setting up a VPN Server with WireGuard
What is WireGuard?
WireGuard is a modern VPN protocol. Running it on your Raspberry Pi turns it into a VPN server — a secure tunnel you can connect to from your phone or laptop when on a coffee shop Wi-Fi, hotel network, or anywhere else. Once connected, all your internet traffic is routed through your home broadband, and you can also reach devices on your home network (Pi-hole, Jellyfin, NAS, etc.) as if you were sitting at home.
WireGuard was chosen over OpenVPN for this project because it is:
- Simpler — a fraction of the code; the entire implementation fits in a small Linux kernel module
- Faster — noticeably lower latency than OpenVPN, especially on a low-power Pi
- Easier to configure — a handful of lines per peer vs hundreds of lines of OpenVPN config
- More secure — uses modern cryptography (Curve25519, ChaCha20, Poly1305) with no algorithm negotiation to attack
All traffic from the phone is encrypted and routed through home. Pi-hole, Jellyfin, and other home services are also reachable.
What You Will Need
Address Plan
WireGuard creates a private virtual network between the server and its clients. You need to choose an IP address range for this tunnel that doesn't conflict with your home network. A common choice is 10.0.0.0/24:
| Device | VPN (tunnel) IP | Role |
|---|---|---|
| Raspberry Pi (server) | 10.0.0.1 | VPN gateway — all clients connect to this |
| Your phone | 10.0.0.2 | Peer / client 1 |
| Your laptop | 10.0.0.3 | Peer / client 2 |
| A second phone | 10.0.0.4 | Peer / client 3 |
Each device you want to connect gets its own unique tunnel IP. You assign these yourself when creating the peer configs — there is no DHCP involved.
Step 1 — Update Your Pi
pi@raspberrypi:~$ sudo apt update && sudo apt upgrade -y pi@raspberrypi:~$ sudo reboot
Step 2 — Install WireGuard
WireGuard is in the standard Raspberry Pi OS repositories:
pi@raspberrypi:~$ sudo apt install -y wireguard wireguard-tools Reading package lists... Done ... Setting up wireguard-tools (1.0.20210914-1+b1) ...
Verify the installation:
pi@raspberrypi:~$ wg --version wireguard-tools v1.0.20210914 - https://www.wireguard.com
Step 3 — Generate Server Keys
WireGuard uses public-key cryptography. Each device has a private key (never shared) and a public key (shared with peers so they can authenticate each other).
Create a secure directory for the server keys:
pi@raspberrypi:~$ sudo mkdir -p /etc/wireguard pi@raspberrypi:~$ sudo chmod 700 /etc/wireguard pi@raspberrypi:~$ cd /etc/wireguard
Generate the server private and public keys:
pi@raspberrypi:/etc/wireguard$ wg genkey | sudo tee server_private.key | wg pubkey | sudo tee server_public.key mH3aX9Kj2vPqRsFgTuBnWcYeZlOiMdNhAkEwJbV4pC8= ← this is your server public key (example)
pi@raspberrypi:/etc/wireguard$ sudo chmod 600 server_private.key server_public.key
View and note your server private key — you'll need it in the next step:
pi@raspberrypi:/etc/wireguard$ sudo cat server_private.key 4L7mR2XvNqTgBfYsKpJuAcWeHiDzOlCnMkEaFdGhPb0= ← server private key (example — yours will differ)
Step 4 — Create the Server Configuration
Create the WireGuard interface configuration file:
pi@raspberrypi:~$ sudo nano /etc/wireguard/wg0.conf
Paste the following, replacing the private key with your actual server private key:
# ─── WireGuard Server Configuration ─────────────────────────── [Interface] Address = 10.0.0.1/24 # Pi's tunnel IP and subnet ListenPort = 51820 # UDP port WireGuard listens on PrivateKey = 4L7mR2XvNqTgBfYsKpJuAcWeHiDzOlCnMkEaFdGhPb0= # your server private key # ── IP forwarding: route client traffic to the internet ──────── PostUp = iptables -A FORWARD -i %i -j ACCEPT; \ iptables -A FORWARD -o %i -j ACCEPT; \ iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PostDown = iptables -D FORWARD -i %i -j ACCEPT; \ iptables -D FORWARD -o %i -j ACCEPT; \ iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE # ── Peer: Your Phone ─────────────────────────────────────────── # (add the phone's public key here after generating it in Step 6) [Peer] PublicKey = PHONE_PUBLIC_KEY_GOES_HERE AllowedIPs = 10.0.0.2/32 # phone's tunnel IP
eth0 in the PostUp/PostDown lines with wlan0. Check your interface name with ip link show.
Step 5 — Enable IP Forwarding
By default, the Linux kernel does not forward packets between network interfaces. You need to enable this so your Pi can route VPN traffic to the internet:
pi@raspberrypi:~$ sudo nano /etc/sysctl.conf
Find the line #net.ipv4.ip_forward=1 and uncomment it (remove the #):
net.ipv4.ip_forward=1
Apply the change immediately without rebooting:
pi@raspberrypi:~$ sudo sysctl -p net.ipv4.ip_forward = 1
Step 6 — Generate Client Keys
Each client device needs its own key pair. Generate them on the Pi (you'll transfer the config to the device later):
pi@raspberrypi:/etc/wireguard$ wg genkey | sudo tee phone_private.key | wg pubkey | sudo tee phone_public.key Xp9vR3KjMqTbFgNsYuBwAcWeLiDzOlCnHkEaFdGhPb0= ← phone public key (example) pi@raspberrypi:/etc/wireguard$ sudo chmod 600 phone_private.key phone_public.key
Now update wg0.conf to replace PHONE_PUBLIC_KEY_GOES_HERE with the actual phone public key:
pi@raspberrypi:~$ sudo cat /etc/wireguard/phone_public.key Xp9vR3KjMqTbFgNsYuBwAcWeLiDzOlCnHkEaFdGhPb0=
pi@raspberrypi:~$ sudo nano /etc/wireguard/wg0.conf # Replace PHONE_PUBLIC_KEY_GOES_HERE with the key above
Create the phone's client config file
Create a config file that you will transfer to the phone:
pi@raspberrypi:~$ sudo nano /etc/wireguard/phone_client.conf
[Interface] PrivateKey = PHONE_PRIVATE_KEY_HERE # contents of phone_private.key Address = 10.0.0.2/32 # phone's tunnel IP DNS = 10.0.0.1 # use the Pi (and Pi-hole!) as DNS over VPN [Peer] PublicKey = SERVER_PUBLIC_KEY_HERE # contents of server_public.key Endpoint = your-ddns-domain.duckdns.org:51820 # your home's public address AllowedIPs = 0.0.0.0/0 # route ALL traffic through VPN PersistentKeepalive = 25 # keep connection alive through NAT
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24 instead — this is called split tunnelling.
Fill in the actual key values:
pi@raspberrypi:~$ sudo cat /etc/wireguard/phone_private.key # copy into PrivateKey pi@raspberrypi:~$ sudo cat /etc/wireguard/server_public.key # copy into PublicKey
Step 7 — Start WireGuard
Bring the WireGuard interface up and enable it to start on boot:
pi@raspberrypi:~$ sudo wg-quick up wg0 [#] ip link add wg0 type wireguard [#] wg setconf wg0 /dev/fd/63 [#] ip -4 address add 10.0.0.1/24 dev wg0 [#] ip link set mtu 1420 up dev wg0 [#] iptables -A FORWARD -i wg0 -j ACCEPT ... pi@raspberrypi:~$ sudo systemctl enable wg-quick@wg0 Created symlink /etc/systemd/system/multi-user.target.wants/wg-quick@wg0.service
Verify the server is running:
pi@raspberrypi:~$ sudo wg show interface: wg0 public key: mH3aX9Kj2vPqRsFgTuBnWcYeZlOiMdNhAkEwJbV4pC8= private key: (hidden) listening port: 51820 peer: Xp9vR3KjMqTbFgNsYuBwAcWeLiDzOlCnHkEaFdGhPb0= allowed ips: 10.0.0.2/32
Step 8 — Forward Port 51820 on Your Router
Log in to your router admin panel and add a port forwarding rule:
| Setting | Value |
|---|---|
| External port | 51820 |
| Internal port | 51820 |
| Protocol | UDP (not TCP — WireGuard uses UDP only) |
| Internal IP | Your Pi's local IP (e.g. 192.168.1.42) |
41194 or 13231 — change ListenPort in wg0.conf and the port forwarding rule to match.
Step 9 — Connect a Client
Transfer the client config file (phone_client.conf) to your device. The most secure way to do this is via QR code:
pi@raspberrypi:~$ sudo apt install -y qrencode pi@raspberrypi:~$ sudo cat /etc/wireguard/phone_client.conf | qrencode -t ANSIUTF8
A QR code will appear in your terminal. Open the WireGuard app on your phone and tap Add a tunnel → Scan from QR code. Point your camera at the terminal.
phone_client.conf to your laptop via SCP or by copying the text. In the WireGuard app click Add Tunnel → Import tunnel(s) from file and select the .conf file.sudo apt install wireguard. Copy the client config to /etc/wireguard/wg0.conf on the laptop and start it with sudo wg-quick up wg0. Add to boot with sudo systemctl enable wg-quick@wg0.brew install wireguard-tools and sudo wg-quick up /path/to/phone_client.conf.Step 10 — Test the Connection
With the WireGuard app active on your phone (ideally on mobile data, not home Wi-Fi), verify the tunnel is working:
On the phone
Visit whatismyip.com — it should show your home public IP address, not your mobile carrier's IP. This confirms all traffic is routing through your Pi.
On the Pi — confirm the peer connected
pi@raspberrypi:~$ sudo wg show interface: wg0 ... peer: Xp9vR3KjMqTbFgNsYuBwAcWeLiDzOlCnHkEaFdGhPb0= endpoint: 203.0.113.99:54321 ← your phone's IP appeared! allowed ips: 10.0.0.2/32 latest handshake: 5 seconds ago ← handshake = connection is live transfer: 1.22 KiB received, 4.50 KiB sent
Reach home devices over VPN
# Ping the Pi from the phone (use your phone's terminal app or VPN app diagnostics) ping 10.0.0.1 # should reply ping 192.168.1.100 # if Pi-hole is on this IP, this should also reply
Adding More Clients
For each additional device, repeat Steps 6–9 with a new key pair and a new tunnel IP (10.0.0.3, 10.0.0.4, etc.), and add a new [Peer] block to the server's wg0.conf.
After editing wg0.conf, reload WireGuard without dropping existing connections:
pi@raspberrypi:~$ sudo wg addconf wg0 <(wg-quick strip wg0) # Or simply restart the service: pi@raspberrypi:~$ sudo systemctl restart wg-quick@wg0
Troubleshooting Common Problems
cat /proc/sys/net/ipv4/ip_forward — should return 1. If not, re-run sudo sysctl -p. (2) List iptables rules to confirm MASQUERADE is present: sudo iptables -t nat -L POSTROUTING. (3) Make sure the interface name in PostUp/PostDown is correct (eth0 vs wlan0) — check with ip route | grep default.nc -zvu your-public-ip 51820 from a phone on mobile data. (3) Check your public IP / DDNS hostname is resolving correctly: nslookup your-domain.duckdns.org. (4) Verify WireGuard is actually listening: sudo ss -ulnp | grep 51820.PersistentKeepalive = 25 is set in the client config — this sends a keepalive packet every 25 seconds to prevent NAT/firewall timeout. On iOS, allow the WireGuard app to run in the background in Settings → General → Background App Refresh.DNS = 10.0.0.1 (or another reachable DNS). Try using 8.8.8.8 as a test. Also check that Pi-hole (if used as DNS) is listening on the wg0 interface — in the Pi-hole admin, go to Settings → DNS and enable "Listen on all interfaces".sudo wg-quick down wg0. Then check your PostUp iptables rules — particularly the MASQUERADE rule. Ensure the output interface is your external interface (eth0/wlan0), not wg0 itself.sudo wg-quick down wg0 && sudo wg-quick up wg0. To add a new peer without restarting: sudo wg set wg0 peer <PUBLIC_KEY> allowed-ips 10.0.0.x/32.*/5 * * * * curl -s "https://www.duckdns.org/update?domains=YOURDOMAIN&token=YOUR_TOKEN&ip=" > /tmp/duckdns.log. This runs every 5 minutes. Your full setup instructions are at duckdns.org/install.jsp.Quick Reference
| Task | Command |
|---|---|
| Start WireGuard | sudo wg-quick up wg0 |
| Stop WireGuard | sudo wg-quick down wg0 |
| Show tunnel status | sudo wg show |
| Show connected peers | sudo wg show wg0 peers |
| Generate a new private key | wg genkey |
| Derive public key from private | echo <private> | wg pubkey |
| Generate QR code for client | cat client.conf | qrencode -t ANSIUTF8 |
| Check IP forwarding is on | cat /proc/sys/net/ipv4/ip_forward |
| List iptables NAT rules | sudo iptables -t nat -L -n -v |
| Check WireGuard is listening | sudo ss -ulnp | grep 51820 |
| Enable WireGuard on boot | sudo systemctl enable wg-quick@wg0 |
| View WireGuard logs | sudo journalctl -u wg-quick@wg0 -f |
| Server config file | /etc/wireguard/wg0.conf |
| Find your public IP | curl api.ipify.org |