Two management entries on one host, one service port, and a policy rule that was meant to help both. The rule matches source port 22, sends the lookup to a table that knows only the WAN default, and the VPN entry stops answering new logins while the direct entry keeps working. None of that is mysterious once the selector is read properly. A source port identifies a service, not the path a connection arrived on.

Two Entries, One Service Port

The host holds a LAN-side address and a VPN address, and sshd listens on both. A direct session and a tunnelled session therefore share the service port and differ only in which local address receives the connection.

That difference is enough to select a reply path, and it is exactly the difference a port-based rule discards. Both sessions produce replies whose source port is 22, so a rule matching that port cannot separate them. It sends both lookups to the same table, and one of them receives an answer that does not fit. The fix is to match the attribute that actually differs.

Giving each entry its own port would also work, and treating that as a requirement is a design choice rather than a property of SSH.

Read the Rule, Then Read the Table

A policy rule is an ordered selector, and the first one that returns a usable answer ends the lookup. Read the list in order and note which rule can win.

Then read the table that rule points at, because a rule which resolves successfully is not a rule which resolves usefully. A table holding only a default route sends the VPN-sourced reply out of the WAN interface. A more specific route in a later table is never consulted, which is why adding a second table so often changes nothing. The selected table is the thing to inspect.

ip -4 rule show
ip route show table 100
ip route show table main

Those two outputs together explain the result; either one alone invites a wrong conclusion.

Scope the Rule to the Flow You Mean

The attribute that separates the two entries is the local source address. A rule matching that address sends only the tunnelled sessions to the private table and leaves direct sessions on the main table.

That rule has to be paired with a table containing the private route and the return path for that subnet. Scope both to management traffic rather than to every service on the host. A rule that catches all traffic from that address will also redirect anything else the host sends from it. Narrow beats clever here, because the next person has to read it.

sudo ip rule add from 10.90.0.1 lookup 100 priority 100
sudo ip route add 10.90.0.0/24 dev wg0 table 100
ip route get 10.90.5.20 from 10.90.0.1

If both entries are translated onto one local address, that distinction disappears and the design needs connection-aware state instead.

Keep Listener, Permission and Reply Selection Apart

Three separate things must be true before a session works: the address exists, the service listens on it, and the reply finds its way back. A route change fixes only the third.

Listener configuration is its own file and its own reload, and multiple listening addresses are normal. Authentication is a third decision again, and a routing exception grants nobody access. Keep host-key verification, account authentication and source restrictions on both entries, and resist the urge to weaken the tunnel's other controls so that one management path works.

Keep a recovery channel that does not depend on the path you are changing, because a mistake here locks out the fix.

Prove It With a New Session, Not an Old One

An established terminal proves nothing, since it was admitted before the change. Open a fresh session through each intended entry.

ss -ltnp | grep ':22'
sudo sshd -T | grep -i listenaddress
ssh -v [email protected] true
sudo journalctl -u ssh -n 30

Then repeat the VPN reconnect and try again, because a route that survives a reconnect is not the same as one added to a table the reconnect flushes. Observe the denied sources separately from the permitted ones, so a blocked attempt does not hide behind a working session. Save the local address, the rule and the table beside the result. If you split the ports instead, record that as an operating decision rather than as a limit of SSH.

Ngaahi tokoni fekau'aki