How-To: A Private WireGuard VPN with Selective Tor Routing
When planning a VPN, OpenVPN is often the default choice. However, in this post, I’ll document the process of building a private VPN using WireGuard to unite different devices (servers, a notebook, and a phone) into a single, secure network. Additionally, I will selectively route all internet traffic from my notebook and phone through the Tor network, while allowing other servers on the VPN to communicate normally without being routed through Tor.
# The WireGuard Server (Ubuntu)
This central server will act as the WireGuard endpoint, terminating connections from all clients and routing traffic.
# System Optimization (sysctl)
First, it’s a good practice to optimize the server’s network settings for better
performance. Many of these settings are from my previous article about
VPNs
(here);
add the following to /etc/sysctl.conf
:
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=hybla
fs.file-max=51200
net.core.netdev_max_backlog=250000
net.core.rmem_max=67108864
net.core.somaxconn=4096
net.core.wmem_max=67108864
net.ipv4.ip_forward=1
net.ipv4.ip_local_port_range=10000 65000
net.ipv4.tcp_fastopen=3
net.ipv4.tcp_fin_timeout=30
net.ipv4.tcp_keepalive_time=1200
net.ipv4.tcp_max_syn_backlog=8192
net.ipv4.tcp_max_tw_buckets=5000
net.ipv4.tcp_mem=25600 51200 102400
net.ipv4.tcp_mtu_probing=1
net.ipv4.tcp_rmem=4096 87380 67108864
net.ipv4.tcp_syncookies=1
net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_wmem=4096 65536 67108864
At a minimum, you will need net.ipv4.ip_forward=1
to allow the server to route
traffic. The other settings are performance optimizations. Apply them with
sudo sysctl -p
.
# WireGuard Installation
sudo apt install wireguard wireguard-tools
mkdir -p /etc/wireguard/ && cd /etc/wireguard/
wg genkey | tee server-privatekey | wg pubkey > server-publickey
Now, create the server’s configuration file at
/etc/wireguard/wg0.conf
:
[Interface]
Address = 192.168.42.1/24
ListenPort = 51820
PrivateKey = <paste content of server-privatekey here>
Start the WireGuard service and enable it to launch on boot
sudo systemctl start wg-quick@wg0 && sudo systemctl enable wg-quick@wg0
.
You can check its status with sudo journalctl -eu wg-quick@wg0.service
.
# Generating Peer Configurations
With the server running, let’s generate keys for our clients and add them as peers:
wg genkey | tee client1-privatekey | wg pubkey > client1-publickey
wg genkey | tee raspberrypi-privatekey | wg pubkey > raspberrypi-publickey
wg genkey | tee myphone-privatekey | wg pubkey > myphone-publickey
wg genkey | tee mypc-privatekey | wg pubkey > mypc-publickey
# Integrating Tor and Firewall Rules
My Tor installation was already described
here. The crucial step is to
ensure Tor’s listeners are bound to the WireGuard interface IP,
/etc/tor/torrc
:
AutomapHostsOnResolve 1
AutomapHostsSuffixes .onion,.exit
AvoidDiskWrites 1
DNSPort 192.168.42.1:9053
TransPort 192.168.42.1:9040
Now, we will update /etc/wireguard/wg0.conf
with robust iptables rules to
route client traffic to Tor and secure the server:
[Interface]
Address = 192.168.42.1/24
ListenPort = 51820
PrivateKey = <paste content of server-privatekey here>
PostUp = iptables -t nat -A PREROUTING -i %i -p tcp --syn -j REDIRECT --to-ports 9040
PostUp = iptables -t nat -A PREROUTING -i %i -p udp --dport 53 -j REDIRECT --to-ports 9053
PostUp = iptables -A INPUT -i %i -p tcp --dport 9040 -j ACCEPT
PostUp = iptables -A INPUT -i %i -p udp --dport 9053 -j ACCEPT
PostUp = iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o ens3 -j MASQUERADE
PostDown = iptables -D FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostDown = iptables -D INPUT -i %i -p udp --dport 9053 -j ACCEPT
PostDown = iptables -D INPUT -i %i -p tcp --dport 9040 -j ACCEPT
PostDown = iptables -t nat -D PREROUTING -i %i -p udp --dport 53 -j REDIRECT --to-ports 9053
PostDown = iptables -t nat -D PREROUTING -i %i -p tcp --syn -j REDIRECT --to-ports 9040
[Peer]
PublicKey = <paste content of client1-publickey>
AllowedIPs = 192.168.42.2/32
[Peer]
PublicKey = <paste content of raspberrypi-publickey>
AllowedIPs = 192.168.42.3/32
[Peer]
PublicKey = <paste content of myphone-publickey>
AllowedIPs = 192.168.42.4/32
[Peer]
PublicKey = <paste content of mypc-publickey>
AllowedIPs = 192.168.42.5/32
Note: Replace ens3 with your server’s public-facing network interface (e.g., eth0).
Finally, apply all the changes by restarting the services
sudo systemctl restart tor && sudo systemctl restart wg-quick@wg0
.
# Part 2: Client Configurations
# Client Type A: Split-Tunnel (Ubuntu/Raspberry Pi)
These clients will only use the VPN to access other devices on the 192.168.42.0/24 network. Their regular internet traffic will not go through the VPN.
Install WireGuard, sudo apt install wireguard wireguard-tools
, create
/etc/wireguard/wg0.conf
:
[Interface]
PrivateKey = <paste private key for this specific client>
Address = 192.168.42.3/24
[Peer]
PublicKey = <paste content of server-publickey>
Endpoint = your.server.public.ip:51820
AllowedIPs = 192.168.42.0/24
PersistentKeepalive = 25
The AllowedIPs = 192.168.42.0/24 setting is the key here. It tells the client to only route traffic for the VPN subnet through the tunnel. PersistentKeepalive helps maintain the connection through NAT firewalls.
Don’t forget to start and enable the service:
sudo systemctl start wg-quick@wg0 && sudo systemctl enable wg-quick@wg0
.
# Client Type B: Full-Tunnel over Tor (Windows/GrapheneOS)
These clients will route all their internet traffic through the VPN, which then gets funneled through the Tor network by the server.
Create the configuration file and import it into your WireGuard app:
[Interface]
PrivateKey = <paste private key for mypc or myphone>
Address = 192.168.42.5/24
DNS = 192.168.42.1
[Peer]
PublicKey = <paste content of server-publickey>
Endpoint = your.server.public.ip:51820
AllowedIPs = 0.0.0.0/0
AllowedIPs = 0.0.0.0/0, this is the standard “full-tunnel” setting that directs all IPv4 traffic through the VPN. DNS = 192.168.42.1 is critical. It forces the client to use our server for all DNS requests, which are then resolved securely by Tor. This prevents “DNS leaks” where requests might bypass the VPN.
# Note for Windows Users
While AllowedIPs = 0.0.0.0/0 is the most secure setting, some Windows users prefer AllowedIPs = 0.0.0.0/1, 128.0.0.0/1 to avoid certain issues with local network access. If you use this alternative, setting the DNS directive as shown above is absolutely essential to prevent DNS leaks.
# Conclusion
To verify everything is working as expected, you can perform two checks:
Check Internal VPN Connectivity, from one client (e.g., your PC at 192.168.42.5), ping another client (e.g., your phone at 192.168.42.4):
ping 192.168.42.4 PING 192.168.42.4 (192.168.42.4) 56(84) bytes of data. 64 bytes from 192.168.42.4: icmp_seq=1 ttl=64 time=40.6 ms ...
A successful reply means your private network is up!
Check Tor Routing on a Full-Tunnel Client, on your PC or phone, open a terminal or browser and check your public IP:
curl https://wtfismyip.com/json
The output should show an IP address belonging to a Tor exit node:
{ "YourFuckingIPAddress": "185.34.33.2", "YourFuckingLocation": "France", "YourFuckingHostname": "tor.laquadrature.net", "YourFuckingISP": "Octopuce s.a.r.l.", "YourFuckingTorExit": true, "YourFuckingCity": "", "YourFuckingCountry": "France", "YourFuckingCountryCode": "FR" }
If
YourFuckingTorExit
istrue
, your setup is a success.
Once you have securely transferred all private keys to their respective client
devices, be sure to remove them from the server,
rm -rf client1* mypc-* myphone-* raspberrypi-* server-*
.