Your Brand New VM Is Terrible at Generating Secrets
You just spun up a fresh VM, ran ssh-keygen, and it finished instantly. That seems great. It’s not great.
Generating a cryptographic key requires high-quality randomness. Real randomness — not pseudo-random patterns, not predictable sequences. The Linux kernel collects “entropy” (unpredictability) from physical hardware events: keyboard interrupts, disk seeks, network packet timing, hardware noise. On a headless VM with no keyboard, no spinning disk, and deterministic network timing? The entropy pool is nearly empty when you boot.
This matters because a weak random number generator produces predictable keys. Predictable keys get broken. That’s not a theoretical concern — it’s been demonstrated in practice with real servers.
Let’s understand what’s actually happening and how to fix it.
The Entropy Pool: What It Is
The Linux kernel maintains an entropy pool — a large internal state that mixes in unpredictable data from hardware events. Every interrupt, every disk seek timing irregularity, every USB transaction jitter contributes bits of entropy.
Check your current entropy pool size:
cat /proc/sys/kernel/random/entropy_avail
# Should be a number between 0 and 4096 on older kernels
# On 5.x+ kernels it's less meaningful (read on)
cat /proc/sys/kernel/random/poolsize
# Typically 4096 bits
On a fresh VM, you might see entropy_avail hovering around 200-500 at boot. On a busy server, it’ll be higher. On a hardware server with real I/O, it typically stays well-stocked.
/dev/random vs /dev/urandom: The Eternal Debate
This distinction confused people for decades and caused a lot of bad decisions.
Historical behavior (pre-kernel 5.6):
/dev/random: blocking. Would block if entropy pool fell below a threshold. Cryptographically strong./dev/urandom: non-blocking. Would fall back to a CSPRNG (cryptographically secure pseudorandom number generator) when entropy was low. Also cryptographically strong, but old docs warned it wasn’t “truly random.”
Modern behavior (kernel 5.6+):
/dev/randomand/dev/urandomare functionally identical (both non-blocking)/dev/randomno longer blocks after the initial seeding is complete- Both use the same CSPRNG (ChaCha20-based) under the hood
getrandom()syscall is the preferred interface anyway
The practical takeaway: use /dev/urandom or getrandom(). Don’t use /dev/random unless you’re on a very old kernel and have a specific reason. Modern cryptography experts are unanimous on this.
# Generate 32 bytes of random data
dd if=/dev/urandom bs=32 count=1 | xxd
# Using getrandom in Python (preferred for applications)
python3 -c "import secrets; print(secrets.token_hex(32))"
# Or the direct syscall
python3 -c "import os; print(os.urandom(32).hex())"
Why VMs and Containers Have Low Entropy
Physical servers collect entropy from:
- Keyboard and mouse interrupts (timing jitter)
- Disk seeks and rotational latency (not present on SSDs, but timing still varies)
- Network packet arrival timing
- CPU performance counter jitter
- Hardware noise amplified through voltage sensors
VMs don’t have most of this. They have:
- Virtual disk I/O with deterministic timing
- Emulated NICs
- No physical keyboard in typical headless deployments
- Hypervisor-controlled scheduling that can remove timing jitter
The result: VMs boot with low entropy and accumulate it slowly. In the worst case, blocking calls to /dev/random could stall for minutes on early boots.
Containers are even worse — they share the host kernel’s entropy pool, and if the host is entropy-starved, every container on it is.
Checking Your Entropy in Real Time
# Watch entropy level change over time
watch -n 1 cat /proc/sys/kernel/random/entropy_avail
# Generate some I/O to see entropy rise
find / -type f > /dev/null 2>&1 &
# Watch the entropy counter
# More accurate: use rngtest to test quality
sudo apt install rng-tools
cat /dev/urandom | rngtest -c 1000
# Should show "FIPS 140-2 successes: 1000" or similar
The Fix: haveged
Haveged (HArdware Volatile Entropy Gathering and Expansion Daemon) is the classic solution for entropy-starved VMs. It uses the HAVEGE algorithm, which exploits unpredictability in CPU execution (branch predictor state, cache behavior, instruction timing) to generate entropy.
# Install
sudo apt install haveged # Debian/Ubuntu
sudo dnf install haveged # RHEL/Fedora
# Enable and start
sudo systemctl enable --now haveged
# Verify it's feeding entropy
watch -n 1 cat /proc/sys/kernel/random/entropy_avail
# Should stay comfortably high (2000+)
Haveged works well and is widely deployed. There are occasional academic critiques about the quality of HAVEGE entropy, but it’s considered good enough for production use and dramatically better than nothing.
virtio-rng: The Right Answer for KVM VMs
If you’re running KVM/QEMU VMs, there’s a better solution: virtio-rng. This passes entropy from the hypervisor host directly to the guest VM through a virtual RNG device.
On the host side, make sure the VM is configured with a virtio-rng device. In a libvirt/QEMU config:
<!-- In your VM's XML definition -->
<devices>
<rng model="virtio">
<backend model="random">/dev/urandom</backend>
<rate period="2000" bytes="1024"/>
</rng>
</devices>
Or with qemu directly:
qemu-system-x86_64 ... -object rng-random,filename=/dev/urandom,id=rng0 \
-device virtio-rng-pci,rng=rng0,max-bytes=1024,period=2000
On the guest:
# Check if virtio-rng device is present
ls /sys/bus/virtio/drivers/virtio_rng/
lsmod | grep virtio_rng
# Load the module if not loaded
sudo modprobe virtio-rng
# Verify it's registered as a hw_random source
cat /sys/class/misc/hw_random/rng_current
Then configure rng-tools to feed it into the kernel pool:
sudo apt install rng-tools
# Enable the rngd service
sudo systemctl enable --now rng-tools
Hardware RNG: /dev/hwrng
Some systems have dedicated hardware random number generators. Modern Intel/AMD CPUs include RDRAND and RDSEED instructions. TPM chips include RNG capabilities. Some embedded boards have dedicated entropy sources.
# Check if hardware RNG is available
ls /dev/hwrng
# If it exists, you have hardware RNG
# Check which driver is backing it
cat /sys/class/misc/hw_random/rng_available
cat /sys/class/misc/hw_random/rng_current
# Test read speed (hardware RNG should be fast)
dd if=/dev/hwrng of=/dev/null bs=512 count=100
The rngd daemon (from rng-tools) automatically reads from /dev/hwrng and feeds it to the kernel entropy pool:
sudo apt install rng-tools
sudo systemctl enable --now rng-tools
# Manually specify the source if needed
sudo rngd -r /dev/hwrng -o /dev/random -f
For AWS EC2 instances, they provide a hardware RNG via the virtio-rng interface automatically. GCP and Azure also provision entropy sources for VMs.
Practical: Checking Before Key Generation
Here’s a quick check to run before generating critical keys:
#!/bin/bash
# check-entropy.sh
ENTROPY=$(cat /proc/sys/kernel/random/entropy_avail)
echo "Current entropy: $ENTROPY bits"
if [ $ENTROPY -lt 1000 ]; then
echo "WARNING: Low entropy. Consider installing haveged."
echo "Run: sudo apt install haveged && sudo systemctl enable --now haveged"
else
echo "Entropy looks healthy."
fi
# Show available RNG sources
echo ""
echo "Available RNG sources:"
cat /sys/class/misc/hw_random/rng_available 2>/dev/null || echo "No hw_random device"
The Container Situation
Containers share the host kernel’s entropy pool. A host with good entropy = all containers get good entropy. A starved host = every container suffers.
For container workloads:
- Make sure the host has haveged or virtio-rng running
- Don’t try to run haveged inside the container itself (it needs hardware access)
- Consider mounting the host’s
/dev/urandominto the container if you need fine-grained control
# docker-compose.yml
services:
app:
image: myapp
devices:
- /dev/urandom:/dev/urandom
Does This Matter for Modern Kernels?
Sort of. On kernel 5.6+, /dev/urandom is non-blocking and uses a CSPRNG that’s seeded once at boot. After initial seeding, it doesn’t deplete. So the “block forever waiting for entropy” problem largely doesn’t exist on modern kernels.
But: if your VM boots with very low entropy and you generate keys before the initial seeding completes, you can still have a problem. The kernel now waits until it has enough entropy for that initial seed before unlocking the RNG, so very early key generation can still stall.
The solution is the same: get more entropy sources, use haveged or virtio-rng, don’t generate critical keys immediately at first boot before the system has had time to collect entropy.
For anything touching cryptography — SSH keys, TLS certs, GPG keys, session tokens — a few seconds checking your entropy situation is worth it. Your 2 AM self debugging “why did this key fail to verify” will agree.