The browser shows "Your connection is not private", "The server certificate is not trusted" or the code NET::ERR_CERT_DATE_INVALID. Behind such messages stands one of five causes, and openssl identifies each of them in a minute. Simply viewing a certificate is covered in the article How to check an SSL certificate. Here we go through the causes of the error and the ways to fix it.
Quick diagnosis from the server
First find out what exactly the verifying side dislikes. The command below connects to the site the way a browser does and prints the verification code, the certificate owner and the issuer.
openssl s_client -connect example.com:443 -servername example.com -verify_hostname example.com </dev/null 2>/dev/null | grep -E "Verify return code|subject=|issuer="
The code on the Verify return code line points to the cause. Compare it with the table.
| Cause | openssl verification code | Where it shows up |
|---|---|---|
| Certificate has expired | 10 (certificate has expired) | Everywhere, for all visitors |
| Self-signed certificate | 18 (self-signed certificate) | Everywhere, for all visitors |
| Intermediate certificate missing | 20 or 21 (unable to get local issuer certificate / unable to verify the first certificate) | Often only on phones, in curl and in applications |
| Name does not match the domain | 62 (Hostname mismatch) | Everywhere, for all visitors |
| Wrong clock on the client | 0 (ok) on the server | Only for one user or device |
Cause 1: the certificate has expired
The most common cause. A certificate is valid for a limited time, and after the end date the browser stops trusting it. Two commands show the date and the remaining time: the first prints notAfter, the second exits with a non-zero code if the certificate has already expired.
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -enddate
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -checkend 0
The fix is to reissue the certificate and reload the web server. If you use Let's Encrypt, the cause is almost always broken auto-renewal. The setup is described in the article Nginx and Certbot. Make sure renewal completes without errors, then reload the configuration.
sudo certbot certificates
sudo certbot renew --dry-run
sudo systemctl reload nginx
Cause 2: the certificate is self-signed
A self-signed certificate was issued by the server itself and vouched for by nobody, so openssl shows code 18, and the subject and issuer lines hold identical values. This is fine for a test environment or an internal service, but not for a public site: visitors will see a warning.
The fix for a public site is to obtain a certificate from a trusted certificate authority. The differences between certificate kinds are covered in the article on DV, OV and EV certificate types. For an internal service you can add your own root certificate to the trusted ones on the clients.
Cause 3: the intermediate part of the chain was not sent
A certificate authority signs your certificate not with its root key but with an intermediate one. The server must hand the client its own certificate together with the intermediates. Desktop browsers often rebuild the missing link on their own, so everything opens on a computer, while a phone, curl or a third-party application fails.
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null | grep -E "^ *[0-9] s:|^ *i:|Verify return code"
The output below should contain at least two links: your certificate (line 0) and an intermediate one (line 1). If there is a single link and the output ends with code 20 or 21, the chain is incomplete.
- In Nginx, point ssl_certificate to the file with the full chain (fullchain.pem), not to a single certificate.
- In Apache, use the chain file supplied by the certificate authority in the SSLCertificateChainFile directive, or merge the chain into the main file.
- After the change, reload the service and repeat the command: the code should become 0.
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
Cause 4: the name in the certificate does not match the domain
The certificate was issued for different names than the ones used to open the site. Typical cases: a certificate for example.com while the site opens as www.example.com; the certificate of an old domain after a move; access by IP address. Modern clients look only at the Subject Alternative Name field, not at Common Name.
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
The fix is to reissue the certificate so that it contains all the required names, or to set up a redirect to the name that is covered. The installation procedure is described in the article connecting SSL.
Cause 5: the clock on the client is wrong
A certificate is valid from one date to another. If the device has a wrong date, the browser treats a valid certificate as not yet started or already expired. The sign: the error appears on one computer or phone only, while the check on the server passes with code 0. The time on the server itself can be checked like this.
date -u
timedatectl status
- On the client, enable automatic date and time and check the time zone.
- On the server, set up time synchronization, otherwise client certificate checks and token signatures will fail too.
- If the error disappeared after fixing the clock, the certificate was not at fault.
Frequently asked questions
What does "the server certificate is not trusted" mean?
The client could not build a chain from the site certificate to a root certificate it trusts. The causes are a self-signed certificate, a missing intermediate link or an unknown certificate authority. The verification code in openssl s_client shows which case you are dealing with and hints at the fix.
Why does the site open on a computer but fail on a phone?
Most often the server does not send the intermediate certificate. A desktop browser can rebuild the chain from its cache or from a link inside the certificate, while mobile clients, curl and applications do not. Connect the full chain (fullchain) in the web server settings and reload the service.
How can I tell that the certificate has simply expired?
Run openssl x509 with the -enddate option on the server response, or look for code 10 on the Verify return code line. An end date in the past means an expired certificate. Reissue it and reload the web server. If you use auto-renewal, find in the logs why it did not work.
Can I just click "proceed to the site anyway"?
For someone else's public site you should not: if the certificate is substituted, a third party can intercept the traffic. For your own test environment it is acceptable, but it is better to issue a proper certificate or add your own root certificate to the trusted ones on your devices.