Even a quiet server seeds its generator before it needs it, so the randomness failures that still happen are not shortages. They are replays: a seed baked into an image, a snapshot resumed, a state restored from before the last reseed. The kernel does not drip entropy at you; it concentrates a little device noise into a generator, then produces keystream indefinitely.

That inversion is why most of the operational folklore around entropy is now wrong: watching entropy_avail, reading /dev/random for keys, and installing an entropy daemon all address a scarcity the kernel stopped having, while the thing that genuinely breaks keys, an identical seed on two machines, is invisible to all of them.

A pool goes in, a generator comes out

The kernel mixes physical noise, including interrupt timing jitter, device latencies, hardware RNGs where available, and CPU-based jitterentropy, and extracts it with BLAKE2s into an entropy pool. That pool seeds a set of ChaCha20 CRNGs, and the CRNGs are what actually produce bytes. Collection continues and the generator is periodically reseeded, but the strength of a byte you already emitted is fixed by the state it came from.

Once initialized, the output is keystream, not a finite resource. entropy_avail sits at 256 bits, the size of a ChaCha20 key, and it does not fall when you read; a megabyte read consumes no pool credit at all. random(4) still describes entropy_avail as a value in the range 0 to 4096 and poolsize as 4096 bits on Linux 2.6 and later: documentation that keeps a stale alert alive.

/dev/random stopped being the careful choice

Since Linux 5.6, /dev/random no longer blocks outside early boot, and O_NONBLOCK is ignored on it. The separate blocking pool is gone; the same generator now sits behind a legacy name. getrandom(2), added in Linux 3.17, draws from the urandom source by default and blocks only while that source is uninitialized. GRND_NONBLOCK turns that block into EAGAIN, and GRND_RANDOM selects the random source instead, the one that can still block without an upper bound.

random(7) is blunt: unless you are doing long-term key generation, and probably not even then, avoid /dev/random and GRND_RANDOM. Once initialized, a read of up to 256 bytes always returns in full and is not interrupted by signals. The price of the safer interface is a call that can hang indefinitely.

Boot is the only window that matters

The real failure lives in the moments before initialization, which the kernel announces with a crng init done line. A machine with no saved seed has to accumulate one from device noise; a machine with a seed file, a virtio-rng device or RDRAND has it immediately. random(4)’s recipe remains the cheapest fix: at shutdown, dd 512 bytes from /dev/urandom into a mode-600 seed file, and at boot cat it back before anything asks for a key.

The asymmetry is stark: a 32-byte seed file or a single virtio-rng read reaches the 256-bit threshold instantly, while an interrupt-only boot accumulates thousands of samples first. Whether a key is minted before or after crng init done is a configuration decision, not luck.

When it breaks

The first failure is a first-boot key that blocks. A unit minting a TLS key or a token secret during its first start can call getrandom() before initialization and sit there with nothing useful in the log. Diskless machines, small routers, virtual machines restored from a frozen filesystem and containers with no entropy device are the usual victims. Compare the key file’s mtime against the crng init done line: a key older than that line came from an uninitialized generator.

The second failure is silent: nothing blocks and every dashboard looks healthy. Snapshot a virtual machine, roll it back, or stamp ten cloud instances from one golden image, and each copy resumes with the same generator state and the same seed file. They then emit identical SSH host keys, duplicate UUIDs from /proc/sys/kernel/random/uuid, and overlapping TLS session tickets, so one instance’s output predicts its siblings. Ten clones sharing one baked-in seed produced a single distinct fingerprint; ten reseeded clones produced ten.

Check it quickly

Four commands, in this order:

dmesg | grep -i "crng init done"
cat /proc/sys/kernel/random/entropy_avail /proc/sys/kernel/random/poolsize
python3 -c 'import os; print(os.getrandom(8, os.GRND_NONBLOCK).hex())'
stat -c '%y %n' /etc/ssh/ssh_host_ed25519_key /var/lib/random-seed

On a modern kernel the counter reads 256 and poolsize reads 4096: the first number is a key size, not a fuel gauge, so a low reading is not a problem to fix. The getrandom call should return eight hex bytes immediately; BlockingIOError means the source is uninitialized and a blocking call would stall. The stat output is the real check, because a host key older than the crng init done line came from an uninitialized generator and should be rotated.

Sources

  • Linux random(4) device, /dev/random and /dev/urandom semantics, entropy_avail and poolsize, plus the seed-file recipe, checked 2026-09-16.
  • Linux getrandom(2), GRND_NONBLOCK and GRND_RANDOM, and blocking until the urandom source is initialized, checked 2026-09-16.
  • Linux random(7), the interface comparison table and when not to use GRND_RANDOM, checked 2026-09-16.
  • ArchWiki: random number generation, BLAKE2s extraction seeding ChaCha20 CRNGs, and why entropy_avail stays at 256, checked 2026-09-16.