How to Fix L2TP/IPsec VPN Failing on Linux: Use a Random Source Port

How to Fix L2TP/IPsec VPN Failing on Linux: Use a Random Source Port

I recently spent a day debugging an L2TP/IPsec VPN on Ubuntu that would connect and then time out. It had worked the day before. This post covers how I narrowed it down and the fix that worked.

The symptom

Connecting through NetworkManager (network-manager-l2tp) always ended with:

vpn[...]: failed to connect: 'Timeout was reached'

The strongSwan log showed the client sending IKE packets to the server’s UDP port 500 and retransmitting, with no reply:

charon: sending packet: from 192.168.0.14[500] to <server>[500] (532 bytes)
charon: sending retransmit 1 of request message ID 0

The server was reachable: ping worked and other services answered. So the problem was specific to this traffic.

Step 1: Check whether the server answers IKE at all

I wrote a small Python script that sends an IKEv1 Main Mode packet to UDP 500 and 4500 and reports whether anything comes back. Run it without root, from a random source port:

python3 -I ikeprobe.py <server-ip>

The results were revealing:

  • Before I changed anything on the router: both ports replied from a random source port.
  • The same probe from port 500 (the one NetworkManager uses) got no reply.

So the server was fine. Something in between treated traffic from source port 500 differently.

Step 2: Blame the router, then check it

My router is a cheap TOTOLINK unit. Consumer routers often handle IPsec passthrough (ALG) badly. Turning the “IPsec passthrough” option off made things worse: even random-port probes stopped getting replies. Turning it back on restored replies for random ports, but not for port 500.

I could not fully prove the router is the culprit, since I could not inspect its NAT table. Still, the pattern was clear: the router or ISP was filtering or mishandling the default source port 500.

Step 3: The fix — make strongSwan use a random source port

strongSwan’s charon daemon can be told to use a random local port instead of 500 and 4500. Create a drop-in config:

sudo tee /etc/strongswan.d/zz-random-port.conf <<'EOF'
charon {
    port = 0
    port_nat_t = 0
}
EOF

Setting port = 0 and port_nat_t = 0 tells charon to bind to ephemeral ports. Reconnect the VPN from NetworkManager, and the L2TP tunnel came up immediately.

The file persists across reboots, so you only need to do this once. To undo it, delete the file.

Step 4: Make it a split tunnel

By default, L2TP pushes a full tunnel: all traffic, including normal browsing, goes through the VPN. For a work VPN where I only needed one subnet, I set:

nmcli con mod <vpn-name> ipv4.never-default yes ipv4.routes "10.80.1.0/24"

After reconnecting, ip route get 10.80.1.xx showed the traffic going through ppp0, while curl ifconfig.me still returned my normal public IP. Remmina to the internal host worked as expected.

Step 5: Connect automatically

To avoid reconnecting after every restart:

nmcli con mod <vpn-name> connection.autoconnect yes

You can limit this to specific networks with NetworkManager’s dispatcher scripts, if you do not want it to connect on every WiFi.

Takeaways

  • When an IPsec VPN times out but the server responds to other traffic, check whether IKE gets a reply at all.
  • Test with different source ports. If only the default port fails, the problem is likely a NAT device or ISP filter, not the server.
  • strongSwan’s port = 0 and port_nat_t = 0 settings are a simple workaround that avoids touching the router.
  • Split tunnelling keeps your normal internet fast while still reaching internal resources.

Everything above was tested on Ubuntu 22.04.5 LTS with strongSwan 5.9.5 and network-manager-l2tp 1.20.0.


Discover more from Susiloharjo

Subscribe to get the latest posts sent to your email.

Leave a Comment

Discover more from Susiloharjo

Subscribe now to keep reading and get access to the full archive.

Continue reading