A VPN Server

Raspberry Pi Projects

Project 4 — Setting up a VPN Server with WireGuard

What you will build A personal VPN server so you can securely access your home network from anywhere in the world
Difficulty Intermediate — involves networking concepts and key management
Time to complete 1 – 2 hours
Recommended hardware Any Raspberry Pi — WireGuard is very lightweight (Pi Zero 2W is fine)

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
How your VPN will work
📱 Your phone 10.0.0.2
──
🔒 WireGuard tunnel (UDP 51820)
──
🍓 Pi VPN server 10.0.0.1
──
🌐 Internet your home IP

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

🍓 Any Raspberry Pi WireGuard is extremely lightweight. Even a Pi Zero 2W will handle several simultaneous VPN clients comfortably.
🌐 Public IP or DDNS Your home must be reachable from the internet. A DDNS service (DuckDNS, No-IP) handles a changing home IP automatically.
📡 Router access You need to forward UDP port 51820 from your router to the Pi. Admin access to your router is required.
🔒 Static local IP The Pi must keep the same local IP so port forwarding always points to it. Set a DHCP reservation in your router.
📱 WireGuard client app Free on iOS, Android, Windows, Mac, and Linux. You will install this on the devices that connect to your VPN.
💻 SSH access All configuration is done in the terminal. Take care — misconfiguring networking can lock you out of SSH.
Read before you start: If you misconfigure the network settings on your Pi, you could lose SSH access. Work through each step carefully, and if possible have a screen and keyboard available as a fallback to connect directly to the Pi.

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:

DeviceVPN (tunnel) IPRole
Raspberry Pi (server)10.0.0.1VPN gateway — all clients connect to this
Your phone10.0.0.2Peer / client 1
Your laptop10.0.0.3Peer / client 2
A second phone10.0.0.4Peer / 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).

🔐 Private key
Known only to the device that owns it. Never transmitted, never shared. If this leaks, your VPN is compromised.
🔑 Public key
Derived from the private key. Shared with the other side of the tunnel so they can verify your identity. Safe to share.

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
If your Pi uses Wi-Fi instead of wired Ethernet: replace 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 = 0.0.0.0/0 routes all internet traffic through your Pi — full tunnel mode. If you only want to reach home network resources (not route all traffic through home), use 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:

SettingValue
External port51820
Internal port51820
ProtocolUDP (not TCP — WireGuard uses UDP only)
Internal IPYour Pi's local IP (e.g. 192.168.1.42)
If your ISP blocks incoming UDP connections on standard ports (rare but possible), try an alternative port like 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.

📱 iOS / Android
Install the free WireGuard app from the App Store or Google Play. Tap the + button → Scan from QR code and scan the QR code shown in your terminal. Give the tunnel a name (e.g. "Home VPN") and tap Save. Toggle it on to connect.
🪟 Windows
Download the WireGuard app from wireguard.com/install. Transfer 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.
🐧 Linux
Install WireGuard: 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.
🍎 macOS
Install from the Mac App Store (search "WireGuard"). Import the .conf file via File → Import Tunnel(s) from File, or use Homebrew: 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

⚠ Phone connects but no internet traffic flows
IP forwarding or iptables masquerading is not working. Check: (1) 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.
⚠ "latest handshake" never updates — no connection
The server is not reachable on UDP 51820. Work through: (1) Confirm port forwarding is set to UDP (not TCP). (2) Test from outside your network: 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.
⚠ Connection drops when phone screen locks
This is normal iOS/Android battery management behaviour. Make sure 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 doesn't work over VPN (websites don't load even though ping works)
The DNS setting in the client config may not be taking effect. Verify the client config has 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".
⚠ I lost SSH access after starting WireGuard
A routing loop has occurred — traffic to the Pi is being sent back into the VPN tunnel. Stop WireGuard immediately with a direct console connection: 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.
⚠ wg0.conf edits have no effect
WireGuard reads the config only at startup. To apply changes to a running tunnel: 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.
⚠ My ISP has a dynamic IP and DDNS stops updating
Set up automatic DDNS updates on the Pi itself rather than relying on your router. For DuckDNS, create a cron job: */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

TaskCommand
Start WireGuardsudo wg-quick up wg0
Stop WireGuardsudo wg-quick down wg0
Show tunnel statussudo wg show
Show connected peerssudo wg show wg0 peers
Generate a new private keywg genkey
Derive public key from privateecho <private> | wg pubkey
Generate QR code for clientcat client.conf | qrencode -t ANSIUTF8
Check IP forwarding is oncat /proc/sys/net/ipv4/ip_forward
List iptables NAT rulessudo iptables -t nat -L -n -v
Check WireGuard is listeningsudo ss -ulnp | grep 51820
Enable WireGuard on bootsudo systemctl enable wg-quick@wg0
View WireGuard logssudo journalctl -u wg-quick@wg0 -f
Server config file/etc/wireguard/wg0.conf
Find your public IPcurl api.ipify.org