I had a Raspberry Pi lying around and wanted to do something actually useful with it—something more exciting than just another media server or a blinking LED project. So, naturally, I decided to build my own WireGuard VPN server at home. The goal? To access my entire home network securely from anywhere in the world, whether I’m on dodgy public Wi-Fi or just out and about. Turns out, setting up a VPN on a Pi is way easier than it sounds, and it opens up a world of possibilities—from grabbing files on my laptop to tinkering with projects remotely. Here’s how I did it—and how you can too.
Table of Contents
Wait, What the Hell Is a VPN Again?
We’ve all used a VPN before. Maybe to sneak onto the office network while pretending to be productive in a café. Maybe to stream Netflix shows that aren’t available in your country (looking at you, Canadian The Office). Maybe because your company made you install one, and now you’re afraid to ask what it actually does. So for all the self-taught devs, bootcamp graduates, and proud Stack Overflow copy-pasters out there (hi, I’m one of you), here’s the no-jargon version:
A VPN, or Virtual Private Network, is basically a private tunnel over the internet. It lets your device behave as if it’s inside your home network—even if you’re halfway across the world or, worse, inside a coworking space surrounded by people who call themselves “thought leaders” and use words like “synergy” without irony. It’s like teleportation for your packets. You connect from anywhere, and boom: your phone, laptop, or whatever suddenly has the same access as if it were sitting on your couch at home, watching the logs roll by.
Why WireGuard Is the New Hotness
WireGuard is a relatively new VPN protocol that was designed to be simple, fast, and secure. It’s built into the Linux kernel, which means it runs with minimal overhead and is optimized from the ground up. Where other VPNs come with layers of legacy options and deeply nested config files, WireGuard offers a config syntax that looks like something a sane person wrote.
Installing WireGuard is refreshingly straightforward if you’re on a Debian-based system (like Raspberry Pi OS or Ubuntu). Run sudo apt update
followed by sudo apt install wireguard wireguard-tools
and you’re done. No manual kernel modules, no weird compilation steps. It’s almost suspicious how easy it is.
Configuring the Server: Welcome to the WireGuard Cult
Once you’ve got WireGuard installed, it’s time to generate the config file on the server side. In the device that you want to act as the VPN server, get the root access:
sudo su
After gaining the root access, proceed to the wireguard folder located in the /etc/ folder.
cd /etc/wireguard
(Tip: If there exists no such folder, just make one by running mkdir /etc/wireguard
).
In the folder, we will generate the server keys. For this, run the following:
wg genkey | tee privatekey | wg pubkey > publickey
Run ls
to check if the files are generated.
Keep the private key secret and safe. The public key will later be useful to share it with the client devices later on. It’s like exchanging friendship bracelets, but with cryptographic certainty instead of childhood delusion.
Now, create the server configuration file wg0.conf
in the same folder (/etc/wireguard/
). It’ll look something like this:
[Interface]
PrivateKey=<server-private-key>
Address=<server-ip-address>/<subnet>
SaveConfig=true
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o <public-interface> -j MASQUERADE;
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o <public-interface> -j MASQUERADE;
ListenPort = 51820
In the config above, you need to entire the private key of the server which you will find in the privatekey file that we generated above. Enter the value in the PrivateKey
field.
Next, choose an address and a subnet of the server for your VPN connection. An example of this would be 10.0.1.1/8.
That PostUp
line sets up IP masquerading so traffic from your VPN clients can reach the wider internet through your home connection. In that line, notice the <public-interface> parameter. You need to replace this with the interface used by your device. You can know this by running ip addr show
in your terminal. In many cases, it is either wlp3s0 or eth0.
Ultimately, determine the port number of your VPN server. You can set it to pretty much any unused UDP port, but 51820 is the default WireGuard port and works just fine unless your ISP or paranoid firewall has other ideas.
Once you’ve picked your port, you’ll need to forward it to your VPN server device. This involves diving into your router’s admin panel—the digital equivalent of spelunking into a ‘90s web design time capsule. First, you’ll need the IP address of your router’s interface. Run the following command on your Pi or laptop:
ip route | grep default
The IP that appears after “default via” typically something like 192.168.1.1
or 192.168.0.1
—is your router’s address. Paste that into your browser’s address bar, hit enter, and behold: the login screen to the gateway of network power. If you’ve never logged in before, the credentials are probably still set to the defaults. These are usually printed on a sticker somewhere on the underside of the router, along with the Wi-Fi password you never bothered to change.
Bring up the interface using:
wg-quick up wg0
Congratulations—you’ve now got a WireGuard VPN server listening for connections like a digital bouncer at the club of your home network.
Set Up Your VPN Client (Desktop or Mobile)
For the client configuration, you can set up either on the server itself and directly on the client. What we ultimately need is that .conf file that we generate. If you are carrying this out on the server platform itself, my advice is that you create a folder somewhere else apart from root, and generate the config over there.
We repeat the key generation process:
wg genkey | tee client_private | wg pubkey > client_public
Next, create a client.conf (or any other name with .conf extension) and fill it up with the following:
[Interface]
PrivateKey = <client-private-key>
Address = <client-ip-address>/<subnet>
SaveConfig = true
[Peer]
PublicKey = <server-public-key>
Endpoint = <server-public-ip-address>:51820
AllowedIPs = 0.0.0.0/0
In the [Interface]
section of your client configuration file, paste the client’s private key into the PrivateKey
field. Then assign an IP address to this client that’s within the same subnet as your VPN server. For example, if your server is using 10.0.1.1/8
, you might give this client 10.0.1.2/8
. Just make sure all devices in your WireGuard network share the same subnet—otherwise they’ll be shouting into the void.
You can set SaveConfig = true
so that any runtime changes made to the interface (like new peers or routing tweaks) are written back into the config file automatically.
Next comes the [Peer]
section, where you tell the client how to reach the server. First, add the server’s public key under PublicKey
. This key acts like the server’s identity badge—your client will only trust traffic from peers with this key.
Then, configure AllowedIPs
. If you want your client to route all of its traffic through the VPN tunnel—including public internet traffic—set this to 0.0.0.0/0
. If you’d rather just allow access to your home network or the server itself (without tunneling everything), set it more selectively—for example, 10.0.1.1/32
if you’re only interested in reaching the VPN server.
Now, the crucial part: Endpoint
. This field tells the client where to find your VPN server on the internet. It should be in the format your.public.ip.or.domain:port
, such as 203.0.113.45:51820
or your-cool-subdomain.duckdns.org:51820
. This is how your client finds its way home, across the vast, chaotic expanse of the internet. If your home IP is dynamic (meaning it changes periodically), use a dynamic DNS service like DuckDNS to keep things stable.
The Endpoint
is essentially the lighthouse for your client. Without it, the client has no idea where to send that beautifully encrypted traffic. You add the public IP address of your server in this section.
(Note: The VPN IP that you entered in the server config is not the public IP. To know the public IP of your server’s device, enter curl -4 ifconfig.me
in the terminal. It will give you the IPv4 address of the device).
Handling a Changing Public IP: Set It and Forget It with DuckDNS
One of the most annoying realities of home internet is that your public IP address is probably dynamic—meaning your ISP can change it whenever they feel like it (usually right after you’ve finally configured everything). If your WireGuard client relies on a static Endpoint
to connect to your server, any change to your home IP will break the connection, and you’ll be left wondering why your beautifully encrypted traffic is going nowhere.
To solve this, we use a free dynamic DNS service like DuckDNS. It gives you a custom subdomain (like myvpn.duckdns.org
) that automatically updates to point to your current IP address. You’ll configure your WireGuard clients to connect to this domain instead of a raw IP, and then use a simple script to keep DuckDNS in sync with your real IP.
Step 1: Create Your DuckDNS Domain
- Go to https://www.duckdns.org.
- Choose a subdomain and add it to your DuckDNS account. (There are chances that the sub domain you want is already taken by another user. Keep trying some unique name and ultimately you’ll hit luck).
- DuckDNS will now give you a long token string. This token is your API key and will be used in the update script (we will use this in a while).
After your domain name is accepted, you will see something like this:

