You bind SSH to the tunnel address during setup, it works, and after the next reboot nobody can log in until someone restarts the service by hand. Credentials are not the issue. The daemon asked the kernel for an address that did not exist yet, the bind failed, and the message scrolled past in the boot log. What reads as an authentication problem is a startup ordering problem.

What the bind error actually says

A listening socket must be created on an address that exists at that moment. When the VPN interface comes up later, a daemon configured to bind only to the tunnel address has nothing to attach to. The configuration remains syntactically valid; the environment was simply not ready. A service that has been running since a manual start never exercises the boot path, which is why the fault appears only on the next restart. Compare the timestamp of the bind failure with the timestamp of the address assignment, and the order becomes obvious.

Configuration validity and runtime readiness are separate properties. Test both, because one of them fails without saying anything.

Which process owns the port

The daemon is not always the component that creates the listener. Socket activation moves that responsibility to a unit, and restarting the daemon then changes nothing.

ss -ltnp 'sport = :22'
systemctl cat ssh.socket

The socket listing shows which process holds the port and on which address, while the unit dump shows whether a socket unit exists at all. When activation is in play, the address belongs in the socket unit and the daemon file is not authoritative. Read the boot log for the real bind error instead of inferring it from the symptom. A missing socket unit simply means the classic arrangement, where the daemon binds directly.

Make readiness explicit, not a sleep

A fixed delay is a guess that treats fast hardware and slow networks identically. Express the dependency instead: wait for the address, then start.

ip -4 addr show dev tun0
systemctl edit ssh.service

Add an ExecStartPre command that loops until the address appears, or order the unit after the specific network target that owns the interface. Both approaches record the dependency somewhere the next administrator can read. The loop needs a timeout, or a failed VPN leaves a unit that hangs instead of reporting a fault. Then confirm the daemon binds the tunnel address while the ordinary interface stays governed by your filter rules.

Test a clean boot and a reconnect

A manual restart after everything is online proves nothing about boot order. Reboot the machine with the tunnel configured to come up automatically and watch the listener appear without intervention.

systemctl status ssh.service
ss -ltnp 'sport = :22'

Then drop and re-establish the tunnel, because a reconnect can change the address and a hard-coded binding will not follow it. Keep an out-of-band recovery path while testing, so a bad ordering change cannot lock you out of the host.

The success criterion is a listener that appears under its documented dependency, not one that somebody remembered to restart.

Аффингæй хæстæг статьялтæ