14 KiB
Implementation Plan
Prerequisites
- SSH access to both VDS servers (RU: 176.124.216.197, DE: 194.31.173.178)
- Root or sudo privileges on both servers
- Basic firewall rules allowing SSH access
Phase 1: DE VDS Setup (Exit Node)
The simpler node - just accepts traffic from RU VDS and NATs it to the internet.
Step 1.1: Install packages
apt update && apt install -y wireguard nftables
Step 1.2: Enable IP forwarding
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.d/99-vpn.conf
sysctl -p /etc/sysctl.d/99-vpn.conf
Step 1.3: Generate WireGuard keys
mkdir -p /etc/wireguard/keys
chmod 700 /etc/wireguard/keys
wg genkey | tee /etc/wireguard/keys/server.key | wg pubkey > /etc/wireguard/keys/server.pub
chmod 600 /etc/wireguard/keys/*
Step 1.4: Create WireGuard config
Create /etc/wireguard/wg0.conf:
[Interface]
Address = 10.20.0.2/30
ListenPort = 51821
PrivateKey = <DE_SERVER_PRIVATE_KEY>
PostUp = nft -f /etc/nftables.conf
PostDown = nft flush ruleset
[Peer]
# RU VDS
PublicKey = <RU_SERVER_PUBLIC_KEY>
AllowedIPs = 10.20.0.1/32, 10.10.0.0/24
Step 1.5: Configure nftables
Create /etc/nftables.conf:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Allow established connections
ct state established,related accept
# Allow loopback
iif lo accept
# Allow SSH (adjust port if needed)
tcp dport 22 accept
# Allow WireGuard from RU VDS only
ip saddr 176.124.216.197 udp dport 51821 accept
# Allow ICMP
icmp type echo-request accept
}
chain forward {
type filter hook forward priority 0; policy drop;
# Allow forwarding from VPN
iifname "wg0" accept
# Allow established connections back
ct state established,related accept
}
chain output {
type filter hook output priority 0; policy accept;
}
}
table inet nat {
chain postrouting {
type nat hook postrouting priority 100;
# NAT traffic from VPN to internet
oifname != "wg0" ip saddr { 10.10.0.0/24, 10.20.0.0/30 } masquerade
}
}
Step 1.6: Enable and start services
systemctl enable --now nftables
systemctl enable --now wg-quick@wg0
Step 1.7: Verify
wg show
ip addr show wg0
Phase 2: RU VDS Setup (Gateway)
The main node - handles user connections, DNS-based routing decisions.
Step 2.1: Install packages
apt update && apt install -y wireguard dnsmasq nftables ipset
Step 2.2: Enable IP forwarding
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.d/99-vpn.conf
sysctl -p /etc/sysctl.d/99-vpn.conf
Step 2.3: Generate WireGuard keys
mkdir -p /etc/wireguard/keys
chmod 700 /etc/wireguard/keys
# Server key for user-facing interface
wg genkey | tee /etc/wireguard/keys/server.key | wg pubkey > /etc/wireguard/keys/server.pub
# Key for DE tunnel
wg genkey | tee /etc/wireguard/keys/de-tunnel.key | wg pubkey > /etc/wireguard/keys/de-tunnel.pub
chmod 600 /etc/wireguard/keys/*
Step 2.4: Create routing tables
Add to /etc/iproute2/rt_tables:
200 proxy
Step 2.5: Create WireGuard configs
Create /etc/wireguard/wg0.conf (user-facing):
[Interface]
Address = 10.10.0.1/24
ListenPort = 51820
PrivateKey = <RU_SERVER_PRIVATE_KEY>
PostUp = /etc/wireguard/postup.sh
PostDown = /etc/wireguard/postdown.sh
# Users will be added here as [Peer] sections
Create /etc/wireguard/wg1.conf (DE tunnel):
[Interface]
Address = 10.20.0.1/30
PrivateKey = <RU_DE_TUNNEL_PRIVATE_KEY>
[Peer]
# DE VDS
PublicKey = <DE_SERVER_PUBLIC_KEY>
Endpoint = 194.31.173.178:51821
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Step 2.6: Create PostUp script
Create /etc/wireguard/postup.sh:
#!/bin/bash
# Create ipsets for routing decisions
ipset create direct hash:net -exist
ipset create proxy hash:net -exist
# Add default route via DE tunnel for 'proxy' table
ip route add default via 10.20.0.2 table proxy
# Load nftables rules
nft -f /etc/nftables.conf
Make executable:
chmod +x /etc/wireguard/postup.sh
Step 2.7: Create PostDown script
Create /etc/wireguard/postdown.sh:
#!/bin/bash
# Flush routing table
ip route flush table proxy
# Destroy ipsets
ipset destroy direct 2>/dev/null
ipset destroy proxy 2>/dev/null
# Flush nftables
nft flush ruleset
Make executable:
chmod +x /etc/wireguard/postdown.sh
Step 2.8: Configure nftables
Create /etc/nftables.conf:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Allow established
ct state established,related accept
# Allow loopback
iif lo accept
# Allow SSH
tcp dport 22 accept
# Allow WireGuard from anywhere (user connections)
udp dport 51820 accept
# Allow DNS from VPN clients only
iifname "wg0" udp dport 53 accept
iifname "wg0" tcp dport 53 accept
# Allow ICMP
icmp type echo-request accept
}
chain forward {
type filter hook forward priority 0; policy drop;
# Allow forwarding from user VPN
iifname "wg0" accept
# Allow forwarding from DE tunnel
iifname "wg1" accept
# Allow established
ct state established,related accept
}
chain output {
type filter hook output priority 0; policy accept;
}
}
table inet nat {
chain postrouting {
type nat hook postrouting priority 100;
# NAT direct traffic (going out main interface)
oifname != "wg0" oifname != "wg1" ip saddr 10.10.0.0/24 masquerade
}
}
table inet mangle {
chain prerouting {
type filter hook prerouting priority -150;
# Mark packets destined for 'proxy' ipset
ip daddr @proxy meta mark set 0x1
}
}
Note: nftables native sets will be used instead of ipset. See Step 2.10 for updated approach.
Step 2.9: Configure dnsmasq
Disable systemd-resolved if running:
systemctl disable --now systemd-resolved
rm /etc/resolv.conf
echo "nameserver 8.8.8.8" > /etc/resolv.conf
Create /etc/dnsmasq.d/vpn-routing.conf:
# Listen only on VPN interface
interface=wg0
bind-interfaces
# Upstream DNS
server=8.8.8.8
server=8.8.4.4
# Don't read /etc/resolv.conf
no-resolv
# Cache size
cache-size=10000
# Log queries (optional, disable in production)
# log-queries
# Russian TLDs - route directly (add to 'direct' ipset)
ipset=/ru/direct
ipset=/рф/direct
ipset=/su/direct
# Everything else goes to proxy (default)
# This is handled by routing table, not ipset
Important: dnsmasq's ipset feature requires ipset, not nftables sets. We'll use a hybrid approach.
Step 2.10: Updated routing approach (hybrid ipset + nftables)
Since dnsmasq works with ipset, we'll use ipset for the sets and nftables for the rules.
Update /etc/nftables.conf:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif lo accept
tcp dport 22 accept
udp dport 51820 accept
iifname "wg0" udp dport 53 accept
iifname "wg0" tcp dport 53 accept
icmp type echo-request accept
}
chain forward {
type filter hook forward priority 0; policy drop;
iifname "wg0" accept
iifname "wg1" accept
ct state established,related accept
}
chain output {
type filter hook output priority 0; policy accept;
}
}
table inet nat {
chain postrouting {
type nat hook postrouting priority 100;
# NAT direct traffic going out main interface
oifname != "wg0" oifname != "wg1" ip saddr 10.10.0.0/24 masquerade
}
}
Update /etc/wireguard/postup.sh:
#!/bin/bash
# Create ipsets
ipset create direct hash:net -exist
ipset flush direct
# Add default route via DE tunnel for 'proxy' table
ip route add default via 10.20.0.2 table proxy 2>/dev/null || true
# Policy routing: packets NOT going to 'direct' ipset use 'proxy' table
ip rule add from 10.10.0.0/24 fwmark 0x1 table proxy priority 100 2>/dev/null || true
# Load nftables
nft -f /etc/nftables.conf
# Add iptables rule for fwmark (nftables mangle is complex with ipset)
iptables -t mangle -A PREROUTING -m set ! --match-set direct dst -s 10.10.0.0/24 -j MARK --set-mark 0x1
Update /etc/wireguard/postdown.sh:
#!/bin/bash
ip rule del from 10.10.0.0/24 fwmark 0x1 table proxy priority 100 2>/dev/null || true
ip route flush table proxy 2>/dev/null || true
iptables -t mangle -F PREROUTING 2>/dev/null || true
ipset destroy direct 2>/dev/null || true
nft flush ruleset
Step 2.11: Enable and start services
systemctl enable --now dnsmasq
systemctl enable --now wg-quick@wg0
systemctl enable --now wg-quick@wg1
Step 2.12: Verify
wg show
ip route show table proxy
ipset list direct
Phase 3: Key Exchange
After generating keys on both servers, exchange public keys:
On DE VDS:
cat /etc/wireguard/keys/server.pub
# Copy this value
On RU VDS:
cat /etc/wireguard/keys/server.pub
cat /etc/wireguard/keys/de-tunnel.pub
# Copy these values
Update configs:
- DE VDS
/etc/wireguard/wg0.conf: Replace<RU_SERVER_PUBLIC_KEY>with RU'sde-tunnel.pub - RU VDS
/etc/wireguard/wg1.conf: Replace<DE_SERVER_PUBLIC_KEY>with DE'sserver.pub
Restart WireGuard:
# On DE VDS
systemctl restart wg-quick@wg0
# On RU VDS
systemctl restart wg-quick@wg1
Verify tunnel:
# On RU VDS
ping 10.20.0.2
wg show wg1
Phase 4: Add First Client
Step 4.1: Generate client keys (on RU VDS)
CLIENT_NAME="phone"
wg genkey | tee /etc/wireguard/keys/client_${CLIENT_NAME}.key | wg pubkey > /etc/wireguard/keys/client_${CLIENT_NAME}.pub
chmod 600 /etc/wireguard/keys/client_${CLIENT_NAME}.*
Step 4.2: Add peer to server
CLIENT_IP="10.10.0.2"
CLIENT_PUBKEY=$(cat /etc/wireguard/keys/client_${CLIENT_NAME}.pub)
wg set wg0 peer ${CLIENT_PUBKEY} allowed-ips ${CLIENT_IP}/32
wg-quick save wg0
Step 4.3: Create client config
CLIENT_NAME="phone"
CLIENT_IP="10.10.0.2"
SERVER_PUBKEY=$(cat /etc/wireguard/keys/server.pub)
CLIENT_PRIVKEY=$(cat /etc/wireguard/keys/client_${CLIENT_NAME}.key)
cat > /etc/wireguard/clients/${CLIENT_NAME}.conf << EOF
[Interface]
PrivateKey = ${CLIENT_PRIVKEY}
Address = ${CLIENT_IP}/32
DNS = 10.10.0.1
[Peer]
PublicKey = ${SERVER_PUBKEY}
Endpoint = 176.124.216.197:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
EOF
mkdir -p /etc/wireguard/clients
chmod 600 /etc/wireguard/clients/${CLIENT_NAME}.conf
Step 4.4: Transfer to client
Display as QR code (for mobile):
apt install -y qrencode
qrencode -t ansiutf8 < /etc/wireguard/clients/${CLIENT_NAME}.conf
Or copy the file contents manually.
Phase 5: Testing
Test 1: Basic connectivity
# From client
ping 10.10.0.1 # Should work - RU VDS
ping 10.20.0.2 # Should work - DE VDS
Test 2: DNS resolution
# From client
nslookup google.com 10.10.0.1
nslookup yandex.ru 10.10.0.1
Test 3: Routing verification
# On RU VDS - check ipset after client visits some .ru sites
ipset list direct
# From client - check external IP
curl ifconfig.me # Should show DE VDS IP (194.31.173.178)
curl ifconfig.me --resolve ifconfig.me:80:$(dig +short yandex.ru | head -1) # Won't work, but...
# Better test - check where traffic goes
curl https://2ip.ru # Russian service, should go direct, show RU VDS IP
curl https://ifconfig.me # Should show DE VDS IP
Test 4: Check that .ru domains go direct
# From client
traceroute yandex.ru # Should not go through DE
traceroute google.com # Should go through DE (you'll see 10.20.0.x hop)
Troubleshooting
WireGuard not connecting
# Check if service is running
systemctl status wg-quick@wg0
# Check for errors
journalctl -u wg-quick@wg0 -e
# Verify port is open
ss -ulnp | grep 51820
DNS not working
# Check dnsmasq
systemctl status dnsmasq
journalctl -u dnsmasq -e
# Test locally
dig @127.0.0.1 google.com
Routing not working
# Check ipset
ipset list direct
# Check routing table
ip route show table proxy
ip rule show
# Check marks
iptables -t mangle -L -v
# Test marking
ping -c 1 8.8.8.8
conntrack -L | grep 8.8.8.8
Traffic not NATed
# Check nftables
nft list ruleset
# Check forwarding
cat /proc/sys/net/ipv4/ip_forward
Summary Checklist
-
DE VDS
- WireGuard installed
- IP forwarding enabled
- Keys generated
- wg0.conf configured
- nftables configured
- Services enabled and started
-
RU VDS
- WireGuard installed
- dnsmasq installed
- ipset installed
- IP forwarding enabled
- Keys generated
- Routing table 'proxy' added
- wg0.conf configured (users)
- wg1.conf configured (DE tunnel)
- postup.sh / postdown.sh created
- nftables configured
- dnsmasq configured
- Services enabled and started
-
Key Exchange
- DE public key → RU wg1.conf
- RU de-tunnel public key → DE wg0.conf
- Tunnel verified (ping 10.20.0.2 from RU)
-
First Client
- Keys generated
- Peer added to wg0
- Client config created
- Connection tested
- Routing verified