Fix Nginx "Cannot Load Certificate" for a Django Site
Fix Nginx emerg errors such as cannot load certificate BIO_new_file() failed, no such file, or key values mismatch: check certificate paths, Certbot live symlinks, file permissions, PEM formats, and that the key matches the certificate.
Problem statement
After moving servers, restoring a backup, or editing TLS settings, nginx -t fails:
nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/example.com/fullchain.pem":
BIO_new_file() failed (SSL: error:80000002:system library::No such file or directory:
calling fopen(/etc/letsencrypt/live/example.com/fullchain.pem, r) ...)
or:
nginx: [emerg] SSL_CTX_use_PrivateKey("/etc/ssl/private/example.key") failed
(SSL: error:05800074:x509 certificate routines::key values mismatch)
Nginx reads certificates and keys when it loads the configuration. If a file is missing, unreadable, in the wrong format, or the key does not belong to the certificate, Nginx will not start or reload. Your Django app is fine; the HTTPS front door is closed.
- Check the path exists:
sudo ls -l /etc/letsencrypt/live/example.com/. - Certbot's
live/files are symlinks intoarchive/; copy both directories (with links) when moving servers, or reissue. - Use
fullchain.pemforssl_certificateandprivkey.pemforssl_certificate_key. - Check the key matches: compare
openssl x509 -noout -pubkey -in cert.pemwithopenssl pkey -pubout -in key.pem. - Make sure files are readable by the user Nginx starts as (root reads them, then drops privileges).
Step-by-step solution
1. Find which file and which error
nginx -t names the file and includes the OpenSSL reason. No such file or directory is a path problem. key values mismatch is a pairing problem. PEM_read_bio_X509_AUX ... no start line is a format problem.
2. Fix missing files and broken Certbot links
sudo ls -l /etc/letsencrypt/live/example.com/
Each file in live/ should be a symlink to a numbered file in archive/example.com/. Copying only live/ with cp or rsync without -a, or restoring from a backup that did not preserve links, leaves broken symlinks. Options:
- Copy the whole
/etc/letsencryptdirectory withsudo rsync -aortarfrom the old server. - Or issue a new certificate on the new server:
sudo certbot --nginx -d example.com -d www.example.com.
When issuing fresh, temporarily comment out the TLS server block or Certbot's own Nginx test will fail on the missing files.
3. Use the right files in the right directives
server {
listen 443 ssl;
http2 on;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
}
fullchain.pem includes the intermediate certificate browsers need. Using cert.pem loads, but some clients then fail with "unable to verify the first certificate". Putting the key in ssl_certificate gives "no start line".
4. Check that key and certificate match
openssl x509 -noout -pubkey -in fullchain.pem | sha256sum
openssl pkey -pubout -in privkey.pem | sha256sum
The two hashes must be identical. Mismatches happen when a certificate is reissued with a new key and only one file is updated, or when files from two domains get mixed up.
5. Convert formats from commercial CAs
Certificates bought from a CA may come as .crt, .cer, .pfx, or a ZIP of separate files. Nginx needs PEM: the server certificate followed by intermediates in one file, and an unencrypted PEM key:
cat example_com.crt intermediate.crt > fullchain.pem
openssl pkcs12 -in example.pfx -nocerts -nodes -out privkey.pem # from PFX
An encrypted key (-----BEGIN ENCRYPTED PRIVATE KEY-----) makes Nginx prompt for a passphrase at startup, which fails under systemd; remove the passphrase or use ssl_password_file.
Explanation
Nginx's master process runs as root and loads all certificates and keys while parsing the configuration. It asks OpenSSL to open each file, parse it, and verify that the private key corresponds to the certificate's public key. Any failure is fatal for the configuration, so nginx -t fails and a reload is refused, leaving the previous configuration running if Nginx was already up. A cold start or restart with a broken configuration leaves no HTTPS listener at all, which is why testing before restarting matters.
Edge cases / notes
- Certbot renewals. Renewal changes the
archive/file numbers and updates thelive/symlinks; always referencelive/, neverarchive/files directly. - Docker. Mount
/etc/letsencryptas a whole directory; mounting singlelive/files breaks when renewals change symlink targets. - Variables in paths.
ssl_certificateaccepts variables for per-host certificates, but then files load per handshake and errors appear in the error log instead of at startup.
Next steps
- Issue and renew certificates with Configure HTTPS With Let’s Encrypt for Django.
- If renewals fail, see Fix Certbot Renewal Failed.
- Tune the TLS setup with Nginx TLS Configuration A+ for Django.
FAQ
Why does Nginx not start after copying /etc/letsencrypt?
The copy probably broke the symlinks in live/. Copy with rsync -a or tar, or reissue the certificate.
Should I use cert.pem or fullchain.pem?
fullchain.pem. It includes the intermediate certificates that clients need to verify your certificate.
How do I check that my key matches the certificate?
Compare the SHA-256 of the public key extracted from each file with openssl. They must be identical.
Does this affect my Django app?
Only through Nginx. Django keeps running, but HTTPS requests cannot reach it until Nginx loads the certificate.