How the tools word it
| Node 26 tls | DEPTH_ZERO_SELF_SIGNED_CERT: self-signed certificate; if the root CA is installed locally, try running Node.js with --use-system-ca |
|---|---|
| OpenSSL 3.6 s_client | verify error:num=18:self-signed certificate |
| OpenSSL 3.6 verify | error 18 at 0 depth lookup: self-signed certificate |
| curl 8.23 | curl: (60) SSL certificate OpenSSL verify result: self-signed certificate (18) |
| Python 3.14 ssl | ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1082) |
Common causes
The server uses a certificate it made for itself
Issuer and subject are the same name, and no other certificate comes with it. The Certificate Decoder says Same as the subject and Self-signed. A development server, a router or an appliance does this out of the box.
Input
-----BEGIN CERTIFICATE----- MIIBhDCCASmgAwIBAgIBBjAKBggqhkjOPQQDAjAWMRQwEgYDVQQDDAtzZXJ2ZXIu dGVzdDAeFw0yNjAxMDEwMDAwMDBaFw0zNjAxMDEwMDAwMDBaMBYxFDASBgNVBAMM C3NlcnZlci50ZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5AQZINGTVGz2 E4ip0sTj/Q9uZjvCjo8aBDmw2fAZGPEnFKRN8E1iv4qf5LuNdsavJmMZ62Ky4XY9 xCKZleexTaNoMGYwHQYDVR0OBBYEFAcRP2dk59U5vcQBSxUqur3W1NImMB8GA1Ud IwQYMBaAFAcRP2dk59U5vcQBSxUqur3W1NImMAwGA1UdEwEB/wQCMAAwFgYDVR0R BA8wDYILc2VydmVyLnRlc3QwCgYIKoZIzj0EAwIDSQAwRgIhAKsK71tbzp8UA4fs VaX3xgi2yYNRRo211NJ6TpSuV2qMAiEA16zHBGn60FM11g6YiRwqXdsVL5oW6Tf+ 3Z5agmDeGNM= -----END CERTIFICATE-----
Certificate Decoder says
Issuer · Same as the subject · Self-signed
A self-signed root is normal at the top of a chain
Every chain ends in a root that signed itself, and the Decoder says the same for it. That is not the problem; trust is. A server certificate is the odd one out: no root ever vouches for it. If the self-signed certificate sits above a server certificate, the wording is self-signed certificate in certificate chain.
Input
-----BEGIN CERTIFICATE----- MIIBwzCCAWqgAwIBAgIBATAKBggqhkjOPQQDAjA5MRgwFgYDVQQKDA9mam9yZHRv b2xzIHRlc3QxHTAbBgNVBAMMFGZqb3JkdG9vbHMgdGVzdCByb290MB4XDTI2MDEw MTAwMDAwMFoXDTM2MDEwMTAwMDAwMFowOTEYMBYGA1UECgwPZmpvcmR0b29scyB0 ZXN0MR0wGwYDVQQDDBRmam9yZHRvb2xzIHRlc3Qgcm9vdDBZMBMGByqGSM49AgEG CCqGSM49AwEHA0IABH0DdyM4j2Qm5yUYrP9opJIyAW5oKmYs1KlgD5QaEspetzDt LmYCaEdSk051mmCgtov0ROqWiElrHplTLeJ0vQWjYzBhMB8GA1UdIwQYMBaAFLcg 9Ms28bssX2hEC2iuZ/lj9P4oMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD AgEGMB0GA1UdDgQWBBS3IPTLNvG7LF9oRAtormf5Y/T+KDAKBggqhkjOPQQDAgNH ADBEAiBJvWgbvsYiq9JtmZfKyepnmBnyzFQ93G+Tu9MHiZSt9wIgLz6fgDWJkUSD djhBBe3H4vsZwwTf+tNiFatZWY+Bkno= -----END CERTIFICATE-----
Certificate Decoder says
Issuer · Same as the subject · Self-signed
A device in the path replaced the certificate
A firewall, a login page or an appliance that intercepts a connection to a public site can answer with a certificate of its own. If a public site gives this error on one network only, suspect that, and compare with another network.
How to fix it
- If you control the server, use a certificate from a CA: a free ACME certificate for a public name, or one from your own CA for an internal name.
- To trust one specific certificate, give the client that file: curl --cacert cert.pem, NODE_EXTRA_CA_CERTS=cert.pem for Node, SSL_CERT_FILE=cert.pem for Python and OpenSSL, or openssl s_client -CAfile cert.pem.
- To make a development certificate with its name inside, run openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -keyout key.pem -out cert.pem -days 30 -subj "/CN=server.test" -addext subjectAltName=DNS:server.test, then trust cert.pem as above. Without the subjectAltName a browser refuses the name.
- Do not switch checking off. curl -k and NODE_TLS_REJECT_UNAUTHORIZED=0 accept any certificate from anyone, for every site the process contacts, and Node prints a warning saying so.
- Paste the certificate in the Certificate Decoder: Same as the subject and Self-signed confirm the cause.