Point-to-Point WireGuard VPN on Raspberry Pi
Why I Needed This
I had a few services running on my Raspberry Pi at home - a Samba share, some Docker containers, a couple of side projects. They worked great on the local network, but I had no way to reach them when I was out. Port forwarding felt like poking holes in my firewall for each service individually. What I wanted was a single encrypted tunnel back into my home network.
WireGuard kept coming up in everything I read. It’s a modern VPN protocol - small codebase (~4,000 lines of kernel code), fast cryptographic handshake, and dead simple configuration compared to OpenVPN or IPSec. The whole thing runs as a kernel module, so there’s almost no overhead.
What I Used
- Raspberry Pi 5 (any Pi 4+ works)
- Ethernet connection (Wi-Fi works but I wanted stability for a VPN endpoint)
- Client device - my phone and MacBook
- WireGuard app on the client side
How WireGuard Works
WireGuard is peer-to-peer - there’s technically no “server” and “client”, just peers that exchange public keys. But for this setup, the Pi acts as the always-on endpoint (I’ll call it the server) and my phone/laptop connects to it (the client).
sequenceDiagram
autonumber
participant Client as Client Device
participant Router as Home Router
participant Pi as Raspberry Pi
Note over Client, Pi: One-time setup (manual key exchange)
Client -->> Pi: Share client public key
Pi -->> Client: Share server public key
Note over Client, Pi: Connection (automatic)
Client ->> Router: UDP 51820 (encrypted)
Router ->> Pi: Port forward
Pi -->> Router: Encrypted response
Router -->> Client: Encrypted response
Note over Client, Pi: Ongoing communication
Client ->> Router: Encrypted traffic
Router ->> Pi: Forward to 10.0.0.1
Pi -->> Client: Encrypted traffic
The key exchange happens once during setup. After that, WireGuard handles handshakes automatically - it re-negotiates every few minutes for forward secrecy.
Setting Up the Server (Raspberry Pi)
Install WireGuard
sudo apt update && sudo apt upgrade -y
sudo apt install -y wireguard
WireGuard is in the official repos for Raspberry Pi OS, so this just works.
Generate Keys
WireGuard uses Curve25519 key pairs. The umask 077 ensures the private key file is only readable by root.
umask 077
wg genkey | sudo tee /etc/wireguard/private.key | wg pubkey | sudo tee /etc/wireguard/public.key
Configure the Interface
I created the WireGuard config file:
sudo vim /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = $(cat /etc/wireguard/private.key)
# Client configurations will be added here later
A quick breakdown of what these do:
| Field | Value | Purpose |
|---|---|---|
| Address | 10.0.0.1/24 | Pi’s IP on the VPN subnet (range: 10.0.0.1 - 10.0.0.254) |
| ListenPort | 51820 | UDP port WireGuard listens on |
| PrivateKey | from file | Encrypts all traffic from this peer |
Start the Service
sudo systemctl enable --now wg-quick@wg0
Quick check to see if it’s running:
sudo wg show
interface: wg0
public key: <SERVER_PUBLIC_KEY>
private key: (hidden)
listening port: 51820
I also verified the interface showed up in NetworkManager:
nmcli d
DEVICE TYPE STATE CONNECTION
wlan1 wifi connected Wi-Fi connection 1
lo loopback connected (externally) lo
docker0 bridge connected (externally) docker0
wg0 wireguard connected (externally) wg0
eth0 ethernet disconnected --
wlan0 wifi disconnected --
p2p-dev-wlan0 wifi-p2p disconnected --
There’s wg0 - the VPN interface is live.
Setting Up the Client (Phone / Laptop)
I installed the WireGuard app on my phone from the App Store and on my MacBook from the official site.
The app generates a key pair automatically when you create a new tunnel. I filled in the config:
[Interface]
PrivateKey = <CLIENT_PRIVATE_KEY, AUTO_GENERATED>
Address = 10.0.0.2/24
DNS = 1.1.1.1, 8.8.8.8
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = <SERVER_PUBLIC_IP>:51820
AllowedIPs = 10.0.0.0/24
The important fields:
| Field | What It Does |
|---|---|
| Address | Client’s IP on the VPN subnet (10.0.0.2) |
| DNS | DNS servers to use while connected |
| PublicKey | The Pi’s public key (from /etc/wireguard/public.key) |
| Endpoint | The Pi’s public IP (or local IP if on the same network) |
| AllowedIPs | Which traffic goes through the tunnel |
Setting AllowedIPs = 10.0.0.0/24 means only VPN subnet traffic gets tunneled. If you want all internet traffic routed through the Pi (useful on public Wi-Fi), change it to 0.0.0.0/0.
Connecting the Pieces
Add the Client to the Server
Now that the client has a public key, I added it to the server’s config:
[Peer]
PublicKey = <CLIENT_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32
The /32 means this peer can only use the single IP 10.0.0.2 - not a range. This is important for security.
Then restart the service:
sudo systemctl restart wg-quick@wg0
Making It Reachable from Outside
Everything so far only works on the local network. To connect from outside (coffee shop, office, mobile data), two things need to happen.
1. Port forwarding on the router
Log into your router’s admin panel and forward UDP port 51820 to the Pi’s local IP address. For my setup:
| Setting | Value |
|---|---|
| Protocol | UDP |
| External port | 51820 |
| Internal IP | 192.168.1.x (Pi’s local IP) |
| Internal port | 51820 |
This is the only port you need to open - WireGuard handles everything over a single UDP port, which is one of the things I like about it compared to OpenVPN’s multi-port setups.
2. Finding your public IP
Your client needs to know your home’s public IP for the Endpoint field. You can find it by running this on the Pi:
curl -s ifconfig.me
The problem is most ISPs assign dynamic IPs that change periodically. I dealt with this by setting up a free DDNS service - it gives you a hostname like myhome.ddns.net that always points to your current public IP. You can use that as the endpoint instead:
Endpoint = myhome.ddns.net:51820
Most routers have built-in DDNS support (No-IP, DuckDNS, etc.), so the IP updates automatically whenever it changes.
graph LR
Client[Client Device] -->|Internet| DDNS[DDNS / Public IP]
DDNS -->|UDP 51820| Router[Home Router]
Router -->|Port Forward| Pi[Raspberry Pi<br/>192.168.1.x]
Testing It
Activated the tunnel on my phone and pinged the Pi:
ping -c 3 10.0.0.1
PING 10.0.0.1 (10.0.0.1): 56 data bytes
64 bytes from 10.0.0.1: icmp_seq=0 ttl=64 time=8.444 ms
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=11.057 ms
64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=30.645 ms
--- 10.0.0.1 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 8.444/16.715/30.645/9.907 ms
Three packets, zero loss. To make sure it wasn’t just ICMP working, I started a quick HTTP server on the Pi:
python3 -m http.server 8000
Opened 10.0.0.1:8000 in my phone’s browser - the directory listing loaded. The tunnel was working end to end.
The Full Picture
Here’s how everything fits together:
graph TD
Phone[Phone<br/>10.0.0.2] -->|WireGuard tunnel| Internet[Internet]
Laptop[MacBook<br/>10.0.0.3] -->|WireGuard tunnel| Internet
Internet -->|UDP 51820| Router[Home Router]
Router -->|Port Forward| Pi[Raspberry Pi<br/>10.0.0.1]
Pi --> Samba[Samba Share]
Pi --> Docker[Docker Services]
Pi --> Web[Web Apps]
One tunnel in, access to everything on the Pi. No per-service port forwarding needed.
What I’d Do Differently
- More peers: Adding a second client (like my work laptop) is just another
[Peer]block in the server config with a unique IP. - Firewall rules: Right now the VPN subnet has full access to the Pi. For a multi-user setup, I’d want
iptablesrules to restrict what each peer can reach. - Split tunneling: Right now I either tunnel VPN traffic only or everything. A more granular setup could route specific services through the tunnel while keeping the rest direct.
References
- WireGuard. WireGuard: Fast, modern, secure VPN tunnel. https://www.wireguard.com/
- Install server - Pi-hole documentation. https://docs.pi-hole.net/guides/vpn/wireguard/server/
- Wikipedia contributors. (2024, July 10). WireGuard. Wikipedia. https://en.wikipedia.org/wiki/WireGuard
- Ludwig, B. J. (2020, November 10). WireGuard Point to point configuration. Pro Custodibus. https://www.procustodibus.com/blog/2020/11/wireguard-point-to-point-config/