IPconf
Back to tutorials
Security 8 min read

SSL certificate troubleshooting checklist

Hostname match, expiry, chain, TLS versions, redirects, and mixed content — the order to check things in when a browser warning shows up.

Start from the browser error, not your local trust

Local browsers and `openssl` on your laptop have years of accumulated trust anchors, intermediate caches, and OS-specific bundles. A site that "works for me" can fail for everyone else.

Always test from an environment that does not share your trust store: a different machine, a different OS, an online checker, or a CI runner. Treat the browser warning as ground truth, not your local terminal output.

Confirm the hostname is covered

The certificate must list the exact hostname users opened — either in the Common Name (CN, deprecated but still common) or in the Subject Alternative Name (SAN) extension. Modern browsers ignore CN entirely and only honor SANs.

Wildcards (`*.example.com`) cover exactly one label. They match `www.example.com` and `api.example.com`, but not the apex `example.com` itself, and not `staging.api.example.com`. Multi-level wildcards are not allowed.

# List all hostnames the cert covers:
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -ext subjectAltName

Check expiry and renewal automation

Expired certs are the most public failure. Let's Encrypt is 90 days, commercial CAs commonly 397 days, and the industry is moving toward shorter lifetimes — automation is not optional.

Check the expiry, but also check whether renewal is actually running. A cert with 30 days left that has never been renewed is a time bomb. Monitor renewal logs, not just expiry dates.

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -dates
# notBefore=Jan 1 ...
# notAfter=Apr 1 ...

Verify the chain is complete

A cert is valid only if the client can build a chain to a trusted root. Servers must send the leaf and all intermediates; only the root lives in the client trust store. Forgetting to install the intermediate is the most common "works in Chrome, fails on iOS" bug.

Test from outside your trust store. Tools like `ssllabs.com/ssltest/` or `whatsmychaincert.com` report exactly which chain the server presents and what is missing.

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

# Verify chain reaches a trusted root:
curl -v https://example.com 2>&1 | grep -i "ssl\|tls\|verify"

TLS versions and cipher suites

TLS 1.0 and 1.1 are deprecated and removed from major browsers. TLS 1.2 is the minimum acceptable today; TLS 1.3 is the modern default. If you still allow 1.0/1.1, modern security scanners and compliance frameworks will flag you.

Weak cipher suites (RC4, 3DES, export-grade) should be gone. Most servers ship sane defaults now, but legacy configurations from older guides occasionally re-introduce them.

# Probe what protocols and ciphers the server supports:
nmap --script ssl-enum-ciphers -p 443 example.com

# Or with openssl:
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

Redirects and HSTS

Plain HTTP must redirect to HTTPS. Verify the redirect chain — sometimes there are extra hops through www-stripping or trailing-slash normalization that break in edge cases.

Once HTTPS works reliably, add HSTS so browsers refuse to fall back to HTTP. Be careful — the `preload` directive is essentially permanent. Test without preload first for at least a few weeks.

# Follow the redirect chain:
curl -sIL http://example.com | grep -i "location\|HTTP/"

# Check HSTS header:
curl -sI https://example.com | grep -i "strict-transport-security"

Mixed content

A valid certificate does not prevent the "not fully secure" indicator. If the HTML loads any image, script, stylesheet, or iframe over plain `http://`, browsers downgrade the lock icon.

Search your code for hardcoded `http://` URLs, and switch to protocol-relative (`//example.com/path`) or absolute HTTPS. Browser DevTools → Console will list mixed content warnings on page load.