Fix Nginx 'cannot load certificate' SSL Error on Startup or Reload

intermediateโšก Nginx2026-04-20| Nginx 1.18+ on Ubuntu 20.04/22.04, Debian 11/12, CentOS 7/8, RHEL โ€” any Linux system running Nginx with SSL/TLS

Error Message

nginx: [emerg] cannot load certificate "/etc/nginx/ssl/cert.pem": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory)
#nginx#ssl#tls#certificate#https

The Error

Nginx refuses to start or reload, and you see this in the logs:

nginx: [emerg] cannot load certificate "/etc/nginx/ssl/cert.pem": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory)

The path in the error matches whatever you have in ssl_certificate inside your Nginx config. The underlying OpenSSL call BIO_new_file() failed. That usually means one of three things: the file doesn't exist at that path, Nginx can't read it, or the path is simply wrong.

Root Causes

  • The certificate file simply doesn't exist at the configured path
  • The path in nginx.conf has a typo or points to the wrong location
  • The file exists but is owned by root and not readable by the www-data / nginx user
  • A symlink pointing to the cert is broken (common after Let's Encrypt renewal)
  • The cert was stored on a mounted volume that isn't mounted yet at boot time

Fix 1: Confirm the File Actually Exists

Start simple โ€” does the file actually exist at that path?

ls -la /etc/nginx/ssl/cert.pem

If that returns No such file or directory, the file is missing. Check where your certificate actually lives:

# Let's Encrypt certs are here:
ls -la /etc/letsencrypt/live/yourdomain.com/

# Certbot / acme.sh typical output:
# cert.pem  chain.pem  fullchain.pem  privkey.pem

Using Let's Encrypt? Point ssl_certificate to fullchain.pem, not cert.pem. Nginx needs the full chain โ€” just the leaf cert will fail TLS handshakes with most clients. The private key goes to privkey.pem:

ssl_certificate     /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

Fix 2: Correct the Path in Your Nginx Config

Scan all your Nginx configs for the ssl_certificate directive:

grep -r 'ssl_certificate' /etc/nginx/

Then update the paths to match where your cert actually lives. Example for a custom cert stored in /etc/nginx/ssl/:

server {
    listen 443 ssl;
    server_name yourdomain.com;

    ssl_certificate     /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;

    # ... rest of config
}

After editing, always validate before reloading:

nginx -t

Fix 3: Create the SSL Directory and Copy Your Cert

Got a certificate from a commercial CA (say, they emailed you a .crt file)? Put it in the right place:

# Create the directory if missing
mkdir -p /etc/nginx/ssl

# Copy your certificate files
cp yourdomain.crt /etc/nginx/ssl/cert.pem
cp yourdomain.key /etc/nginx/ssl/privkey.pem

# Set restrictive permissions
chmod 600 /etc/nginx/ssl/privkey.pem
chmod 644 /etc/nginx/ssl/cert.pem
chown root:root /etc/nginx/ssl/privkey.pem

Fix 4: Check for a Broken Symlink (Let's Encrypt)

Let's Encrypt stores live certs as symlinks into /etc/letsencrypt/archive/. A failed renewal can leave the symlink pointing to a file that no longer exists:

# Check if symlinks are valid
ls -la /etc/letsencrypt/live/yourdomain.com/

# A broken symlink looks like:
# cert.pem -> ../../archive/yourdomain.com/cert3.pem  (file doesn't exist)

# Verify the archive files exist
ls -la /etc/letsencrypt/archive/yourdomain.com/

If the archive files are gone, force a renewal:

certbot renew --force-renewal -d yourdomain.com

Or if you're using acme.sh:

acme.sh --renew -d yourdomain.com --force

Fix 5: Fix File Permissions

The file is there โ€” Nginx just can't open it. Find out which user the Nginx worker runs as:

ps aux | grep nginx | grep -v grep
# Look for the worker process user (www-data, nginx, or nobody)

Then test whether that user can actually read the cert:

# For Ubuntu/Debian (www-data user)
sudo -u www-data cat /etc/nginx/ssl/cert.pem

# For CentOS/RHEL (nginx user)
sudo -u nginx cat /etc/nginx/ssl/cert.pem

If access is denied, adjust permissions. Certs can be world-readable; private keys should not be:

chmod 644 /etc/nginx/ssl/cert.pem
chmod 640 /etc/nginx/ssl/privkey.pem
chown root:www-data /etc/nginx/ssl/privkey.pem

For Let's Encrypt, Nginx also needs execute permission on the live/ and archive/ directories to follow the symlinks. Without it, the traversal fails silently:

chmod 755 /etc/letsencrypt/live/
chmod 755 /etc/letsencrypt/archive/
chmod 755 /etc/letsencrypt/archive/yourdomain.com/

Verify the Fix

Before touching Nginx, run the config test:

nginx -t
# Expected output:
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful

Once it passes, reload (or restart if Nginx was stopped entirely):

# Reload running instance
systemctl reload nginx

# Or restart if it was stopped
systemctl restart nginx

# Confirm it's running
systemctl status nginx

Now confirm HTTPS actually works end-to-end:

curl -I https://yourdomain.com
# Should return HTTP/2 200 (or 301/302), not a connection error

# Check certificate details
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com < /dev/null 2>&1 | grep -E 'subject|issuer|expire'

Prevention

  • Use absolute paths in ssl_certificate โ€” never relative paths.
  • Hook into cert renewal: drop a script containing nginx -t && systemctl reload nginx into /etc/letsencrypt/renewal-hooks/deploy/ so Nginx reloads automatically after every successful renewal.
  • Catch renewal failures early: run certbot renew --dry-run in a weekly cron job โ€” it simulates renewal without replacing anything, so you'll know about failures before they cause downtime.
  • Avoid storing certs on external mounts: if you must, add RequiresMountsFor= to the Nginx systemd unit so it waits for the mount before starting.

Related Error Notes