Skip to content
🎉 Launch sale:50% off everything over $22 — automatically applied at checkout· ends Aug 2Shop the sale →
DevOps AI ToolKit
Newsletter
All guides
AI for NGINX By James Joyner IV · · 8 min read Last reviewed Jul 2026

Nginx Error: 'cannot load certificate ... BIO_new_file() failed (fopen: No such file or directory)' — Cause, Fix, and Troubleshooting Guide

Quick answer

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
Free toolkit

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() failed on start, reload, or nginx -t.
  • The bracketed OpenSSL reason is fopen:No such file or directory (path/deploy issue) or fopen:Permission denied (permissions issue).
  • systemctl start nginx exits non-zero; systemctl status nginx shows 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:LINE where the ssl_certificate directive 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 typossl_certificate points at /etc/nginx/ssl/example.com.crt but 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 directory is 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-data worker (or the master at load time) cannot read it; the reason field then reads Permission denied rather than No such file or directory. Common after copying keys with 600 root:root into a restrictive directory.
  • A broken symlink/etc/letsencrypt/live/example.com/ symlinks into ../../archive/...; if the archive file was pruned or the whole live tree is missing, fopen fails 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 denied and 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 -t in CI/CD before reloading; this error is a [emerg] and will refuse the reload, but a blind restart after 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 (not cert.pem) so intermediates are served, and remember certbot renew --deploy-hook reloads only after a successful renewal.
  • Keep the private key at 0600/0640 and 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.

See the NGINX category for more guides.

Free download · 368-page PDF

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?

Free download · 368-page PDF

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.