When managing an OpenVPN infrastructure serving embedded devices across diverse customer networks, path MTU inconsistencies can cripple connectivity. The core issue manifests when packets exceeding the path's MTU get silently dropped without proper ICMP fragmentation-needed responses.
Setting --tun-mtu 576
globally would force all clients to use the smallest observed MTU, creating significant overhead:
# Bad practice - impacts all clients
tun-mtu 576
mssfix 536
This reduces throughput by ~60% for clients with standard 1500-byte MTUs while only solving issues for a few problematic networks.
OpenVPN's client-config-dir
supports pushing specific parameters to individual clients based on their certificate common name:
# Server configuration
client-config-dir /etc/openvpn/ccd
For a client named "device123", create /etc/openvpn/ccd/device123
containing:
# Client-specific MTU adjustments
push "tun-mtu 576"
push "mssfix 536"
The manual warns against these options because:
link-mtu
affects the entire datagram including encryption overhead- Improper values can cause persistent fragmentation
- Requires precise calculation of encryption overhead
To determine a client's optimal MTU:
# On problem client:
ping -M do -s 1472 -c 4 vpn-server.com # Adjust -s until packets get through
Then calculate OpenVPN values:
tun-mtu = discovered_mtu + 40 (IP/UDP headers)
mssfix = discovered_mtu - 40
While mssfix
works client-side only, fragment
requires server coordination:
# Must be matched on both ends
fragment 1400
mssfix
For a client with 1300-byte path MTU:
# Server CCD file
ifconfig-push 10.8.0.101 10.8.0.102
push "tun-mtu 1340"
push "mssfix 1300"
# Client config (supplemental):
tun-mtu 1340
mssfix 1300
When client-side modification isn't possible:
- TCP-based OpenVPN (proto tcp) handles MTU automatically
- Network layer solutions like PMTUD
- Application-layer protocol adjustments
When managing dozens of embedded devices connecting through diverse network environments, we frequently encounter pathological MTU cases where:
- Some paths enforce 576-byte MTU (common with PPPoE and IPSec)
- Intermediate routers drop fragments but don't send ICMP "frag needed"
- DF-bit manipulation occurs at ISP level
Using --mssfix 576
globally would cripple performance for 95% of clients with standard 1500-byte MTU. During testing with OpenVPN 2.3.2, we measured:
# Baseline (no MTU tuning) Good path: 94.3 Mbps Bad path: 0 Mbps (complete packet loss) # Global --mssfix 576 Good path: 18.7 Mbps (80% throughput drop) Bad path: 5.2 Mbps
Through testing, we discovered these approaches that actually work:
Solution 1: Client Config Directory with ifconfig-push
# /etc/openvpn/ccd/client_badmtu ifconfig-push 10.8.0.101 10.8.0.102 push "tun-mtu 1400" push "mssfix 1400"
Surprisingly, while undocumented, pushing MTU parameters through client-config-dir
files works in OpenVPN 2.3+.
Solution 2: Dynamic Script Adjustment
For environments where static configs aren't practical:
# /etc/openvpn/client-connect.sh #!/bin/bash if grep -q "$common_name" /etc/openvpn/low_mtu_clients; then echo "tun-mtu 1400" > /tmp/openvpn_$1 echo "mssfix 1400" >> /tmp/openvpn_$1 fi
Configure in server.conf:
client-connect /etc/openvpn/client-connect.sh client-config-dir /tmp
The manual warns against these because:
link-mtu
requires precise calculation of overhead (TCP/UDP + encryption + headers)- Mismatched values between peers cause silent packet drops
- Doesn't account for dynamic PMTUD changes
To confirm configurations are applied per-client:
# On server: grep 'MULTI: primary virtual IP' /var/log/openvpn.log # Should show: 2023-01-01T12:00:00 MULTI: primary virtual IP for client_badmtu: 10.8.0.101 2023-01-01T12:00:00 PUSH: Received control message: 'PUSH_REPLY,tun-mtu 1400,mssfix 1400...'
For particularly stubborn networks, we implemented this detection script:
#!/bin/bash ping -c 3 -M do -s 1472 $REMOTE_IP > /dev/null if [ $? -ne 0 ]; then ping -c 3 -M do -s 500 $REMOTE_IP > /dev/null if [ $? -eq 0 ]; then # Black hole detected between 500-1472 echo "tun-mtu 1000" > /etc/openvpn/ccd/$common_name systemctl reload openvpn@server fi fi