How to Set Up a Smart VPN with WireGuard: Step-by-Step Guide

Trickster VPN: Understanding WireGuard and Building Your Own Smart VPN

It’s a common situation: more and more websites and services are only accessible via VPN, but at the same time, many Russian companies are blocking access from abroad. As a result, you end up constantly toggling your VPN on and off, which is exhausting. In this guide, I’ll show you how to use routing magic and WireGuard to solve this problem and create a “smart” VPN that you don’t have to keep turning off.

If you use a VPN, you’ve probably run into blocks on foreign traffic. For example, sites like pochta.ru, leroymerlin.ru, rt.ru, and avito.ru might not open. It’s a real meme-worthy situation.

Everyone deals with this in their own way. For example, on Apple devices, you can use built-in automation to launch the VPN when you open certain apps (like Twitter) and turn it off when you close them. But that’s a clunky workaround, and it would be nice to do things more elegantly—and level up your networking skills at the same time. So let’s try “turning on just a little bit of VPN.”

Note: In this usage, it’s more like a proxy than a VPN, but since any method of bypassing blocks is now called a VPN, let’s just go with it.

We’ll also improve the quality of connections to local resources: having to route traffic first to a VPN outside the country and then back to a server inside dramatically increases latency, if not always reducing speed. Even on wired internet, a 4ms ping to Yandex can easily turn into 190ms, and on mobile internet, 80ms can become 240ms. An extra hop inside the country will make things a bit worse, but not nearly as dramatically.

We’ll be using WireGuard—a relatively new VPN technology (developed since 2016, compared to OpenVPN and IPsec, which are much older). It was essentially created by one person—Jason Donenfeld (aka zx2c4). The advantages of WireGuard are speed (especially on Linux, where it can run as a kernel module since Kernel 5.6, and on Windows, where a kernel module was released recently), low latency, modern cryptography, and simple setup and use for end users.

Another plus: UDP. UDP is great for tunnels because TCP already has mechanisms to handle imperfect connections, while UDP is designed for just that. If you encapsulate TCP in TCP, you lose most of those mechanisms and add unnecessary overhead. Encapsulating UDP in TCP is even worse, as it breaks assumptions made by apps like Skype, which would rather drop a few packets than increase latency.

For individual users, encryption is easy: you don’t need certificates, CAs, or logins/passwords—just exchange public keys with your peer. For large companies, this can be a downside, as can the fact that WireGuard is just the basic part of a full VPN infrastructure. Still, WireGuard is used by services like Cloudflare’s WARP (with their own implementation, boringtun).

One downside: WireGuard traffic isn’t obfuscated, so Deep Packet Inspection (DPI) can detect and block it (and blocking UDP entirely, while it doesn’t break the web, will break WireGuard). To hide traffic, use specialized tools like Cloak, Obfsproxy, Shadowsocks, Stunnel, SoftEther, SSTP, or even just SSH. Some of these can work with WireGuard, others can replace it for steganography purposes—WireGuard was designed for speed and cryptographic security.

How WireGuard Keys Work (Simplified)

You have a private key, from which you can generate a public key. You can’t get the private key from the public one. You encrypt a string with your private key, and your peer can decrypt it with your public key, proving you have the private key. This way, you can safely share your public key—it only allows verification, not impersonation. This is similar to SSH: the public key sits on the server, and if it’s stolen, the worst that can happen is someone puts it on their own server so you can connect to them.

In WireGuard, during the initial handshake, each side proves its identity using its private key, verified by the other’s public key. Then, using these keys and some math, they create symmetric keys for encrypting the actual traffic. Thanks to asymmetric encryption, you can safely exchange a symmetric key, which is less resource-intensive for ongoing encryption. The Diffie-Hellman protocol (specifically ECDH on elliptic curves) is used for this key exchange. Every two minutes, a new handshake occurs and session keys are rotated.

For more details, check out the WireGuard protocol description.

Step 1: Set Up and Configure Two Servers

One server will be inside the country (for local resources), and the other will be abroad. We’ll call them local and external.

Ideally, your local server is on your home network, so traffic to external resources looks like your normal home traffic. For this, you need a host at home, a public IP, and the ability to forward a port. I use a virtual machine on my home server, but a Raspberry Pi or similar single-board computer should work too.

Note: I haven’t tested the single-board computer option, and I’m not sure it will work. A Raspberry Pi would have to route all device traffic and hold about 11,000 routes in memory, which might be too much.

If you don’t have a host at home, you can use any VDS server from a hosting provider. Unfortunately, some sites block hosting provider subnets to avoid bots, so this is less ideal. The external server can be rented from a hoster—RU VDS and VDSina have foreign locations, or you can use a foreign hoster if you can pay for it (I use Icelandic 1984.hosting, made for privacy enthusiasts).

