Nginx

How to Resolve "Nginx SSL Certificate Error: x509: Certificate Signed by Unknown Authority"

4 min read by DebuggedIt

Quick answer

A client connecting to your Nginx server over HTTPS rejects the certificate as untrusted, even though you're confident you installed a valid certificate from a...

A client connecting to your Nginx server over HTTPS rejects the certificate as untrusted, even though you're confident you installed a valid certificate from a real certificate authority. This error almost always means Nginx is only serving the leaf certificate without the intermediate certificates needed to build a trust chain back to a root CA the client already trusts.

The Problem

A client β€” often a backend service, a CLI tool, or a mobile app rather than a browser (browsers are more forgiving and sometimes cache intermediates) β€” refuses the connection:

$ curl https://yourapp.com/api
curl: (60) SSL certificate problem: unable to get local issuer certificate

Go clients report it even more explicitly:

Get "https://yourapp.com/api": x509: certificate signed by unknown authority

Testing the certificate chain directly with OpenSSL reveals the gap:

$ openssl s_client -connect yourapp.com:443 -showcerts
Verify return code: 21 (unable to verify the first certificate)

Why It Happens

A trusted HTTPS connection requires the server to present not just its own certificate, but the full chain of intermediate certificates linking it back to a root certificate authority the client already trusts. Browsers often mask this problem because they cache intermediate certificates from other sites and can sometimes fetch missing ones automatically (via AIA fetching) β€” but curl, most programming language HTTP clients, and mobile apps are much stricter and fail outright if the chain isn't complete. Common causes:

  • Nginx's ssl_certificate directive points only at the leaf/end-entity certificate, without the intermediate certificate(s) concatenated after it.
  • The certificate was installed manually rather than through a tool like certbot, and the person configuring it didn't realize a "full chain" file was needed rather than just the individual certificate.
  • An outdated intermediate certificate is being served β€” some certificate authorities rotate their intermediates periodically, and an old cached chain file becomes invalid.
  • The certificate order in the chain file is wrong β€” it needs to be leaf certificate first, then intermediates in order, not the reverse.

The Fix

Check what Nginx is currently configured to serve:

grep ssl_certificate /etc/nginx/sites-enabled/yourapp
ssl_certificate /etc/ssl/certs/yourapp.crt;
ssl_certificate_key /etc/ssl/private/yourapp.key;

If you're using Let's Encrypt via Certbot, it already generates a proper full-chain file β€” make sure you're referencing fullchain.pem, not just cert.pem:

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

If you're using a manually purchased certificate, most certificate authorities provide both your leaf certificate and a separate intermediate bundle file. Combine them into one file, leaf first:

cat yourapp.crt intermediate.crt > yourapp-fullchain.crt
ssl_certificate /etc/ssl/certs/yourapp-fullchain.crt;
ssl_certificate_key /etc/ssl/private/yourapp.key;

Test and reload Nginx:

sudo nginx -t
sudo systemctl reload nginx

Verify the fix worked by checking the chain again:

openssl s_client -connect yourapp.com:443 -showcerts </dev/null 2>/dev/null | grep "Verify return code"
Verify return code: 0 (ok)

Still Not Working?

If the chain looks complete but verification still fails, double-check the certificate order β€” some CAs distribute intermediate bundles with certificates listed in the wrong order for concatenation, or with a root certificate mistakenly included that shouldn't be part of the served chain at all:

openssl crl2pkcs7 -nocrl -certfile yourapp-fullchain.crt | openssl pkcs7 -print_certs -noout

This lists each certificate's subject and issuer in the file β€” the first certificate's subject should match your domain, and each subsequent certificate's subject should match the previous one's issuer, forming an unbroken chain. If the order is wrong, reassemble the file in the correct sequence and reload Nginx again. Also confirm you're not accidentally still serving an older, expired, or self-signed certificate from a leftover config include or a different server block matching the same domain, since a stale duplicate configuration is a common source of confusing, seemingly-random certificate errors.

It's also worth setting up ongoing monitoring for this specific failure mode, since certificate chains can silently break well after initial setup β€” a certificate authority rotating its intermediate certificates is rare but does happen, and it can invalidate a previously-working chain without any change on your end at all. A simple periodic check, run from outside your own infrastructure so it mirrors what a real external client experiences, catches this before it affects users:

0 6 * * * openssl s_client -connect yourapp.com:443 -showcerts </dev/null 2>/dev/null | grep "Verify return code" | grep -qv "(0)" && echo "Certificate chain broken" | mail -s "SSL Alert" you@example.com

This cron job checks the verification return code daily and sends an alert the moment it stops reporting 0 (ok), which is far better than discovering a broken chain from a support ticket after clients have already started failing.