The Error
Nginx or Apache refuses to start after a cert renewal or server migration:
nginx: [emerg] SSL_CTX_use_PrivateKey_file("/etc/ssl/private/server.key") failed
(SSL: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib)
Or from OpenSSL directly:
error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib
Server won't start. HTTPS is down. Let's fix it.
What This Actually Means
OpenSSL tried to load your private key and failed. Three things cause this:
- Wrong key file โ the private key doesn't pair with the certificate (most common by far)
- Corrupted or truncated PEM file โ the file got damaged, cut off, or partially overwritten
- Wrong format โ the key is binary DER instead of base64 PEM, or the headers are missing
Diagnose before you act โ the fix for each case is completely different.
Step 1: Check If the Key Matches the Certificate
Run both commands and compare the MD5 output. Matching hashes mean your key and cert are a valid pair:
# Fingerprint of your certificate
openssl x509 -noout -modulus -in /etc/ssl/certs/server.crt | openssl md5
# Fingerprint of your private key
openssl rsa -noout -modulus -in /etc/ssl/private/server.key | openssl md5
Matching output looks like this:
(stdin)= a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4
(stdin)= a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 โ same = good
Different hashes? Mismatched pair. Skip straight to Step 3.
Step 2: Verify the PEM File Isn't Corrupted
Try loading the key directly:
openssl rsa -in /etc/ssl/private/server.key -check
A healthy key prints:
RSA key ok
Any error here means the file is damaged, or it was never a valid PEM key to begin with.
Also sanity-check the file boundaries:
head -1 /etc/ssl/private/server.key
tail -1 /etc/ssl/private/server.key
A valid RSA key file:
-----BEGIN RSA PRIVATE KEY-----
...base64 content...
-----END RSA PRIVATE KEY-----
PKCS#8 format (used by newer tools and ECDSA keys):
-----BEGIN PRIVATE KEY-----
...base64 content...
-----END PRIVATE KEY-----
Binary garbage or a missing footer means the file is corrupted. Restore from backup or reissue the certificate.
Step 3: Fix a Mismatched Key and Certificate
Key and cert came from different issuance rounds. The most common scenario: you renewed the cert but the new certificate landed in a different path, leaving the old key behind. Or a deploy script grabbed the cert from this month and the key from last quarter's package.
Option A โ You still have the original private key somewhere
Check your backup location, the machine where the CSR was generated, or your secret storage (HashiCorp Vault, GitHub Secrets, AWS Secrets Manager). Once you find it, verify the match before touching anything in production:
openssl x509 -noout -modulus -in server.crt | openssl md5
openssl rsa -noout -modulus -in recovered.key | openssl md5
When they match, drop it into place and reload:
cp recovered.key /etc/ssl/private/server.key
chmod 600 /etc/ssl/private/server.key
nginx -t && systemctl reload nginx
Option B โ The key is gone, reissue from scratch
No key, no shortcut. Generate a new key pair, create a new CSR, and get a fresh certificate from your CA:
# New 2048-bit key (use 4096 for long-lived certs)
openssl genrsa -out /etc/ssl/private/server.key 2048
# CSR from the new key
openssl req -new -key /etc/ssl/private/server.key -out server.csr
Submit the CSR to Let's Encrypt, DigiCert, or whoever issued the original. For a self-signed cert to unblock testing right now:
openssl req -x509 -new -nodes \
-key /etc/ssl/private/server.key \
-sha256 -days 365 \
-out /etc/ssl/certs/server.crt
Option C โ The key is in DER format
If head -1 shows binary garbage instead of a -----BEGIN header, the file is DER-encoded. One command fixes it:
openssl rsa -inform DER -in server.key.der -outform PEM -out /etc/ssl/private/server.key
Step 4: Fix File Permissions
Wrong permissions cause silent failures โ OpenSSL can't read the key but the error message won't always spell that out. Set them correctly:
# Private key: root-only
chmod 600 /etc/ssl/private/server.key
chown root:root /etc/ssl/private/server.key
# Certificate: world-readable is fine
chmod 644 /etc/ssl/certs/server.crt
On Ubuntu, Nginx runs as www-data. A 600 key owned by root blocks it completely. Fix:
chown root:www-data /etc/ssl/private/server.key
chmod 640 /etc/ssl/private/server.key
Verify the Fix
Test the config before reloading โ don't take the server down a second time:
# Nginx
nginx -t
# Apache
apachectl configtest
Then reload:
systemctl reload nginx
# or
systemctl reload apache2
Confirm the right certificate is actually being served:
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null | openssl x509 -noout -dates -subject
You'll see the domain name and expiry dates. Wrong domain in the output means the cert path in your config still points at the old file.
Quick Diagnostic Checklist
- MD5 modulus of cert and key must be identical โ check this first, every time
openssl rsa -in key.pem -checkmust return "RSA key ok"- PEM file must start with
-----BEGIN ... KEY-----and end with-----END ... KEY----- - Permissions: 600 (or 640 for www-data) for the private key, 644 for the cert
- Key file path in Nginx/Apache config must match the actual file location exactly โ trailing slash, symlink, everything
Avoid This Next Time
Generate the key and CSR together in one command, in the same directory, with a year in the filename. No more mixing files from different issuance rounds:
openssl req -newkey rsa:2048 -nodes \
-keyout /etc/ssl/private/yourdomain-2026.key \
-out /etc/ssl/csr/yourdomain-2026.csr
Add the modulus check to your deployment script. Two commands, ten seconds, catches this every single time.