In the Current IP section, add the VPN server’s IP address to the domain name you want to assign to it. You can leave the ipv6 field blank.
Now, we want to update the IP to this domain everytime the IP of our server device changes. For this, we create a shell script.
Step 2: Create the Update Script
Now on your Raspberry Pi or old laptop that’s acting as the VPN server, create a directory for the script:
mkdir -p ~/duckdns
cd ~/duckdns
Create the script:
nano duck.sh
Paste the following content, replacing <your-token>
and <your-subdomain>
accordingly:
#!/bin/bash
echo "Updating DuckDNS..."
curl -s "https://www.duckdns.org/update?domains=<your-subdomain>&token=<your-token>&ip="
Save and exit (CTRL+X
, then Y
and Enter
). Make it executable:
chmod +x duck.sh
Step 3: Automate with cron
You’ll want this script to run periodically—say, every 5 minutes—to keep your DuckDNS record up to date.
Edit the crontab:
crontab -e
Add the following line at the bottom:
*/5 * * * * ~/duckdns/duck.sh >/dev/null 2>&1
This tells cron
to run your script every 5 minutes and discard the output (so your logs don’t fill with meaningless confirmations).
To confirm it’s working, you can either tail the system log or visit your DuckDNS dashboard and check the timestamp of the last update.
Back to the config file of the client, enter the domain with the listening port in the Endpoint
section. Ultimately it should look like this:
[Interface]
PrivateKey = <client-private-key>
Address = <client-ip-address>/<subnet>
SaveConfig = true
[Peer]
PublicKey = <server-public-key>
Endpoint = your-subdomain.duckdns.org:51820
AllowedIPs = 0.0.0.0/0
Now your WireGuard clients can always connect to your-subdomain.duckdns.org:51820
, regardless of how often your ISP changes your IP. No more scrambling to check “what’s my IP” before heading out. No more remote access issues. Just a smooth, auto-updating tunnel into your digital lair.
Once you have created the client config file, move it to the /etc/wireguard/ folder in your client’s laptop/raspberry pi. Start the VPN on the client:
wg-quick up wg0
The command above looks for the name of the config file in the /etc/wireguard/ folder. So if your client config is named something else like client_config.conf, replace the wg0 in the command above accordingly (like sudo wg-quick up client_config).
Back to Server
Now we want to inform the server about the existing client. Hence, start the VPN interface on the server:
# Server side
wg-quick up wg0
and execute the following command in the terminal:
wg set wg0 peer <client-public-key> allowed-ips <client-ip-address>/32
Et Voila! You just created a VPN server which you can access from anywhere else. To check if the server and the client have established communication, enter the command wg
in the terminal on the serer side. It should show some information along with the field of latest handshake.

