SSL Handshake Errors: Fixing Handshake Failures
Before a browser or crawler can ask for a page over HTTPS, it has to agree a secure connection with your server. That exchange is the TLS handshake, still usually called SSL.
If the certificate has expired, names the wrong host, is missing part of its chain, or the two sides cannot agree on a protocol, the connection stops before any status code is sent. Visitors see a full-page warning and crawlers record a failed fetch. This guide shows how to read the error, test the certificate from the command line and fix each cause.
What happens in the handshake
When a visitor opens https://www.example.co.uk/, their browser and your server run through
a short negotiation before any page is requested:
- The browser says which TLS versions and encryption methods it supports, and which hostname it wants. Sending the hostname at this stage is called SNI, and it is how one server with one IP address can hold certificates for many sites.
- The server picks a version and method, and sends its certificate plus any intermediate certificates that link it to a trusted authority.
- The browser checks the certificate: is it in date, does it name this hostname, and does the chain lead to a root authority the browser already trusts?
- If everything checks out, both sides set up encryption and the ordinary HTTP request begins.
A failure at step 2 or 3 means there is no HTTP response at all, which is why an audit reports an SSL error rather than a status code. It also means one bad certificate breaks every page on that hostname at the same moment.
Reading the error message
Browsers name the specific check that failed. The name is the most useful clue you have, so copy it exactly before doing anything else.
| Chrome and Edge | Firefox | What failed |
|---|---|---|
NET::ERR_CERT_DATE_INVALID | SEC_ERROR_EXPIRED_CERTIFICATE | The certificate has expired, or is not valid yet. Can also be caused by a wrong clock on the visitor's device. |
NET::ERR_CERT_COMMON_NAME_INVALID | SSL_ERROR_BAD_CERT_DOMAIN | The certificate does not list the hostname in the address bar. |
NET::ERR_CERT_AUTHORITY_INVALID | SEC_ERROR_UNKNOWN_ISSUER | The browser cannot link the certificate to a trusted root: a self-signed certificate, or a missing intermediate. |
ERR_SSL_VERSION_OR_CIPHER_MISMATCH | SSL_ERROR_NO_CYPHER_OVERLAP | The server only offers old protocols or ciphers the browser no longer accepts. |
ERR_SSL_PROTOCOL_ERROR | SSL_ERROR_RX_RECORD_TOO_LONG | The server did not answer with TLS at all, often because port 443 is serving plain HTTP. |
Behind Cloudflare there are two more to know. Error 525 means Cloudflare could not complete a handshake with your origin server, and error 526 means the origin's certificate was not valid when Cloudflare's SSL mode is set to check it strictly. In both cases visitors' browsers are fine; the problem is between Cloudflare and your server.
Testing the certificate from the command line
Browsers cache intermediate certificates and sometimes fetch missing ones on their own, so a site
can look fine in your browser while failing for crawlers, older phones and command-line tools. openssl
shows exactly what the server sends.
# What does the server send for this hostname? Always include -servername.
openssl s_client -connect www.example.co.uk:443 -servername www.example.co.uk -showcerts </dev/null
# Expiry dates, subject and the hostnames the certificate covers
openssl s_client -connect www.example.co.uk:443 -servername www.example.co.uk </dev/null 2>/dev/null \
| openssl x509 -noout -dates -subject -issuer -ext subjectAltName
# Does a normal client accept it? curl fails loudly on chain and name problems
curl -sv -o /dev/null https://www.example.co.uk/ 2>&1 | grep -iE "SSL|certificate|subject|expire"
What to look for in the output:
notAfteris the expiry date. If it is in the past, the certificate needs renewing, and if it is within a few days, renewal has probably stopped working.subjectAltNamelists every hostname the certificate covers. Browsers ignore the older common name field, so the hostname must be here.- The chain.
-showcertsprints each certificate the server sends. A publicly issued certificate normally comes with at least one intermediate. If only one certificate appears, the chain is incomplete. Verify return codeat the end should be0 (ok). A code such as 21, "unable to verify the first certificate", confirms a missing intermediate.
-servername shows you the server's default certificate,
not the one for your site. If the test without it shows another domain's certificate, that is exactly
what very old clients without SNI support would see, but it is not what modern browsers receive.Expired certificates and failed renewals
Most expiry problems are really renewal problems. Free certificates from Let's Encrypt last 90 days and are meant to renew automatically, and the maximum lifetime of all publicly trusted certificates is being reduced in stages under CA/Browser Forum rules, starting in 2026. Manual renewal is becoming impractical, so the aim is a renewal process that works on its own and tells you when it does not.
Common reasons automatic renewal stops:
- The validation request cannot reach the server. Let's Encrypt's HTTP check fetches
a file under
/.well-known/acme-challenge/. A firewall, a country block, a deny rule for dotfolders or a maintenance page can all stop it. - DNS points somewhere else. After a server move, an old
AAAA(IPv6) record often still points at the previous server. The certificate authority may try IPv6 first and fail even though IPv4 is correct. - The renewal job is not running. Check that the certbot timer or cron job exists
and look at its log, usually under
/var/log/letsencrypt/. - The new certificate was issued but not loaded. The web server needs a reload after renewal. Without one it keeps serving the old certificate from memory until it expires.
- Nobody was told. Let's Encrypt stopped sending expiry reminder emails in 2025, so do not rely on them. Use your own monitoring.
# Certbot: list certificates and their expiry, then rehearse a renewal
sudo certbot certificates
sudo certbot renew --dry-run
# After a real renewal, reload the web server so it uses the new files
sudo systemctl reload nginx
If only one visitor sees a date error while everyone else is fine, check that visitor's device clock. A clock set months out causes a date error on every HTTPS site they visit.
Hostname mismatches
A certificate covers exactly the names listed in its subject alternative names, and nothing else. The most frequent gaps are:
- www and the bare domain. A certificate for
www.example.co.ukdoes not coverexample.co.uk. Even if the bare domain only redirects to www, the redirect happens after the handshake, so the bare domain needs its own name on the certificate. - Wildcards cover one level only.
*.example.co.ukcoversshop.example.co.ukbut notexample.co.ukitself and noteu.shop.example.co.uk. - Extra domains on the same site. Old brand names,
.comversions and parked domains that redirect to the main site all need valid certificates if anyone ever visits them over HTTPS. - The wrong virtual host. If a hostname has no HTTPS configuration of its own, the server answers with its default certificate, which belongs to another site. The fix is to add the hostname to the correct site, not to change the default.
# Issue one certificate covering both names with certbot and nginx
sudo certbot --nginx -d example.co.uk -d www.example.co.uk
Once both names have a valid certificate, redirect one to the other with a single 301. Redirect order and HTTPS redirects are covered in the HTTPS redirects guide.
Incomplete certificate chains
Your certificate is signed by an intermediate certificate, which is signed by a root that browsers trust. The server must send your certificate and the intermediates; the root is already on the visitor's device. If the intermediate is missing, desktop browsers that have seen it before may cope, while crawlers, API clients, some Android devices and command-line tools fail with an unknown issuer error. This is one of the most common reasons an audit reports an SSL error that you cannot reproduce in your own browser.
The fix is to point the server at the full chain file rather than the certificate on its own:
# nginx
ssl_certificate /etc/letsencrypt/live/example.co.uk/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.co.uk/privkey.pem;
# Apache 2.4.8 and later
SSLCertificateFile /etc/letsencrypt/live/example.co.uk/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.co.uk/privkey.pem
With a certificate bought from a commercial authority, you usually receive your certificate and a separate bundle of intermediates. For nginx, join them into one file with your certificate first and the intermediates after it. Hosting panels usually have a separate field for the intermediate or CA bundle; make sure it is filled in.
Protocol and cipher mismatches
TLS 1.0 and 1.1 are formally deprecated and current browsers refuse them. A server that only offers those versions, or only very old ciphers, produces a version or cipher mismatch error in every modern browser. The opposite problem also happens: a server locked down to TLS 1.3 only can refuse older but still reasonable clients, including some crawlers and business systems.
A sensible setting for most public sites in 2026 is TLS 1.2 and TLS 1.3 enabled, with nothing older:
# nginx
ssl_protocols TLSv1.2 TLSv1.3;
# Apache
SSLProtocol -all +TLSv1.2 +TLSv1.3
# Check which versions the server accepts
openssl s_client -connect www.example.co.uk:443 -servername www.example.co.uk -tls1_2 </dev/null
openssl s_client -connect www.example.co.uk:443 -servername www.example.co.uk -tls1_3 </dev/null
An ERR_SSL_PROTOCOL_ERROR with no certificate at all often means port 443 is answering
with plain HTTP, usually because the ssl parameter is missing from an nginx listen 443
line, or a load balancer is forwarding to the wrong port.
Behind a CDN
With Cloudflare or a similar CDN, there are two connections: visitor to CDN, and CDN to your server. Use a mode that encrypts and validates the second connection, called Full (strict) in Cloudflare, and give your origin a valid certificate, either a public one or the CDN's own origin certificate. A 525 means the origin handshake failed; check that the origin listens on 443 with TLS and accepts TLS 1.2 or later. A 526 means the origin certificate is expired, self-signed or for the wrong name.
The fix, step by step
Step 1
Read the exact error name
Open
the hostname the HTTP Error Audit flagged in a browser and note the precise error code, such as NET::ERR_CERT_DATE_INVALID
or SEC_ERROR_UNKNOWN_ISSUER. The name tells you which check failed: date, hostname, chain
or protocol. If the site sits behind Cloudflare and shows error 525 or 526, the problem is on the connection
from Cloudflare to your origin server rather than in the visitor's browser.
Step 2
Inspect the certificate the server sends
Run openssl s_client with -connect and -servername
set to the failing hostname, adding -showcerts to print the chain. Pipe it through openssl x509 -noout -dates -ext subjectAltName
to read the expiry date and the covered names. Check that the verify return code at the end is 0. Test
both the www and bare domain, because each can be configured separately.
Step 3
Renew an expired certificate and fix renewal
Renew the certificate through your host, panel or certbot renew, then
reload the web server so it loads the new file. Run certbot renew --dry-run to prove the
next renewal will work. If it fails, make sure /.well-known/acme-challenge/ is reachable
over HTTP, remove stale DNS records such as an old AAAA record, and confirm the renewal timer or cron
job exists.
Step 4
Cover every hostname you serve
Compare the subject alternative names on the certificate with every hostname that receives traffic: the bare domain, www, subdomains and any old domains that redirect. Reissue the certificate with all of them, remembering that a wildcard covers only one level and not the bare domain. Make sure each hostname is attached to the right site on the server, so nobody receives another site's default certificate.
Step 5
Serve the full certificate chain
Point
the server at the full chain rather than the leaf certificate alone: fullchain.pem for Let's
Encrypt, or your certificate followed by the intermediate bundle for a commercial one. In a hosting panel,
fill in the CA bundle or intermediate field. Reload the server and repeat the openssl s_client -showcerts
test to confirm that more than one certificate is sent and verification returns 0.
Step 6
Enable TLS 1.2 and TLS 1.3
Set
the server to offer TLS 1.2 and TLS 1.3 only, with ssl_protocols TLSv1.2 TLSv1.3 in nginx
or SSLProtocol -all +TLSv1.2 +TLSv1.3 in Apache, and remove any hand-picked list of old
ciphers copied from an outdated guide. Check that nginx's listen 443 line includes ssl.
Behind a CDN, use a strict mode that validates the origin certificate, and make sure the origin accepts
TLS 1.2 or later.
Step 7
Retest from outside your own network
Repeat the curl and openssl checks from a different machine, or use an online TLS checker, because your own browser may have cached intermediates. Test every hostname you fixed, then re-run the HTTP Error Audit so each URL is fetched again. Finally, set up expiry monitoring that alerts you a couple of weeks before any certificate runs out, since renewal failures are otherwise silent.
Keeping certificates healthy
- Automate renewal everywhere. Every hostname that serves HTTPS should renew automatically, including redirect-only domains and subdomains used for email links or campaigns.
- Monitor expiry independently. Use an external check that warns you well before expiry, rather than relying on your certificate authority or host to email you.
- Reload after renewal. Make sure the renewal process reloads the web server, for example with a certbot deploy hook.
- Keep the challenge path open. When adding firewall rules, maintenance pages or dotfolder
blocks, leave
/.well-known/reachable. - Tidy DNS after moves. Remove or update A and AAAA records that point at old servers.
- Fix mixed content too. Once the certificate is sound, pages that load images or scripts over plain HTTP still show warnings; the mixed content guide covers that separately.
1. What does SSL handshake failed mean?
It means the browser or crawler and your server could not agree a secure connection, so no page was ever requested. The usual causes are an expired certificate, a certificate that does not list the hostname being visited, a missing intermediate certificate so the chain cannot be verified, or a server offering only old TLS versions. Behind Cloudflare, error 525 is the specific message for a failed handshake between Cloudflare and your own server rather than the visitor.
2. Why does my site work in my browser but the audit reports an SSL error?
The most common reason is a missing intermediate certificate. Desktop browsers often remember intermediates from other sites or fetch them automatically, so they cope, but crawlers, command-line tools and some mobile devices do not. Test with openssl s_client and the -showcerts option: if only one certificate is sent, configure the server with the full chain file. Another cause is a hostname, such as the bare domain, that you rarely visit yourself.
3. Does my SSL certificate need to cover both www and non-www?
Yes, if both addresses can be reached, which is almost always the case. The certificate is checked before any redirect happens, so a visitor typing the bare domain sees a warning even if the server would have redirected them to www. Issue one certificate listing both names, or separate certificates for each, and then redirect one to the other. Remember that a wildcard certificate does not cover the bare domain.
4. Why did my Let's Encrypt certificate expire when renewal is automatic?
Automatic renewal can fail silently. The validation request may be blocked by a firewall or a deny rule for the .well-known folder, an old IPv6 record may point at a previous server, the renewal timer may have been removed during a server change, or the certificate may have renewed without the web server being reloaded. Run certbot renew with the dry-run option to see the error, and add your own expiry monitoring.
5. How do I fix NET::ERR_CERT_COMMON_NAME_INVALID?
The certificate your server sent does not include the hostname in the address bar. Check the subject alternative names with openssl, and compare them with the address being visited. Either the certificate needs reissuing with the missing name, such as the bare domain or a subdomain, or the hostname is not attached to the right site on the server, so it receives the default certificate of another site. Fix whichever applies and test again.
6. What causes Cloudflare error 525 or 526?
Both are about the connection between Cloudflare and your origin server, not the visitor. A 525 means the TLS handshake with your origin failed, often because the origin is not listening for HTTPS on port 443 or only supports old protocols. A 526 appears when Cloudflare is set to validate the origin strictly and the origin certificate is expired, self-signed or issued for another name. Install a valid public or Cloudflare origin certificate on the server.
7. Which TLS versions should my server support?
For most public websites, enable TLS 1.2 and TLS 1.3 and disable everything older. TLS 1.0 and 1.1 are formally deprecated and modern browsers refuse them, so keeping them adds risk without helping real visitors. Disabling TLS 1.2 as well is usually too strict for now, because some crawlers, older devices and business systems still rely on it. Test the result with openssl using the tls1_2 and tls1_3 options, from a machine outside your own network.
The HTTP Error Audit requests every URL in your crawl scope, records the status code and response time, follows redirects and flags anything outside the 200 range, including handshake failures that stop a page before any status code is sent.
Open HTTP Error Fixes โ