Security

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)ValueMeaning
Byte 0 (UDP+8)0x38OpenVPN control packet, hard reset
Bytes 9-120x00000001Message sequence ID = 1 (first packet)
Bytes 13-160x00000000Packet 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)
  • iptables installed
  • iptables-persistent for persistence across reboots
sudo apt install iptables-persistent -y

Step 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 DROP

This 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 ACCEPT

Step 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 ACCEPT

What this BPF program does:

  1. Checks byte at UDP offset 8 = 0x38 (OpenVPN hard reset control packet)
  2. Checks bytes at offsets 17-24 match expected message/packet ID values
  3. If all checks pass → ACCEPT
  4. 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 NEW

If 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 ACCEPT

Step 5: Save rules

sudo netfilter-persistent save

Rules 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 1194

This 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 ACCEPT

Limitations

  • 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-crypt to tls-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_bpf kernel 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_bpf

Remove 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

On this page