# OpenSSL error messages explained: diagnose and fix TLS errors

> Common OpenSSL 3.x errors with diagnosis commands and fixes: local issuer, expired, hostname mismatch, wrong version number, PKCS#12.

Source: https://ssl.sh/cookbook/troubleshooting/
Last updated: 2026-11-10

Each common OpenSSL error message below comes with the command that diagnoses it and the fix. Messages are quoted as OpenSSL 3.6.1 prints them; where 1.1.1 or 3.0 wording differs, both are given. For a public website, start with the [SSL Checker](https://ssl.sh/tools/ssl-checker/); use these commands to see exactly what OpenSSL sees.

## Decode any OpenSSL error code

`openssl errstr` turns the hex code in an error line into its reason string.

```bash
openssl errstr 0A000086 0A000410 0308010C
```

```text
error:0A000086:SSL routines::certificate verify failed
error:0A000410:SSL routines::ssl/tls alert handshake failure
error:0308010C:digital envelope routines::unsupported
```

A full line looks like `80FF14F601000000:error:0A000086:SSL routines:tls_post_process_server_certificate:certificate verify failed:ssl/statem/statem_clnt.c:2126:`. The first field is a thread ID, then the code, library, function and reason. For `certificate verify failed`, the actual cause is the `verify error:num=` line printed directly above it.

## Fix "unable to get local issuer certificate" (error 20)

The client cannot find the certificate that signed the leaf; usually the server omits the intermediate, sometimes the client lacks the root.

```bash
openssl s_client -connect www.example.com:443 -servername www.example.com -showcerts < /dev/null 2>&1 \
  | grep -E 'verify error| [0-9] s:|Verify return'
```

```text
verify error:num=20:unable to get local issuer certificate
verify error:num=21:unable to verify the first certificate
 0 s:CN=www.example.com
Verify return code: 21 (unable to verify the first certificate)
```

One certificate in the chain means the server is missing its intermediate: configure the full chain (`fullchain.pem` for Certbot, leaf plus CA bundle for commercial certificates). If the chain is complete but the error persists, the client's trust store lacks the root: update `ca-certificates` or pass `-CAfile`. Same root cause as [curl error 60](https://ssl.sh/errors/curl-60-ssl-certificate-problem/) and [missing intermediate certificate](https://ssl.sh/errors/missing-intermediate-certificate/).

## Fix "certificate has expired" (error 10) and "not yet valid" (error 9)

Compare the certificate dates with the clock of the machine doing the check.

```bash
openssl s_client -connect www.example.com:443 -servername www.example.com -showcerts < /dev/null 2>/dev/null \
  | grep -E ' [0-9] s:|v:NotBefore'
date -u
```

```text
 0 s:CN=www.example.com
   v:NotBefore: Nov  3 13:44:34 2026 GMT; NotAfter: Feb  1 13:44:33 2027 GMT
 1 s:C=CN, O=Example Corp, CN=Example Intermediate CA
   v:NotBefore: Sep  3 00:00:00 2025 GMT; NotAfter: Sep  2 23:59:59 2028 GMT
Tue Nov 10 02:30:00 UTC 2026
```

If the leaf expired, renew it and reload the server: a renewed file on disk does nothing until Nginx or Apache reloads. If an intermediate in the served chain expired, replace the bundle with the CA's current one. Error 9 or an expiry error on a valid certificate points to a wrong system clock, common on embedded devices without NTP. Browsers show this as [NET::ERR_CERT_DATE_INVALID](https://ssl.sh/errors/net-err-cert-date-invalid/).

## Fix "hostname mismatch" (error 62)

The name you connect to is not in the certificate's Subject Alternative Name.

```bash
openssl s_client -connect www.example.com:443 -servername shop.example.com \
  -verify_hostname shop.example.com < /dev/null 2>/dev/null | grep -E 'verify error|Verify return'
openssl x509 -in cert.pem -noout -ext subjectAltName
```

```text
verify error:num=62:hostname mismatch
Verify return code: 62 (hostname mismatch)
X509v3 Subject Alternative Name: 
    DNS:www.example.com, DNS:example.com
```

Either reissue the certificate with the missing name in the SAN, or fix the client to use a name that is listed. A wildcard covers exactly one label. If the certificate shown belongs to a completely different site, the server picked a default certificate because SNI was missing or the virtual host is not configured for that name. Browser equivalent: [NET::ERR_CERT_COMMON_NAME_INVALID](https://ssl.sh/errors/net-err-cert-common-name-invalid/).

## Fix "self-signed certificate in certificate chain" (error 19)

The chain ends in a root the client does not trust, typically a private CA or a TLS-inspecting proxy.

```bash
openssl s_client -connect www.example.com:443 -servername www.example.com < /dev/null 2>&1 \
  | grep -E '^depth|verify error' | head -4
```

```text
depth=2 C=CN, O=Example Corp, CN=Example Root CA
verify error:num=19:self-signed certificate in certificate chain
depth=1 C=CN, O=Example Corp, CN=Example Intermediate CA
depth=0 CN=www.example.com
```

If the root at the top is your organisation's CA, add it to the client's trust store (see [running a private CA](https://ssl.sh/cookbook/private-ca/)) instead of disabling verification. If it is an unexpected corporate or antivirus CA, traffic is being intercepted. Error 18 (`self-signed certificate`) means the leaf itself is self-signed. OpenSSL 1.1.1 spelled these without a hyphen (`self signed`). More in [self-signed certificate in certificate chain](https://ssl.sh/errors/self-signed-certificate-in-certificate-chain/).

## Fix "wrong version number" and "packet length too long"

These mean the other side did not answer with TLS at all, most often because you hit a plain HTTP port.

```bash
openssl s_client -connect www.example.com:80 < /dev/null
curl -sI http://www.example.com:443/ | head -1
```

```text
...:error:0A0000C6:SSL routines:tls_get_more_records:packet length too long:...
...:error:0A000139:SSL routines::record layer failure:...
HTTP/1.1 200 OK
```

OpenSSL 1.1.1 and 3.0 typically report `wrong version number`; newer releases (as tested with 3.6) may report `packet length too long` for the same situation. The curl line confirms that port 443 is answering plain HTTP. Check the `listen 443 ssl` directive in Nginx, the port in your client configuration, or whether a proxy expects `CONNECT` first. Mail clients see the same when they use implicit TLS on a STARTTLS port (587 vs 465).

## Fix "handshake failure" (alert 40) and "no shared cipher"

Client and server found no common protocol version, cipher suite, group or signature algorithm.

```bash
openssl s_client -connect www.example.com:443 -servername www.example.com < /dev/null 2>&1 | grep -m1 alert
openssl s_client -connect www.example.com:443 -servername www.example.com -tls1_2 -cipher 'ALL:@SECLEVEL=0' -brief < /dev/null
```

```text
...:error:0A000410:SSL routines:ssl3_read_bytes:ssl/tls alert handshake failure:...:SSL alert number 40
```

On the server side the matching log line is `no shared cipher`. Typical causes: a cipher list that excludes everything the client offers, an ECDSA-only certificate with a client that only accepts RSA, an over-restrictive `ssl_ecdh_curve`/groups setting, or a TLS 1.2 server that requires a client certificate. Older releases such as 1.1.1 print `sslv3 alert handshake failure` for the same alert. Browsers show [ERR_SSL_VERSION_OR_CIPHER_MISMATCH](https://ssl.sh/errors/err-ssl-version-or-cipher-mismatch/); compare lists with [ciphers and protocols](https://ssl.sh/cookbook/ciphers-and-protocols/).

## Fix "no protocols available" and "alert protocol version" (alert 70)

`no protocols available` is your own client refusing; `alert protocol version` is the server refusing the version you offered.

```bash
openssl s_client -connect www.example.com:443 -tls1_1 < /dev/null
openssl s_client -connect www.example.com:443 -tls1_1 -cipher 'DEFAULT:@SECLEVEL=0' < /dev/null
```

```text
...:error:0A0000BF:SSL routines:tls_setup_handshake:no protocols available:...
...:error:0A00042E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version:...:SSL alert number 70
```

OpenSSL 3.x disables TLS 1.0/1.1 at the default security level, so legacy clients built on it cannot reach old servers until the server supports TLS 1.2. If an old device must keep working, upgrade it; lowering `@SECLEVEL` is a stopgap. Browsers show [ERR_SSL_PROTOCOL_ERROR](https://ssl.sh/errors/err-ssl-protocol-error/) or a version mismatch error.

## Fix "key values mismatch"

The certificate and private key do not belong together, or the chain file starts with the intermediate instead of the leaf.

```bash
openssl x509 -in fullchain.pem -noout -subject
openssl x509 -in fullchain.pem -noout -pubkey | openssl sha256
openssl pkey -in privkey.pem -pubout | openssl sha256
```

```text
subject=C=CN, O=Example Corp, CN=Example Intermediate CA
SHA2-256(stdin)= d6f7b12a13ec99a751aad52deee4ce9339710d60fb07acefa86eb1e8d753648d
SHA2-256(stdin)= 3152719d149366da47e37904e2740fb6d296ade304092ce5308560b45d75e458
```

Here the first certificate in the file is the intermediate, so Nginx compares the wrong public key: put the leaf first (`cat leaf.pem intermediate.pem > fullchain.pem`). If the subject is right but the hashes differ, the key belongs to another certificate, often an older CSR; find the matching key or reissue. Nginx reports this as [key values mismatch](https://ssl.sh/errors/nginx-key-values-mismatch/).

## Fix "unsupported" when reading a PKCS#12 file

OpenSSL 3.x moved RC2 and other old algorithms to the legacy provider; read old `.pfx`/`.p12` files with `-legacy`.

```bash
openssl pkcs12 -in old.pfx -info -noout
openssl pkcs12 -in old.pfx -info -noout -legacy
```

```text
MAC: sha1, Iteration 2048
MAC length: 20, salt length: 16
PKCS7 Encrypted data: pbeWithSHA1And40BitRC2-CBC, Iteration 2048
Error outputting keys and certificates
...:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:...:Algorithm (RC2-40-CBC : 0), Properties ()
...
Certificate bag
Certificate bag
```

The second command succeeds. To modernise the file, extract with `-legacy` and re-export without it, which produces AES-256-CBC with PBKDF2 (the 3.0+ default). Do the opposite (`-export -legacy`) only when an old consumer cannot read modern files. See [PKCS#12 commands](https://ssl.sh/cookbook/pkcs12/).

## Fix "Mac verify error: invalid password?"

The PKCS#12 password is wrong, or the file has an empty password and you supplied none (or vice versa).

```bash
openssl pkcs12 -in bundle.p12 -noout -passin pass:wrong
openssl pkcs12 -in bundle.p12 -noout -passin pass:
```

```text
Mac verify error: invalid password?
```

The second form passes an explicitly empty password, which some tools (Windows export without password, Java keystores converted with empty passwords) produce. If neither works and the file came from Windows or a Java keystore, check whether it was exported with a different password for the key and the store. Never put real passwords on the command line on shared machines; use `-passin file:pass.txt` or `env:VAR`.

## Fix "bad decrypt" when loading a private key

The passphrase for an encrypted private key is wrong.

```bash
openssl pkey -in privkey.pem -noout
head -2 privkey.pem
```

```text
Could not find private key of key from privkey.pem
...:error:1C800064:Provider routines:ossl_cipher_unpadblock:bad decrypt:...
...:error:11800074:PKCS12 routines:PKCS12_pbe_crypt_ex:pkcs12 cipherfinal error:...:maybe wrong password
-----BEGIN ENCRYPTED PRIVATE KEY-----
```

`ENCRYPTED PRIVATE KEY` (PKCS#8) or a `Proc-Type: 4,ENCRYPTED` header (traditional format) confirms the key is encrypted. Web servers cannot prompt for a passphrase at startup, so after confirming the right one, write an unencrypted copy with restrictive permissions: `openssl pkey -in privkey.pem -out privkey-plain.pem && chmod 600 privkey-plain.pem`.

## Fix "Could not find certificate" and "no start line"

OpenSSL could not find a PEM block of the expected type: wrong file, DER instead of PEM, or damaged content.

```bash
file cert.pem
head -1 cert.pem
openssl x509 -in cert.pem -noout -subject
openssl asn1parse -in cert.pem | head -3
```

```text
cert.pem: ASCII text
-----BEGIN CERTIFICATE-----
Could not find certificate from cert.pem
...:error:1E08010C:DECODER routines:OSSL_DECODER_from_bio:unsupported:...:No supported data to decode. Input structure: Certificate
...:error:0680009B:asn1 encoding routines:ASN1_get_object:too long:...
```

OpenSSL 1.1.1 prints `unable to load certificate ... no start line` instead. `asn1parse` failing with `too long` or `header too long` means the Base64 body is truncated or edited, typically by a copy-paste that dropped a line; get the file again. If `file` says `data` or `Certificate`, it is DER: add `-inform DER`. OpenSSL 3.6 itself tolerates a UTF-8 BOM and Windows CRLF line endings, but Java, Go and some control panels do not; normalise with `dos2unix cert.pem` or `sed -i '1s/^\xEF\xBB\xBF//' cert.pem`. Feeding a key where a certificate is expected gives the same error, so check the header line. Conversions are in [converting formats](https://ssl.sh/cookbook/convert-formats/).

## Fix "unknown ca" (alert 48) and "certificate required" (alert 116)

In mutual TLS these alerts come from the server: it rejected the client certificate, or it got none.

```bash
openssl s_client -connect api.example.com:443 -servername api.example.com -brief < /dev/null 2>&1 | grep alert
openssl s_client -connect api.example.com:443 -servername api.example.com \
  -cert client.crt -key client.key -brief < /dev/null 2>&1 | grep alert
```

```text
...:error:0A00045C:SSL routines:ssl3_read_bytes:tlsv13 alert certificate required:...:SSL alert number 116
...:error:0A000418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca:...:SSL alert number 48
```

Run s_client without `-brief` and read `Acceptable client certificate CA names` to see which CAs the server accepts, then present a certificate issued by one of them, including its intermediate (`-cert_chain`). Public TLS certificates increasingly lack the clientAuth EKU, so servers that check EKU reject them; see [client auth EKU removed](https://ssl.sh/errors/client-auth-eku-removed/). Servers log the matching `certificate verify failed` with the exact reason.
