495 SSL Certificate Error (nginx): Client Cert Failed
495 SSL Certificate Error: nginx got your mTLS cert and rejected it — expired, wrong CA, or bad chain. Find why in 3 checks. Free instant check, no sign-up.
Check your domain for this issue now
Free, no sign-up. Runs the exact check this guide describes and shows what to fix.
Problem
Your request to an nginx endpoint comes back 400 Bad Request with the body The SSL certificate error, and the server’s access log records the status as 495. You did send a client certificate — this isn’t the empty-handed case. nginx is enforcing mutual TLS, it asked for your certificate, you presented one, and it looked at that certificate and rejected it. The server’s own certificate is fine; the padlock side works. What failed is the verification of your identity: the cert is expired, not yet valid, revoked, or signed by a CA nginx doesn’t trust for client auth. The question is which of those, and the answer is sitting in one log line you probably haven’t read yet.
Symptoms
- The client sees
400 Bad Requestwith the body textThe SSL certificate error; the nginx access log shows status495(not 400). - It only happens against endpoints configured for client-certificate auth (
ssl_verify_client on) — everything else on the server loads normally. curl --cert client.crt --key client.key https://HOST/still fails, where a cert-less request to the same host returns496instead.- The nginx error log carries the real reason:
client SSL certificate verify error: (NN:reason) while reading client request headers. - Sometimes it starts right after an OpenSSL or nginx package upgrade, with no config change on your side (see the FAQ).
What This Error Actually Means
495 is one of nginx’s non-standard internal status codes, defined in its source next to 494 (request header or cookie too large), 496 (NGX_HTTP_NO_CERT — no client certificate sent at all), and 497 (a plain HTTP request on the HTTPS port). 495 — internally NGX_HTTPS_CERT_ERROR — means a client certificate was presented and failed verification. Because 495 isn’t a registered HTTP status, nginx doesn’t put it on the wire; it returns 400 Bad Request with the body The SSL certificate error and logs 495 for you.
It appears when a server block turns on client-cert verification with ssl_verify_client on; and names a trust bundle with ssl_client_certificate. During the handshake nginx sends a CertificateRequest, the client answers with its certificate, and nginx runs OpenSSL’s chain verification against that bundle. If verification returns any error, nginx sets $ssl_client_verify to FAILED:<reason> and answers 495 — the opposite of 496, where verification never started because nothing was presented. The reason string is the useful part: it’s an OpenSSL X.509 verify code, and each code points at a different fix.
Top 3 Causes
- The client certificate is expired, not yet valid, or revoked - The simplest and most common. The error log reads
(10:certificate has expired),(9:certificate is not yet valid)— usually a client clock set in the past or a freshly minted cert whosenotBeforeis still in the future — or(23:certificate revoked). This is a client-identity problem, not a server misconfiguration: the cert nginx received is genuinely out of its validity window or on a CRL. - The issuing CA (or an intermediate) isn’t in the server’s trust bundle - The client cert is valid, but nginx can’t build a trusted path to it. Error
(20:unable to get local issuer certificate)or(21:unable to verify the first certificate)means the CA that signed the client cert — or an intermediate between them — is missing from the file named byssl_client_certificate, orssl_verify_depth(default 1) is too shallow to reach the issuer. Also(19:self signed certificate in certificate chain)when a private root isn’t included. - An OpenSSL upgrade changed chain-building under a config you never touched - OpenSSL 1.1.1i (December 2020) tightened how it constructs the client chain (nginx trac #1847). Certificates that verified for years began failing with error 20/21 because the trust bundle was relying on lenient chain-building that OpenSSL removed. Nothing in nginx changed; the platform did. The fix is to complete the intermediate chain in the bundle — downgrading OpenSSL only hides it.
Diagnose with DechoNet
- SSL Check reads the server’s own TLS handshake and certificate from the outside, with no client certificate of its own. It confirms the server cert and chain are healthy, so you can rule out a server-side problem and keep your attention on the client-auth layer where 495 actually lives. (A cert-less probe against an mTLS endpoint is refused, which itself confirms the endpoint is enforcing client auth for everyone.)
- HTTP Check confirms the endpoint returns
400withThe SSL certificate errorto an outside request and captures the response, and — by testing from a network that isn’t yours — tells you whether the requirement applies to the whole host or only to a path a proxy in your own path is mangling.
Resolution Checklist
- Read the error log, not the access log.
grep "SSL certificate verify error" /var/log/nginx/error.loggives you(NN:reason). Everything below depends on that number — don’t guess. - For
(10:...)/(9:...)/(23:...)— expired, not yet valid, revoked — the certificate itself is the problem. Reissue or renew the client cert, and if it’s(9:...)on a brand-new cert, check the client’s clock against real time. - For
(20:...)/(21:...)/(19:...), fix the trust path on the server. Put the full issuing chain (root + every intermediate) into the file named byssl_client_certificate, and raisessl_verify_depthuntil it can reach the client cert’s issuer. Verify the cert against the bundle out of band:openssl verify -CAfile ca-bundle.pem client.crt. - Confirm you’re presenting the cert you think you are.
curl -v --cert client.crt --key client.key https://HOST/PATH— check that curl actually loads the pair (a mismatched key or wrong path fails before the request) and that the cert’s issuer matches what the server trusts. - If 495 started after a package update with no config change, treat it as the OpenSSL chain-building change: complete the intermediate chain in
ssl_client_certificate. Do not pin an old OpenSSL to “fix” it. - If a front proxy terminates TLS and re-originates to nginx, make sure it forwards a certificate the backend’s bundle actually trusts — a re-signed or substituted cert from the proxy layer is a frequent hidden cause of 495 on the origin.
When to Escalate
- If
openssl verify -CAfile ca-bundle.pem client.crtsucceeds locally but nginx still logs 495, the bundle nginx is actually loading isn’t the one you tested — check the resolvedssl_client_certificatepath and reload nginx. That’s a deployment/config drift issue, not a certificate defect. - If the client cert is valid and trusted but a proxy or load balancer sits in front, hand the direct-vs-proxied evidence to whoever owns that hop: they’re terminating and re-presenting a certificate the origin doesn’t trust.
- If 495 appears only after an OS or OpenSSL upgrade across a fleet, it’s a platform change, not a per-host problem — fix the CA bundle in your configuration management so every node gets the complete chain, rather than chasing hosts one at a time.
Related Tools
Related Guides
Share this guide