OpenVPN: Per-Client Path MTU Configuration for Problematic Networks


1 views

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