OpenVPN DDoS Protection
Protect your OpenVPN UDP server from DDoS attacks using iptables BPF packet filtering, allow only legitimate handshakes, drop everything else
This guide implements a whitelist-based firewall for OpenVPN using Berkeley Packet Filter (BPF) rules inside iptables. Instead of trying to block attack IPs (impossible with a distributed DDoS), you drop all UDP traffic on the OpenVPN port except packets that match the exact byte signature of a valid OpenVPN handshake.
Credits: technique based on Courvix Network's OpenVPN DDoS Protection.
This method is configuration-specific. The BPF byte offsets depend on your OpenVPN settings (cipher, tls-crypt, tls-auth, protocol version). If you change your OpenVPN config, you must re-derive and update the rules. The values in this guide work for a standard OpenVPN setup with tls-crypt.
How it works
OpenVPN's initial handshake packet (P_CONTROL_HARD_RESET_CLIENT_V2) has a predictable structure. By inspecting specific byte offsets in the UDP payload, we can tell apart a legitimate client from DDoS junk:
| Offset (from UDP payload) | Value | Meaning |
|---|---|---|
| Byte 0 (UDP+8) | 0x38 | OpenVPN control packet, hard reset |
| Bytes 9-12 | 0x00000001 | Message sequence ID = 1 (first packet) |
| Bytes 13-16 | 0x00000000 | Packet ID = 0 |
The iptables BPF rule checks these values and drops anything that doesn't match, before OpenVPN even sees the packet.
Requirements
- OpenVPN server already running on UDP (default port
1194) iptablesinstallediptables-persistentfor persistence across reboots
sudo apt install iptables-persistent -yStep 1: Default deny on OpenVPN port
Drop all new incoming UDP connections on port 1194:
sudo iptables -I INPUT -p udp --dport 1194 -m state --state NEW -j DROPThis blocks everything by default. Established sessions (already authenticated clients) are handled by the rule in Step 3.
Step 2: Allow established sessions
Existing VPN sessions must continue to work:
sudo iptables -I INPUT -p udp --dport 1194 -m state --state ESTABLISHED,RELATED -j ACCEPTStep 3: Allow only valid OpenVPN handshakes (BPF rule)
This is the core rule. It uses BPF bytecode to inspect the raw UDP payload and only accepts packets that match the OpenVPN handshake signature:
sudo iptables -I INPUT -p udp --dport 1194 -m state --state NEW \
-m bpf --bytecode "14,0 0 0 0,48 0 0 8,21 0 10 56,48 0 0 17,21 0 8 0,48 0 0 21,21 0 6 0,48 0 0 22,21 0 4 0,48 0 0 23,21 0 2 0,48 0 0 24,21 0 0 1,6 0 0 65535,6 0 0 0" \
-j ACCEPTWhat this BPF program does:
- Checks byte at UDP offset 8 =
0x38(OpenVPN hard reset control packet) - Checks bytes at offsets 17-24 match expected message/packet ID values
- If all checks pass → ACCEPT
- Otherwise → falls through to the DROP rule from Step 1
Step 4: Verify rule order
The rules must be in this order (check with sudo iptables -L INPUT -n --line-numbers):
num target prot opt source destination
1 ACCEPT udp -- anywhere anywhere udp dpt:1194 state NEW bpf ...
2 ACCEPT udp -- anywhere anywhere udp dpt:1194 state ESTABLISHED,RELATED
3 DROP udp -- anywhere anywhere udp dpt:1194 state NEWIf the order is wrong, reinsert with specific line numbers:
sudo iptables -I INPUT 1 -p udp --dport 1194 -m state --state NEW \
-m bpf --bytecode "..." -j ACCEPTStep 5: Save rules
sudo netfilter-persistent saveRules will persist across reboots.
Verify it works
From a client machine, try to connect normally, it should work. Test that DDoS-like traffic is dropped:
# Send random UDP junk to port 1194 (from another machine)
echo "AAAAAAAAAAAAAAAA" | nc -u YOUR_SERVER_IP 1194This should be silently dropped. A legitimate OpenVPN client connection should still succeed.
Monitor drops in real time:
sudo watch -n1 'iptables -L INPUT -n -v | grep 1194'The DROP rule's packet counter will increase under attack while legitimate clients connect normally.
Adapt for a different port
If your OpenVPN runs on a non-default port, replace 1194 with your port in all three rules:
# Example for port 443
sudo iptables -I INPUT -p udp --dport 443 -m state --state NEW -j DROP
sudo iptables -I INPUT -p udp --dport 443 -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo iptables -I INPUT -p udp --dport 443 -m state --state NEW \
-m bpf --bytecode "14,0 0 0 0,48 0 0 8,21 0 10 56,48 0 0 17,21 0 8 0,48 0 0 21,21 0 6 0,48 0 0 22,21 0 4 0,48 0 0 23,21 0 2 0,48 0 0 24,21 0 0 1,6 0 0 65535,6 0 0 0" \
-j ACCEPTLimitations
- Does not help against bandwidth saturation: if the attack fills your uplink before packets reach iptables, you need upstream protection (null-route or DDoS scrubbing from your provider).
- Config changes break the filter: if you change
tls-crypttotls-auth, or modify the cipher, the byte offsets in the handshake packet will shift. You'll need to re-capture a handshake and re-derive the BPF program. - BPF module required: the
xt_bpfkernel module must be available. On modern Ubuntu/Debian kernels it is loaded automatically.
Check if the module is available:
lsmod | grep xt_bpf
# or load it manually
sudo modprobe xt_bpfRemove the rules
To remove all three rules:
sudo iptables -D INPUT -p udp --dport 1194 -m state --state NEW -j DROP
sudo iptables -D INPUT -p udp --dport 1194 -m state --state ESTABLISHED,RELATED -j ACCEPT
# For the BPF rule, find its line number and delete by number
sudo iptables -L INPUT -n --line-numbers | grep bpf
sudo iptables -D INPUT <line-number>
sudo netfilter-persistent save