Assume both servers run Debian 11. Install the necessary packages:

apt update && apt install -y wireguard iptables ipcalc qrencode curl jq traceroute dnsutils ufw

Enable IP forwarding:

echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
echo "net.ipv4.conf.all.forwarding=1" >> /etc/sysctl.conf
sysctl -p /etc/sysctl.conf

Optionally, change the hostname on both servers for clarity:

hostnamectl set-hostname trickster-internal
hostnamectl set-hostname trickster-external

Step 2: Configure WireGuard to Connect the Two Servers

First, generate keys. Run wg genkey twice to get two private keys:

kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=
6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=

Create two config files. On internal:

/etc/wireguard/wg-internal.conf

[Interface]
Address = 10.20.30.1/32
ListenPort = 17968
PrivateKey = kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=
PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
PostUp = ip rule add from `ip addr show $(ip route | awk '/default/ { print $5 }') | grep "inet" | grep -v "inet6" | head -n 1 | awk '/inet/ {print $2}' | awk -F/ '{print $1}'` table main
PostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
PostDown = ip rule del from `ip addr show $(ip route | awk '/default/ { print $5 }') | grep "inet" | grep -v "inet6" | head -n 1 | awk '/inet/ {print $2}' | awk -F/ '{print $1}'` table main

On external:

/etc/wireguard/wg-external.conf

[Interface]
Address = 10.20.30.2/32
PrivateKey = 6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=
PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE

Now, generate public keys from the private keys:

echo "kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=" | wg pubkey
MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=

echo "6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=" | wg pubkey
FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=

Add the Peer sections to link the servers:

/etc/wireguard/wg-external.conf

[Peer]
PublicKey = MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
AllowedIPs = 10.20.30.0/24
Endpoint = 195.2.79.13:17968
PersistentKeepalive = 25
/etc/wireguard/wg-internal.conf

[Peer] #external node
PublicKey = FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=
AllowedIPs = 10.20.30.2/32, 0.0.0.0/0

Now bring up the tunnels on both servers:

wg-quick down wg-external ; wg-quick up wg-external
wg-quick down wg-internal ; wg-quick up wg-internal

Check tunnel status with wg:

peer: FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=
  endpoint: 51.159.187.77:36276
  allowed ips: 10.20.30.2/32, 0.0.0.0/0
  latest handshake: 13 seconds ago
  transfer: 180 B received, 92 B sent

If you see “latest handshake: … seconds ago” and bytes in both received and sent, everything is working. If not, check your configs and connectivity.

Enable autostart for the tunnels:

systemctl enable [email protected]
systemctl enable [email protected]

Test your route with mtr (install if needed):

mtr -r google.com

With the tunnel up, your traffic should go through the external server first.

Step 3: Add a Client Configuration

Create a config for your client device (e.g., your laptop). Generate a key pair:

prk=`wg genkey` && pbk=`echo $prk | wg pubkey` && printf "Private: $prk\nPublic: $pbk\n"

Example output:

Private: iPK7hYSU8TLVRD+w13nd3aGSYNLfnTx6zwdRzKcGb1o=
Public: 26Vhud00ag/bdB9molvSxfBzZTlzdO+aZgrX3ZDncSg=

Client config (on your device):

/etc/wireguard/wg-notebook-client.conf

[Interface]
Address = 10.20.30.3/32
PrivateKey = iPK7hYSU8TLVRD+w13nd3aGSYNLfnTx6zwdRzKcGb1o=
DNS = 1.1.1.1, 8.8.8.8

[Peer] #internal node
PublicKey = MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
AllowedIPs = 0.0.0.0/0
Endpoint = 195.2.79.13:17968
PersistentKeepalive = 25

Add a Peer section for the client on the internal server:

[Peer] #notebook-client node
PublicKey = 26Vhud00ag/bdB9molvSxfBzZTlzdO+aZgrX3ZDncSg=
AllowedIPs = 10.20.30.3/32

Restart the tunnel on internal (wg-quick down/up), connect from your client, and you should see a handshake and data transfer. Check your IP (e.g., on reg.ru): you should see the external node’s IP and country.

To add more clients, repeat the process. For mobile devices, it’s convenient to show a QR code. Create the config file (with new keys and IP), then generate a QR code in the console:

qrencode -t ansiutf8 < wg-moblie-client.conf

Scan the QR code with your phone, or transfer the config file as needed.

That’s it! You’ve just set up a two-hop VPN. Time to celebrate—open Sbermarket and order a beer…

Oh right, that’s the very problem we set out to solve! Let’s finish the job in the next part.


Leave a Reply