SSL Certificate Errors: How to Diagnose and Fix Them

Updated 11 September 2026 · 7 min read

Check your own site as you read: SSL Checker · Redirect Checker

A certificate warning means the browser refused to trust the TLS connection to your site. Most visitors leave at that point, and if you have an HSTS policy in place, they can't click past the warning at all. The good news is that nearly every certificate error falls into a small number of categories, each with a specific cause and a specific fix. This guide covers them in the order you should check them.

Start with a quick diagnosis

Before you touch server configuration, confirm what is actually wrong. The error code in the browser is the first clue:

Problem Chrome Firefox
Expired or not yet valid NET::ERR_CERT_DATE_INVALID SEC_ERROR_EXPIRED_CERTIFICATE
Name mismatch NET::ERR_CERT_COMMON_NAME_INVALID SSL_ERROR_BAD_CERT_DOMAIN
Self-signed or unknown issuer NET::ERR_CERT_AUTHORITY_INVALID SEC_ERROR_UNKNOWN_ISSUER or MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT

One caveat: a date error can also appear when the visitor's device clock is wrong. If only one person reports it, check their clock before you check your server.

Next, look at what the server really sends. Our SSL Checker shows the certificate, its validity dates, the names it covers and the chain. From a terminal, openssl gives you the same information:

# Show the full chain the server sends
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null

# Show validity dates and covered names on the leaf certificate
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -dates -subject -ext subjectAltName

Always pass -servername. Servers that host many sites use SNI to pick a certificate, and without it you may get a default certificate that has nothing to do with your domain, which sends you chasing the wrong problem.

Expired certificate

Cause: renewal never ran, or it ran but the web server kept serving the old certificate from memory.

Fix:

  1. Renew the certificate through your ACME client, hosting panel or CA.
  2. Reload the web server (for example systemctl reload nginx). Many servers only read certificate files at startup.
  3. Re-check the live certificate, not the file on disk. A renewed file that the server isn't using still produces the same error.

Remember that every hostname has its own certificate status. The main site may be fine while api.example.com or mail.example.com expired last night.

Name mismatch

Browsers match the hostname in the address bar against the Subject Alternative Name (SAN) list in the certificate. If the name isn't on the list, you get a mismatch error. Common causes:

  • Apex versus www. A certificate for example.com does not cover www.example.com, and the reverse is also true.
  • Wildcard scope. *.example.com covers shop.example.com but not example.com itself, and not a.b.example.com.
  • New subdomains. Someone added blog.example.com in DNS but never issued a certificate for it.
  • Shared hosting or CDN defaults. The server answers with a platform certificate because your custom domain isn't configured on it.

Fix: reissue the certificate with every name you serve. With Certbot, list each one: certbot -d example.com -d www.example.com.

A point people often miss: if www.example.com redirects to example.com, the www name still needs a valid certificate. The TLS handshake happens before the server can send the redirect. Use the Redirect Checker to walk every hop from http:// and www variants and confirm that each one resolves cleanly over HTTPS. Our guide to 301 vs 302 redirects covers which status code to use.

Incomplete chain (missing intermediate)

Public certificates are rarely signed directly by a root. Your leaf certificate is signed by an intermediate, and the intermediate is signed by a root that the browser trusts. The server must send the leaf plus the intermediates.

Symptom: the site loads in your desktop browser, but fails on some phones, in curl, in API clients or for crawlers. Some browsers fill the gap by fetching or caching intermediates. Many other clients don't.

Fix: configure the full chain. With Let's Encrypt on Nginx, point to fullchain.pem, not cert.pem:

ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;  # leaf + intermediates
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

On Apache 2.4.8 and later, the file in SSLCertificateFile can contain the leaf followed by its intermediates. Once you've reloaded, run the -showcerts command again and confirm you see more than one certificate.

Self-signed or untrusted certificate authority

A self-signed certificate is fine for local development and internal test boxes. On a public site it will always trigger a warning, because no browser trusts it.

The same error appears when a certificate comes from a private CA, or from a CA that browsers no longer trust. Browser root programs have removed CAs in the past, and certificates chained to them stop validating.

