RAND_bytes() failed is a statement about one local primitive, not about the account, the pre-shared key or the remote port. Two very different faults produce that same line, and they leave different evidence behind. One is a cryptographic backend that refuses to emit bytes; the other is a process that cannot reach the entropy source its code was built to use. Separating them takes one error-queue read and one look at the jailed process's own filesystem view, and that check belongs before anything is created under /dev.

What the Return Value Distinguishes

The documented contract separates three outcomes, not two: 1 for success, -1 when the loaded RAND method does not support the request, and 0 for other failures, with the explanation left to the error queue. The RAND_bytes reference adds a point that is easy to skim past: the default generator seeds from the operating system on supported platforms, and when that seeding fails it stays in an error state rather than handing back predictable bytes.

Only one of those numbers describes the environment. A -1 is a build, provider or feature question, because the code asked for something the active implementation does not offer. A 0 with a seeding complaint is an environment question, because the generator could not obtain entropy at all. The number by itself never says which, and neither does the function name.

When a Hidden Device Node Is the Real Cause

A confined service looks at a different tree from the shell you are typing in. Chroot jails, container images and hardened unit files routinely present a /dev that holds fewer nodes than the host's. If the failing implementation opens /dev/urandom by pathname, that difference is enough to break startup while the host itself looks perfectly healthy. A device existing outside the jail proves nothing about what the jailed process is allowed to open.

Not every random interface needs a path, though. Linux's getrandom system call accepts no filename and no file descriptor, and its manual page notes that this property is useful precisely where a chroot hides device paths.

So the missing-node explanation is a hypothesis about one branch of the code, not a rule about random numbers in general. It holds when the evidence names a path, and it is guesswork when the evidence names something else.

Read the Error Queue, Then the Process View

The quickest way to settle the question is to read what the library already recorded, then look at the jailed view directly. Start with the OpenSSL build the service actually loaded, because provider stacks differ between major versions and between distributions. Then ask systemd which process is running and which restrictions apply to it. Only after both facts are on the table is it worth listing the nodes that process can see.

openssl version -a
systemctl show openvpn-server@lab -p MainPID -p PrivateDevices -p DevicePolicy
pid=$(systemctl show -p MainPID --value openvpn-server@lab)
sudo ls -l /proc/"$pid"/root/dev/urandom /proc/"$pid"/root/dev/random

Run those reads against the same process, not against whatever shell happens to be open. Comparing the host's /dev with an unrelated process's view is how the wrong dependency gets repaired twice.

Add the Dependency Only After You Can Name It

When the evidence does name a path, the repair is small: make that one path available inside the jail and leave everything else alone. A bind mount of the single node is usually enough, and mknod with the standard major and minor numbers for the node produces the same result on setups where mounting is not an option. Widening the sandbox, granting access to every character device or switching PrivateDevices off trades a targeted fix for a permanent security regression.

An entropy daemon is not a substitute for that repair. Packages such as haveged change how the kernel pool is fed; they cannot create a pathname that a jailed process is permitted to open. Installing one may make a health graph look better while the open call still fails. Keep the two problems apart, or the next failure gets attributed to the wrong layer again.

Verify the Generator and the Tunnel Separately

A restart is the only test that reuses the same configuration, the same unit file and the same confinement that produced the failure. Run it, then read the newest journal entries rather than a sample from an earlier boot.

sudo systemctl restart openvpn-server@lab
sudo journalctl -u openvpn-server@lab --since -5min --no-pager | grep -iE 'rand|entropy|Initialization Sequence'

Look for two separate things in that output: the random-source message disappearing, and the tunnel reaching its usual startup milestone. A generator that works again removes one local failure and says nothing about server identity, credentials or routing. Test those with the same client configuration that failed before, so the result is comparable rather than merely better.

Tënë sô zo