Should you drop the chroot or copy iproute2 into the jail?
An incomplete jail fails after startup, not at boot. Decide between relaxing the hardening and copying binaries plus libraries into the new root.
The service starts, a client associates, and no route ever appears on the server side. Or the daemon refuses to start and names a command it could not execute. Both outcomes follow the same three lines, which are usually copied together from a hardening guide: drop to an unprivileged account, drop the group, and change the filesystem root. Only one of the three hides files, and only one of them removes the privilege to change the routing table. Splitting them is how you learn which line you actually want.
What the jail removes that the daemon still needs
Changing the root happens after startup, so the daemon keeps whatever it already opened and loses everything it would open later. Any helper the connection runs after that point has to exist inside the new root, along with the shared libraries it loads. A jail built from a configuration file and a certificate has neither.
Dropping to an unprivileged account is a separate restriction. A process that has given up root may be unable to alter routes or bring an interface up even when the binary is right there, so the two failures have to be diagnosed apart.
Three conditions decide which fix you need
The first condition is how your build installs routes. Recent OpenVPN releases configure the interface and routing table in-process, while older packages and custom setups rely on external tools or on up and down scripts. If the log names a command, an external tool is involved and the jail is hiding it. If the log shows a permission error instead, the account is the problem.
The second is maintenance. A jail with copied binaries has to be refreshed whenever the package that provides them is upgraded, or the first upgrade leaves a broken tunnel behind. The third is whether a specific control actually requires a chroot; if the requirement is really about privilege separation, dropping the account may satisfy it alone.
| Signal in the log | Other side of the boundary | Repair direction |
|---|---|---|
| Command not found after startup | Binary and its libraries absent inside the new root | Populate the jail, or drop the chroot line |
| Operation not permitted | Account lost the privilege to edit routes | Keep the account privileged, or pre-create routes elsewhere |
| No such device | Jail lacks the tun device node | Create the node inside, or open it before the root change |
What a working jail has to contain
A jail that runs commands needs the binary, every library it links against, and any device node the daemon opens late. Copy the pieces and test them without the VPN in the picture:
ldd /usr/sbin/ip
chroot /etc/openvpn/jail /sbin/ip route show
chroot /etc/openvpn/jail /sbin/ip link set tun0 up
The first command lists the dependencies, and it is the one people skip. A copied binary that cannot find a library fails with a different message than a missing binary, and both look like an OpenVPN fault when read from the client side. Running the command inside the jail by hand reproduces the failure without restarting the service.
Copy the configuration, certificates and any CRL into the jail as well, because a file the daemon expects to re-read after startup will not be found once the root changed. Record what you copied, since the next upgrade of the networking package replaces the originals and leaves the jail on the old copy.
Shrinking the hardening instead
If the jail exists because a guide said so, removing that one line while keeping the account restrictions is a defensible outcome. The daemon keeps its filesystem view and loses the problem, and the privilege drop still removes root from the long-lived process.
Write down what remains, and why. A configuration that quietly lost a control without a note is how hardening drifts, and the next audit will ask why the line disappeared.
Bisect the three lines before choosing
Copy the configuration, disable all three directives, and confirm that a client connects and that the expected routes appear. Re-enable the account lines only, then the chroot alone. The step where the tunnel breaks identifies the boundary, and you avoid rebuilding a jail that was never the cause.
Once it routes again, run one client session at higher verbosity and read the line naming the command or the permission that failed. That line is worth more than the timeout the client eventually reports.