Fix: use a publicly trusted certificate. Free, automated certificates from Let's Encrypt and other ACME-based CAs work for almost every site. For internal-only systems, you can run a private CA, but you must install its root on every managed device that needs to connect.

Mixed content

Mixed content isn't a certificate failure, but it breaks the padlock and it can break the page. It happens when an HTTPS page loads subresources over plain HTTP. According to MDN's mixed content reference, browsers automatically upgrade some "upgradable" resources such as images, audio and video. They block "blockable" resources outright, including scripts, stylesheets, iframes, fetch() requests and web fonts. A blocked stylesheet or script can leave a page unstyled or non-functional.

Fix:

  1. Open the browser console. Mixed content warnings list each insecure URL.
  2. Fix the source: theme templates, hardcoded asset URLs, and content stored in your CMS database. On a site migrated from HTTP, old post content is a frequent culprit.
  3. Change http:// references to https://, or to relative URLs for your own assets.
  4. As a temporary safety net, send Content-Security-Policy: upgrade-insecure-requests. It tells browsers to request HTTP subresources over HTTPS. It doesn't fix a third-party host that has no HTTPS at all.

A crawl with the Broken Link Checker helps you find old http:// links that fail once you upgrade them.

HSTS issues

HTTP Strict Transport Security tells browsers to use only HTTPS for your host for max-age seconds. MDN's HSTS documentation points out an important consequence: visitors can't click through certificate errors on an HSTS host. That's the security benefit, but it also means a certificate problem locks every visitor out until you fix it.

Two directives cause most trouble:

  • includeSubDomains applies the policy to every subdomain. If any subdomain lacks a valid certificate, such as an old staging box or an internal tool, it becomes unreachable in browsers that saw the header.
  • preload asks browsers to ship your domain in their built-in HSTS list. The HSTS preload site warns that inclusion can't easily be undone. Removal takes months to reach users through browser updates.

A safe rollout increases the policy in stages:

# Stage 1: short policy while you verify everything
Strict-Transport-Security: max-age=300

# Stage 2: longer policy once the main host is stable
Strict-Transport-Security: max-age=31536000

# Stage 3: only after every subdomain serves valid HTTPS, and only if you are
# committed to HTTPS long term
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

If you need to back out a policy, send max-age=0. Browsers only honor the header over HTTPS, so you need a valid certificate in place to do it.

Automate renewal so this doesn't happen again

Expired certificates are almost always a process failure, not a technical one. The Let's Encrypt FAQ states that its default certificates are valid for 90 days and recommends renewing them every 60 days. It also offers opt-in six-day certificates. Public certificate lifetimes are getting shorter across the industry, so manual renewal on a calendar reminder isn't a viable plan.

A reliable setup has four parts:

  1. An ACME client that renews on a schedule. Certbot installs a scheduled task on most systems. Test it with sudo certbot renew --dry-run.
  2. A reload hook. Renewal is useless if the server keeps the old certificate in memory. For example: certbot renew --deploy-hook "systemctl reload nginx".
  3. External monitoring of the live certificate. Check what the server actually serves on each hostname and alert well before expiry, for example when fewer than 14 days remain. Monitoring the file on disk misses the reload problem.
  4. DNS that allows issuance. A CAA record that doesn't list your CA will block issuance and renewal. Check your CAA and other records with a DNS lookup, and read DNS records explained if CAA is new to you.

Managed platforms and CDNs usually renew for you, but they still depend on correct DNS. After any DNS or hosting change, verify certificates on every hostname.

Quick reference checklist

  • Check the live certificate with SNI, not only the file on disk.
  • Confirm every served hostname, including www and redirect-only names, is in the SAN list.
  • Serve the full chain (fullchain.pem or the equivalent).
  • Use a publicly trusted CA for anything public.
  • Remove every http:// subresource from templates and stored content.
  • Roll out HSTS gradually. Add includeSubDomains and preload last.
  • Automate renewal, reload after renewal, and monitor expiry externally.