WireGuard VPN: Client Profiling, Advanced Routing, and Firewall Hardening (Part 2)
Master WireGuard VPN configuration. Configure split-tunnel routing, set up custom firewall NAT rules, and secure client profile management.
Manual Generation of Client Profiles and Cryptographic Keys
To maintain complete control over client configurations and credentials, avoid automated scripts that abstract key generation. Client profiles are created using the standard wg utility.
Step 1: Generate Client Cryptographic Keypairs
Each client requires a private key, a public key, and a pre-shared key (PSK) to provide post-quantum resistance against future decryption attempts. Run the following command block on a secure terminal to generate these keys:
# Generate the client private key
wg genkey > client.key
# Derive the client public key from the private key
wg pubkey < client.key > client.pub
# Generate a pre-shared key for an additional cryptographic layer
wg genpsk > client.psk
Step 2: Formulate the Client Configuration (wg0-client.conf)
Construct the client configuration file manually. The client configuration maps the client-side details in the [Interface] section and the server-side details in the [Peer] section.
[Interface]
# The static internal IP address assigned to this client within the VPN subnet
PrivateKey = <CLIENT_PRIVATE_KEY>
Address = 10.8.0.2/32, fd42:42:42::2/128
# Local DNS server to prevent leaks
DNS = 10.8.0.1
[Peer]
# The server's public key
PublicKey = <SERVER_PUBLIC_KEY>
# Pre-shared key generated in Step 1
PresharedKey = <PRE_SHARED_KEY>
# Server endpoint IP/domain and port
Endpoint = vps-public-ip:51820
# AllowedIPs determines which traffic is routed through the tunnel
AllowedIPs = 10.8.0.0/24, 192.168.1.0/24
# Keep-alive packets to keep stateful firewalls/NAT open
PersistentKeepalive = 25
Step 3: Register the Client Peer on the WireGuard Server
Append the client's public key and assigned IP address to the server's configuration file (/etc/wireguard/wg0.conf).
# Append this peer block to /etc/wireguard/wg0.conf
[Peer]
PublicKey = <CLIENT_PUBLIC_KEY>
PresharedKey = <PRE_SHARED_KEY>
AllowedIPs = 10.8.0.2/32, fd42:42:42::2/128
Apply the new configuration without disrupting active connections:
wg syncconf wg0 <(wg-quick strip wg0)
Configuring Split-Tunnel Routing vs. Full Tunneling
WireGuard relies on the AllowedIPs directive to manage routing. When the interface is brought up via wg-quick, it translates these values into system routing table entries automatically.
Full-Tunnel Configuration (Route All Traffic)
To route all IPv4 and IPv6 traffic through the VPS, set AllowedIPs on the client configuration to match the entire IP address space:
# Inside wg0-client.conf [Peer] section
AllowedIPs = 0.0.0.0/0, ::/0
When wg-quick processes 0.0.0.0/0, it does not simply replace the default gateway. Instead, it adds routing rules to target a custom routing table using policy routing. This prevents routing loops where WireGuard packets attempt to route through the WireGuard interface itself.
# Verify the routing rules generated by wg-quick
ip rule show
Split-Tunnel Configuration (Route Specific Networks)
To route only internal services and local subnets while sending general internet traffic through the client's local ISP gateway, restrict AllowedIPs to the target subnets:
# Route only VPN subnet and local corporate LAN
AllowedIPs = 10.8.0.0/24, 10.100.0.0/16
This updates the client's local routing table by adding specific route entries pointing to the wg0 interface without touching the default gateway.
Disabling Automated Routing (Policy-Based Custom Routing)
If you require custom routing logic, disable wg-quick's automated routing behavior by adding Table = off in the [Interface] section of the client configuration:
[Interface]
PrivateKey = <CLIENT_PRIVATE_KEY>
Address = 10.8.0.2/32
Table = off
You can then write custom PostUp and PostDown scripts to configure exactly which traffic goes where:
PostUp = ip route add 10.200.0.0/24 dev wg0 table 100 && ip rule add from 10.8.0.2 table 100
PostDown = ip route del 10.200.0.0/24 dev wg0 table 100 && ip rule del from 10.8.0.2 table 100
VPS Firewall Hardening & Secure Forwarding (iptables)
A secure VPS endpoint must drop unauthorized traffic by default, isolate VPN clients, prevent IP spoofing, and implement tight forwarding rules.
Step 1: Drop-by-Default Policy
Configure the default netfilter policies to drop all incoming and forwarding packets. Enable only SSH, the WireGuard port, and established connection states.
# Set default policies to DROP
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow loopback interface traffic
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# Allow established and related connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow SSH on custom port (e.g., 22 or 2222)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Allow WireGuard UDP port
iptables -A INPUT -p udp --dport 51820 -j ACCEPT
Step 2: Strict Forwarding and NAT Rules
Avoid wildcard MASQUERADE rules. Instead, lock down the forwarding path to ensure that only traffic from the WireGuard interface (wg0) destined for the public interface (eth0) is forwarded, and only if it originates from the authorized WireGuard subnet.
# Allow forwarding of packets originating from the WireGuard interface (wg0) to the public interface (eth0)
iptables -A FORWARD -i wg0 -o eth0 -s 10.8.0.0/24 -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
# Allow forwarding of established/related packets back from the public interface (eth0) to WireGuard (wg0)
iptables -A FORWARD -i eth0 -o wg0 -d 10.8.0.0/24 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Implement Source NAT (SNAT) strictly for the WireGuard interface
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
Step 3: Anti-Spoofing and Drop Rules
Prevent client IP spoofing by discarding packets from the WireGuard interface whose source addresses do not match the assigned WireGuard subnet.
# Drop any packets entering wg0 that do not match the VPN subnet
iptables -A INPUT -i wg0 ! -s 10.8.0.0/24 -j DROP
iptables -A FORWARD -i wg0 ! -s 10.8.0.0/24 -j DROP
To persist these rules across reboots, install iptables-persistent and save the configuration:
netfilter-persistent save
Preventing DNS Leaks
A DNS leak occurs when DNS queries are sent outside the encrypted WireGuard tunnel to the local ISP's DNS servers instead of the designated secure resolvers inside or at the end of the tunnel.
Step 1: Force Client-Side DNS Configuration
Define the secure DNS resolver in the client's [Interface] block. This directs the client operating system to query the specified server for all domain name resolutions.
[Interface]
PrivateKey = <CLIENT_PRIVATE_KEY>
Address = 10.8.0.2/32
# Query the WireGuard gateway DNS server
DNS = 10.8.0.1
If you are using a public DNS service over the tunnel (e.g., Cloudflare 1.1.1.1), make sure it is included in your AllowedIPs list:
AllowedIPs = 10.8.0.0/24, 1.1.1.1/32
Step 2: Systemd-Resolved Integration on Linux
On modern Linux distributions utilizing systemd-resolved, wg-quick uses the helper script resolvconf or systemd-resolve to update global DNS settings. If this fails, DNS requests might fall back to the physical link. Install the openresolv package to ensure correct behavior:
# On Debian/Ubuntu systems
sudo apt update && sudo apt install openresolv
Alternatively, manually configure the WireGuard interface DNS link status using systemd-resolve or resolvectl in the client's PostUp hooks:
PostUp = resolvectl dns %i 10.8.0.1; resolvectl domain %i "~."
PostDown = resolvectl revert %i
The ~. domain parameter acts as a routing-only search domain, instructing systemd-resolved to route all DNS queries through the wg0 interface preferentially.
Step 3: Validate DNS Routing
Verify that no DNS queries escape the tunnel. On the client, check the active DNS resolver interfaces:
resolvectl status wg0
Verify that the DNS Server is set to 10.8.0.1 and Protocols include +DefaultRoute.
Additionally, execute a DNS leak test command to verify the outgoing public IP of your DNS queries matches your WireGuard gateway or the configured VPN upstream DNS server:
curl -s https://www.dnsleaktest.com/ | grep -i "from"
This ensures both traffic and metadata remain encrypted and confined to your WireGuard deployment.