Similarly, on the client side, you can enter the same wg
command. If you see that packets are received in the transfer
field, it means commuication is successfully established.

But, if it doesn’t work, here are some troubleshooting steps.
Troubleshooting: When Your VPN Is More “V” Than “PN”
If things aren’t working and you’re spiraling into terminal window purgatory, it might actually be easier (and faster) to just hit me up on Instagram instead of trying to brute-force your way through it. I’ve already made the mistakes so you don’t have to.
But hey, if you’re feeling brave—or just stubborn—here’s how to troubleshoot it yourself like a true home-networking gladiator.
So you followed all the steps, sacrificed some free time and maybe a bit of your soul—and yet, your WireGuard tunnel refuses to handshake like two polite packets in the night. Don’t worry. Here’s your mental checklist before you start blaming your router, your cat, or society.
Check Your Public Keys Like Your Life Depends on It
WireGuard is ruthless in its minimalism—and that includes its tolerance for incorrect keys. If the connection’s not working, the first thing to check is the public key mismatch between client and server. Each peer config must have the other peer’s public key. If you accidentally pasted the private key instead of the public one, congratulations: you’ve created a secure tunnel to nowhere.
Double-check your wg show
output on both client and server. If there’s no handshake, keys are probably to blame.
Make Sure Your Subnet Isn’t a Dumpster Fire
Your server and client should be on the same subnet. If your server is using 10.0.1.1/8
, your client can use something like 10.0.1.2/8
. But if you set your server to 10.0.0.1/24
and your client to 192.168.0.2/16
, you’ve built a VPN that won’t even talk to itself.
Stick to the same subnet—preferably a /24
or /8
unless you know what you’re doing (and let’s be honest, none of us really do).
Disable Your Firewall (Temporarily, Chill)
If you’re running ufw
, firewalld
, iptables
, or have a router that thinks it’s Fort Knox, make sure UDP port 51820 (or whatever port you picked) is open and forwarded to the VPN server.
You can try temporarily disabling the firewall just to test:
sudo ufw disable
If none of these help and you’re contemplating violence, take a breath, grab a coffee, and walk through the configs one more time. 99% of WireGuard issues are caused by copy-paste errors, firewall blocks, or forgetting to sudo systemctl restart wg-quick@wg0
.
And if it still doesn’t work—well, maybe that’s the universe telling you to go outside for once. (Just kidding. You’ll fix it. You always do.)
But, nothing is fun until we test it, right?
Proof of Life — Flask It Up
Let’s test this in a way that doesn’t involve just pinging. Here’s a mini Flask app running on your VPN server:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return "VPN is alive, and so are you."
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Save as app.py
, then:
python3 app.py
On the client, connect to some other Wi-Fi—or go full rogue and start a hotspot from your phone using mobile data. The point is to be outside your home network, where your VPN setup actually matters. Then, open up any browser on the client and punch in your VPN server’s IP with port 5000. It’ll look something like http://10.0.1.1:5000
(replace that with whatever VPN IP you actually set for your server, obviously). If all went well, your browser should proudly display:
“VPN is alive, and so are you.” At that point, you’re not just testing a Flask app—you’re emotionally validating yourself through HTTP.
If everything’s working and you’re now the proud owner of a homebrewed VPN tunnel—congrats. You’ve officially unlocked remote access wizard status. Feel free to flex your setup and tag me on Instagram if you end up doing something cool with it. And hey, leave a follow while you’re there—because nothing says “thanks for the free tech support” like a tiny dopamine boost in my notifications.
What’s Next: Because One Project Wasn’t Enough
Now that you’ve turned your dusty Raspberry Pi or forgotten laptop into a full-blown VPN gateway, you might be asking yourself: What other practical and useful things can I build with this newly secured home network?
How about building your own bike tracking system—because nothing screams “DIY overkill” like using Python and a GPS module to publish real-time coordinates of your bike on the internet. I’ve already gone down that rabbit hole for you, and yes—it works. You can check out the full tutorial here. It shows how to get location via a GNSS module. All you need to do is create a small Flask app that shows that text data (I know you are opening ChatGPT in the next tab).
Not only can you create a live GPS tracker that feeds your bike’s location into a web app, but with your shiny new VPN setup, you can access that tracker remotely and securely, without exposing your home IP to the world.