286 lines
11 KiB
Markdown
286 lines
11 KiB
Markdown
# Raspberry Pi VPN (Networking)
|
|
|
|
In this lesson you'll build a personal VPN server on your Raspberry Pi using
|
|
**WireGuard** — the modern standard for VPNs. It gives you a private road into
|
|
your home network: when you're at school, at a friend's house, or on vacation,
|
|
you can reach devices on your home Wi-Fi — the Pi, a NAS, a media server, a smart
|
|
home dashboard — as if your phone were plugged into the house.
|
|
|
|
As a side effect, the road is encrypted, so nobody in between can read what
|
|
you're accessing.
|
|
|
|
## How it works
|
|
|
|
A quick networking refresher so the steps make sense:
|
|
|
|
- Devices on your home network get **local** IP addresses like
|
|
`192.168.1.42`. To the internet, your whole house appears as **one** public IP
|
|
address (the router's).
|
|
- By default the internet can only knock on the front door — the router. To reach
|
|
devices *inside* the house, we set up one exception: the Pi runs a VPN server,
|
|
and the router forwards that one port to it.
|
|
- A VPN creates an **encrypted tunnel** between your phone and the Pi. Inside the
|
|
tunnel we use our own private addresses: the Pi is `10.100.0.1`, your phone is
|
|
`10.100.0.2`.
|
|
- When you connect, your phone has two identities at once: it's on your carrier's
|
|
network *and* on your home network. Anything in your home range
|
|
(`192.168.1.x`) travels through the tunnel into the house.
|
|
|
|
```
|
|
phone --encrypted tunnel--> Pi --home network--> NAS, Pi, media server, ...
|
|
```
|
|
|
|
**Check your own network first:** find your Pi's local IP on the Pi with
|
|
`hostname -I`. This lesson assumes it looks like `192.168.1.42`, so the home
|
|
range is `192.168.1.0/24`. If your router uses a different range (some use
|
|
`192.168.0.x` or `10.0.0.x`), substitute yours everywhere.
|
|
|
|
## What you need
|
|
|
|
- A Raspberry Pi running Raspberry Pi OS, connected to your home network (wired
|
|
ethernet is best and easiest)
|
|
- Your phone, with the official **WireGuard** app (iOS or Android)
|
|
- Access to your router's admin page (usually `192.168.1.1` in a browser)
|
|
|
|
## Step 1 — Install WireGuard on the Pi
|
|
|
|
```
|
|
sudo apt update
|
|
sudo apt install -y wireguard wireguard-tools nftables qrencode
|
|
```
|
|
|
|
(`qrencode` prints a scannable QR code; `nftables` handles the forwarding rules
|
|
later.)
|
|
|
|
## Step 2 — Create the server's keys
|
|
|
|
Every WireGuard device has a **private key** (a secret) and a **public key**
|
|
(safe to share). Think of them like a password and a public address. The Pi gets
|
|
its pair now:
|
|
|
|
```
|
|
sudo -i
|
|
cd /etc/wireguard
|
|
umask 077
|
|
wg genkey | tee server.key | wg pubkey > server.pub
|
|
cat server.key
|
|
```
|
|
|
|
- `sudo -i` logs you in as the administrator account (root). Type `exit` at the
|
|
end of each root session to go back to your normal user.
|
|
- `umask 077` makes sure new files are only readable by you.
|
|
- Copy the long string from `cat server.key` — you'll paste it into the config in
|
|
the next step. **Never share your private key.** The public key (`server.pub`)
|
|
is fine to share.
|
|
|
|
## Step 3 — Write the server config
|
|
|
|
Still as root, open the config file:
|
|
|
|
```
|
|
nano /etc/wireguard/wg0.conf
|
|
```
|
|
|
|
Type this, replacing the last line with your server's private key:
|
|
|
|
```
|
|
[Interface]
|
|
Address = 10.100.0.1/24, fd08:4711::1/64
|
|
ListenPort = 47111
|
|
PrivateKey = PASTE_YOUR_SERVER_PRIVATE_KEY_HERE
|
|
```
|
|
|
|
Save (`Ctrl+O`, `Enter`) and quit (`Ctrl+X`). What the lines mean:
|
|
|
|
- `Address` — the Pi's address inside the tunnel (`10.100.0.1`).
|
|
- `ListenPort` — the port clients connect to. `47111`, UDP.
|
|
|
|
## Step 4 — Let the Pi forward traffic
|
|
|
|
To send your phone's traffic on to other devices in the house, the Pi has to be
|
|
willing to pass packets through. Two things enable that: **IP forwarding**
|
|
(allow packets through) and **NAT** (let tunnel devices share the Pi's internet
|
|
connection — you'll want it if you ever tunnel more than just home access).
|
|
|
|
First, edit `/etc/sysctl.d/99-sysctl.conf` (`nano /etc/sysctl.d/99-sysctl.conf`).
|
|
Make sure these two lines exist **without** a `#` in front of them (remove the `#`
|
|
if there is one):
|
|
|
|
```
|
|
net.ipv4.ip_forward = 1
|
|
net.ipv6.conf.all.forwarding = 1
|
|
```
|
|
|
|
Save and quit, then apply:
|
|
|
|
```
|
|
sysctl --system
|
|
```
|
|
|
|
Second, open the server config again (`nano /etc/wireguard/wg0.conf`) and add
|
|
these two lines inside the `[Interface]` section (paste them exactly as one line
|
|
each):
|
|
|
|
```
|
|
PostUp = nft add table ip wireguard; nft add chain ip wireguard wireguard_chain {type nat hook postrouting priority srcnat\; policy accept\;}; nft add rule ip wireguard wireguard_chain counter packets 0 bytes 0 masquerade; nft add table ip6 wireguard; nft add chain ip6 wireguard wireguard_chain {type nat hook postrouting priority srcnat\; policy accept\;}; nft add rule ip6 wireguard wireguard_chain counter packets 0 bytes 0 masquerade
|
|
PostDown = nft delete table ip wireguard; nft delete table ip6 wireguard
|
|
```
|
|
|
|
`PostUp`/`PostDown` are commands that run automatically when the tunnel starts and
|
|
stops — here they add and remove the NAT rule. Ugly-looking, but it's copy-paste
|
|
and it's what the official docs recommend.
|
|
|
|
When you're done, type `exit` to leave root.
|
|
|
|
## Step 5 — Start the VPN
|
|
|
|
```
|
|
sudo systemctl enable wg-quick@wg0
|
|
sudo systemctl start wg-quick@wg0
|
|
sudo wg
|
|
```
|
|
|
|
If `sudo wg` prints an interface called `wg0` with a public key and a listening
|
|
port, the server is up. `enable` makes it start automatically after every reboot,
|
|
so you only ever run the `start` command after changing the config.
|
|
|
|
## Step 6 — Open a door in the router
|
|
|
|
Your phone needs to reach the Pi through the internet. Two things in your router's
|
|
admin page:
|
|
|
|
1. **Give the Pi a fixed local IP.** Find the current one on the Pi with
|
|
`hostname -I`, then add a **DHCP reservation** in the router (binds that IP to
|
|
the Pi's MAC address). If the Pi's IP ever changes, the port forward breaks.
|
|
2. **Add a port forward: UDP port `47111` → the Pi's local IP.**
|
|
|
|
Also find your home's **public IP** — it's often shown on the router's front page
|
|
("WAN IP"), or open `https://ifconfig.co` on any device at home. You'll paste it
|
|
into the client config next.
|
|
|
|
## Step 7 — Add your phone as a client
|
|
|
|
WireGuard calls every connected device a **peer**. Back on the Pi:
|
|
|
|
```
|
|
sudo -i
|
|
cd /etc/wireguard
|
|
umask 077
|
|
name=phone
|
|
wg genkey | tee ${name}.key | wg pubkey > ${name}.pub
|
|
wg genpsk > ${name}.psk
|
|
```
|
|
|
|
That created the phone's key pair plus a **preshared key** (an extra per-device
|
|
layer of encryption).
|
|
|
|
Register the phone in the server config — this appends a `[Peer]` section to
|
|
`wg0.conf`:
|
|
|
|
```
|
|
echo "[Peer]" >> wg0.conf
|
|
echo "PublicKey = $(cat ${name}.pub)" >> wg0.conf
|
|
echo "PresharedKey = $(cat ${name}.psk)" >> wg0.conf
|
|
echo "AllowedIPs = 10.100.0.2/32, fd08:4711::2/128" >> wg0.conf
|
|
```
|
|
|
|
Then tell the running server about the change (this reloads the config without
|
|
kicking off connected clients):
|
|
|
|
```
|
|
wg syncconf wg0 <(wg-quick strip wg0)
|
|
```
|
|
|
|
Now build the config file the phone will use:
|
|
|
|
```
|
|
echo "[Interface]" > ${name}.conf
|
|
echo "Address = 10.100.0.2/32, fd08:4711::2/128" >> ${name}.conf
|
|
echo "PrivateKey = $(cat ${name}.key)" >> ${name}.conf
|
|
echo "" >> ${name}.conf
|
|
echo "[Peer]" >> ${name}.conf
|
|
echo "PublicKey = $(cat server.pub)" >> ${name}.conf
|
|
echo "PresharedKey = $(cat ${name}.psk)" >> ${name}.conf
|
|
echo "Endpoint = 203.0.113.10:47111" >> ${name}.conf
|
|
echo "AllowedIPs = 10.100.0.0/24, fd08:4711::/64, 192.168.1.0/24" >> ${name}.conf
|
|
echo "PersistentKeepalive = 25" >> ${name}.conf
|
|
```
|
|
|
|
Replace `203.0.113.10` with **your home's public IP**, and `192.168.1.0/24` with
|
|
**your home range** if it's different. What the important lines do:
|
|
|
|
- `AllowedIPs` decides what travels through the tunnel. Here it's the tunnel
|
|
itself plus your whole home range — so home devices are reachable. Everything
|
|
else (normal web browsing, apps) goes over your carrier's network directly,
|
|
exactly as usual.
|
|
(If you ever want *everything* tunneled — e.g. to route it through a future
|
|
Pi-hole — you'd replace that line with `AllowedIPs = 0.0.0.0/0, ::/0`.)
|
|
- `PersistentKeepalive = 25` — phones sit behind carriers' NAT, so this sends a
|
|
small packet every 25 seconds to keep the tunnel's door open.
|
|
|
|
## Step 8 — Connect your phone
|
|
|
|
Still as root, print the config as a QR code:
|
|
|
|
```
|
|
qrencode -t ansiutf8 < ${name}.conf
|
|
```
|
|
|
|
On your phone: open the WireGuard app → tap the blue **+** → scan the QR code →
|
|
toggle the tunnel **on**. (If you're using the Pi over SSH from a laptop, the QR
|
|
code appears in your terminal window — just scan it with the phone.) Then `exit`
|
|
root.
|
|
|
|
## Step 9 — Check it works
|
|
|
|
The interesting test is from **outside** the house: turn your phone's Wi-Fi off
|
|
and use cellular data.
|
|
|
|
1. On the Pi, run `sudo wg`. Your phone should appear with
|
|
`latest handshake: X seconds ago` and a transfer count.
|
|
2. Open your Pi's local address in your phone's browser —
|
|
`http://192.168.1.42` or `http://192.168.1.42/admin` (use your Pi's own local
|
|
IP). If the page loads from a school Wi-Fi network, your phone is effectively
|
|
sitting in your house.
|
|
3. Try SSH: `ssh YOUR_USER@192.168.1.42` from Termux (Android) or any SSH client
|
|
on iPhone. If you get a shell, remote access is fully working.
|
|
|
|
## What you can do now
|
|
|
|
Rule of thumb: anywhere you would have typed a `192.168.1.x` address while at
|
|
home, that same address now works from anywhere in the world.
|
|
|
|
- Web interfaces: Pi-hole admin, your NAS, Home Assistant — open them in the
|
|
browser.
|
|
- SSH into any device in the house.
|
|
- Stream from a media server or download files from a NAS.
|
|
|
|
One caveat: device **names** like `raspberrypi.local` only resolve *inside* the
|
|
house, so over the tunnel, use IP addresses.
|
|
|
|
## Troubleshooting
|
|
|
|
- **Phone never shows a handshake:** check the port forward is UDP (not TCP) on
|
|
port `47111` pointing at the Pi's current local IP; check the `Endpoint` line
|
|
has your *current* public IP (it can change — call your ISP for a static one if
|
|
it's changing often); check keys were copied without extra spaces.
|
|
- **Can't connect from home Wi-Fi, but cellular works:** common and often
|
|
unfixable — many routers can't loop a forwarded port back to devices already
|
|
inside the network (no "NAT loopback"). Test with cellular data.
|
|
- **Connected, but home devices are unreachable:** the `AllowedIPs` line in the
|
|
client config has the wrong home range — it must match your router's subnet,
|
|
the same range your Pi's IP lives in. Also make sure Step 4 (IP forwarding) is
|
|
done.
|
|
- **A device works at home but not over the tunnel:** use its IP address instead
|
|
of its name (see the caveat above), and double-check the device is actually on
|
|
your home network.
|
|
|
|
## Security notes
|
|
|
|
- Anyone with your phone's config file can walk into your home network. Treat it
|
|
like a password — don't screenshot it into a shared chat.
|
|
- Your Pi is now reachable from the internet. Keeping a firewall enabled (for
|
|
example with `ufw`) is a good next step.
|
|
- Lost a device? Delete its `[Peer]` section from `wg0.conf` and run
|
|
`wg syncconf wg0 <(wg-quick strip wg0)` again — its keys stop working.
|