Nginx Error: 'cannot load certificate ... BIO_new_file() failed (fopen: No such file or directory)' — Cause, Fix, and Troubleshooting Guide
Fix nginx 'cannot load certificate BIO_new_file() failed ... No such file or directory' — wrong ssl_certificate path, permissions, or an undeployed cert.
- #nginx
- #web-server
- #troubleshooting
- #tls
Stuck on this NGINX error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
What this error means
This is a startup (config-load) error: when nginx parses your configuration it eagerly opens every file named in an ssl_certificate directive. If the file cannot be opened — wrong path, missing file, or unreadable permissions — OpenSSL’s BIO_new_file() call fails and nginx aborts with [emerg] before it ever binds a socket. The service will not start (or a reload will be refused and the old workers keep running).
2026/07/12 09:14:52 [emerg] 20413#20413: cannot load certificate "/etc/nginx/ssl/example.com.crt": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/nginx/ssl/example.com.crt','r')) in /etc/nginx/sites-enabled/example.com.conf:12
Because it is a hard [emerg], TLS is completely down for that server block: a fresh systemctl start nginx fails outright, and nginx -t fails, so your deploy pipeline should catch it if you validate before reloading.
Symptoms at the client
nginx: [emerg] cannot load certificate ... BIO_new_file() failedon start, reload, ornginx -t.- The bracketed OpenSSL reason is
fopen:No such file or directory(path/deploy issue) orfopen:Permission denied(permissions issue). systemctl start nginxexits non-zero;systemctl status nginxshows the config test failing.- A reload (
systemctl reload nginx) is rejected and the previously running workers stay up serving the old config. - The error names an exact path and the config
file:LINEwhere thessl_certificatedirective lives. - Site is reachable on plain HTTP (port 80) but 443 never comes up after a restart.
Trust and cipher causes
- The path is wrong or a typo —
ssl_certificatepoints at/etc/nginx/ssl/example.com.crtbut the file is actually at/etc/nginx/ssl/example.com.pem,/etc/letsencrypt/live/example.com/fullchain.pem, or a differently spelled directory.No such file or directoryis literal. - The certificate was never deployed — config was shipped (Ansible/Terraform/CI) but the cert copy step failed, ran in the wrong order, or landed on a different host, so the referenced file does not exist yet.
- Relative vs absolute path confusion — a relative
ssl_certificate ssl/example.com.crt;resolves against nginx’s prefix (often/etc/nginx/), not your current directory, so it silently points somewhere unexpected. - Permissions block the read — the file exists but the
nginx/www-dataworker (or the master at load time) cannot read it; the reason field then readsPermission deniedrather thanNo such file or directory. Common after copying keys with600 root:rootinto a restrictive directory. - A broken symlink —
/etc/letsencrypt/live/example.com/symlinks into../../archive/...; if the archive file was pruned or the wholelivetree is missing,fopenfails even though the symlink path “looks right”. - SELinux/AppArmor context or mount not present — the path exists but is on an unmounted volume or has a label nginx is denied; you will usually see
Permission deniedand a matching AVC/audit entry.
Inspecting the TLS handshake
First reproduce the failure deterministically with a config test — it prints the same [emerg] without touching the running service:
sudo nginx -t
Confirm exactly which path nginx believes it should load, straight from the fully rendered config:
# Show every certificate/key path across all included files
sudo nginx -T 2>/dev/null | grep -nE 'ssl_certificate(_key)?\s'
Now check the file actually exists and is readable, following symlinks:
ls -lL /etc/nginx/ssl/example.com.crt
stat /etc/nginx/ssl/example.com.crt
namei -l /etc/nginx/ssl/example.com.crt # walks each path component's perms
If the file exists, verify it is a valid certificate and matches the hostname you expect:
openssl x509 -in /etc/nginx/ssl/example.com.crt -noout -subject -enddate
If the reason field says Permission denied, test readability as the worker user and check for a security-module denial:
sudo -u www-data cat /etc/nginx/ssl/example.com.crt >/dev/null && echo readable
sudo ausearch -m avc -ts recent 2>/dev/null | grep -i nginx # SELinux hosts
sudo dmesg | grep -i apparmor | tail # AppArmor hosts
The combination of nginx -T (what nginx wants) and ls -lL/namei (what the filesystem provides) pins down whether this is a path, a deploy, or a permissions problem.
The fix
If the path is simply wrong, correct the ssl_certificate (and its paired ssl_certificate_key) to the real, absolute location:
server {
listen 443 ssl;
server_name example.com;
# Use absolute paths; for Let's Encrypt point at fullchain.pem (leaf + intermediates)
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
}
If the file genuinely is not there, (re)deploy it before reloading nginx, and make sure your automation copies the cert before it reloads:
# Example: place the cert, then verify it landed
sudo install -o root -g root -m 0644 example.com.crt /etc/nginx/ssl/example.com.crt
ls -l /etc/nginx/ssl/example.com.crt
If the reason was Permission denied, fix ownership/permissions so nginx can read the cert (the certificate itself is public and can be world-readable; keep the private key tight at 600/640):
sudo chown root:root /etc/nginx/ssl/example.com.crt
sudo chmod 0644 /etc/nginx/ssl/example.com.crt
# Directory must be traversable by the worker:
sudo chmod 0755 /etc/nginx/ssl
Note: nginx reads ssl_certificate files as the master process (root) at config-load time, so a read failure usually means a wrong path or a directory that even root cannot traverse (broken mount/symlink), not the worker user — but tighten worker access anyway for reloads that re-read files.
A quick way to prove the path is the whole problem: temporarily comment the ssl_certificate lines, run nginx -t (it should pass), then restore them with the corrected path.
After any change, validate and reload — and note that if nginx was fully down because start failed, you need start/restart, not reload:
sudo nginx -t && sudo systemctl reload nginx
# If the service never came up: sudo systemctl restart nginx
Certificate lifecycle
- Always run
sudo nginx -tin CI/CD before reloading; this error is a[emerg]and will refuse the reload, but a blindrestartafter a failed cert copy takes the site down. - Prefer absolute paths for
ssl_certificate/ssl_certificate_key; relative paths resolve against nginx’s prefix and are a frequent source of “No such file or directory”. - Order your deploy so the cert is placed and verified before the nginx reload step — a race here is the classic cause of an undeployed cert.
- For Let’s Encrypt, point at
fullchain.pem(notcert.pem) so intermediates are served, and remembercertbot renew --deploy-hookreloads only after a successful renewal. - Keep the private key at
0600/0640and owned by root; keep the certificate readable, and keep the containing directory traversable (0755). - Watch startup health after deploys — a failed TLS load is invisible until the next restart. Track nginx unit state and
[emerg]lines in your monitoring dashboard.
Related TLS errors
- Nginx Error: Ssl Private Key Mismatch — the next failure you hit once the cert loads but the key does not pair with it.
- Nginx Error: Ssl Handshake Certificate — runtime handshake failures from chain/trust issues once nginx is serving the cert.
- Nginx Error: Ssl Do Handshake Failed — protocol/cipher negotiation errors that surface after certificates load cleanly.
See the NGINX category for more guides.
Fixed it? Get 500 NGINX & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.