Stacking a second OpenVPN client on one Linux host fails in one of two places. Either the inner client cannot reach its own server because the outer tunnel has captured that transport, or the second client rewrites the route table and the first tunnel stops working. Both problems live in routing, not in encryption, and both are solved before the new tunnel carries any application traffic. The addresses here are illustrative: tun0 is the outer tunnel, 203.0.113.9 is the inner server, and TCP 9999 is the flow that must use the inner path.

Two failure modes, one routing table

An inner client opens a UDP socket to its server, and that socket follows the current route table like any other. If the outer tunnel already owns the default, the handshake packets go into the outer tunnel. That only works when the outer server can reach the inner server and the far side agrees to carry the flow; otherwise the handshake stalls and the client retries.

The second failure comes from the server push. The usual push is redirect-gateway def1, which installs 0.0.0.0/1 and 128.0.0.0/1 rather than replacing the default route. Two clients that each install their own /1 pair therefore collide, and the newest pair wins for almost every destination. Look for those two prefixes in the table when a tunnel dies after the second one connects, because the default route itself may never have changed.

Mark the flow and give it a table

Steer the selected traffic by marking it in the mangle table and matching the mark in a policy rule. The rule points at a dedicated table whose default route is the inner device, so only marked packets take the inner path.

iptables -t mangle -A PREROUTING -p tcp -m multiport --dport 9999 -j MARK --set-mark 0x9999
ip rule add fwmark 0x9999 table 200 pref 1000
ip route add default dev tun1 table 200

Most failures in this rule set come from two mistakes. The mark value set by the firewall and the value matched by the rule must be identical, because a mismatch produces no error and no effect. And PREROUTING only sees packets that arrive from outside, so a connection started on this host needs an OUTPUT rule with the same match before it will be marked at all.

Take route ownership away from the second client

Two options keep the second client from editing the table, and they act at different points. route-nopull ignores the pushed route options, so nothing is installed. route-noexec accepts the pushed options into the client's environment but leaves the table change to a hook, which is what you want when you need to see what the server offered.

The hook runs with the pushed values in its environment, and it should add only the entries you approved. Make it idempotent: flush the table it owns before rebuilding, and do not assume the previous run left a clean state. A reconnect invokes the hook again, and a hook that appends without clearing will accumulate duplicate routes.

Choose one owner and write it down. A profile that sets both options, or a hook that competes with a manual script, produces a table nobody can explain later.

Pin both server addresses

The inner server needs a host route through the outer device before the inner client connects. That entry is what keeps the inner transport inside the outer tunnel instead of letting it follow the default.

ip route add 203.0.113.9/32 dev tun0
ip route get 203.0.113.9

Add a matching host route for the outer server in the main table as well, or a reconnect can route the outer client's own transport into its own tunnel. The order is not optional: add these entries after the relevant interface exists, which means a hook or a script that runs post-connect. A route pointing at a device that is down is removed or ignored, so an entry written at boot does not survive the sequence you assumed.

Check the lookup instead of reading the whole table. The route lookup output names the device and source address the kernel will use, which is exactly what the client gets when it opens its socket.

Test the chain and make failure visible

Capture on the outer interface while the inner client connects. The inner handshake should appear as encrypted payload inside the outer tunnel; if it appears as plain UDP to 203.0.113.9, the host route is missing and the chain was never built.

A chain that silently reverts to the physical uplink is the worst outcome, because the interface state announces nothing and the inner traffic leaves from the wrong source address. If the second client can reconnect directly when the outer tunnel drops, disable that path or bind it to the outer interface so the failure is visible.

Finish with two flows and one rule dump: a marked connection and an unmarked one should take different paths, and ip rule show should list the preference you intended. If both flows take the same path, the mark is not being applied where you think it is.

Skyldar greinar