How to check if a certificate is self-signed
Print the subject and the issuer of the served certificate and compare the two strings: openssl s_client -connect host:443 -servername host </dev/null | openssl x509 -noout -subject -issuer. Identical strings mean the certificate signed itself, and the same handshake reports Verify return code: 18 (self-signed certificate).
Checker offline. Follow the manual steps below, they give the same answer.
Why check this
A self-signed certificate on staging is normal. The same certificate on the host a customer reaches is an outage, because browsers refuse the navigation and mobile clients throw a trust exception. Run this check after any infrastructure change that touches the load balancer, and on the first deploy to a new environment, where the placeholder certificate written by the provisioning script is often still in place.
Prerequisites
- openssl 1.1.1 or later. See the s_client manual.
- The host name the server expects. Send it with
-servername, otherwise a shared address returns a default certificate that belongs to another site. - Nothing has to be installed on the server. The check reads what the server sends during the handshake.
Steps
- Step 1.
Print the subject and the issuer of the certificate the server sends.
openssl s_client -connect self-signed.badssl.com:443 -servername self-signed.badssl.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuersubject=C = US, ST = California, L = San Francisco, O = BadSSL, CN = *.badssl.com issuer=C = US, ST = California, L = San Francisco, O = BadSSL, CN = *.badssl.comThe two lines are character for character the same. That is the definition of self-signed.
- Step 2.
Read the verification verdict from the same handshake.
openssl s_client -connect self-signed.badssl.com:443 -servername self-signed.badssl.com </dev/null 2>&1 | grep -E 'verify error|Verify return code'verify error:num=18:self-signed certificate Verify return code: 18 (self-signed certificate) - Step 3.
Run the same read against a host with a publicly issued certificate, to see what a passing result looks like.
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuersubject=CN = example.com issuer=C = US, O = SSL Corporation, CN = Cloudflare TLS Issuing ECC CA 3 - Step 4.
Separate a self-signed leaf from a leaf issued by a private CA, which fails for a different reason.
openssl s_client -connect untrusted-root.badssl.com:443 -servername untrusted-root.badssl.com </dev/null 2>&1 | grep -E 's:|i:|Verify return code'0 s:C = US, ST = California, L = San Francisco, O = BadSSL, CN = *.badssl.com i:C = US, ST = California, L = San Francisco, O = BadSSL, CN = BadSSL Untrusted Root Certificate Authority 1 s:C = US, ST = California, L = San Francisco, O = BadSSL, CN = BadSSL Untrusted Root Certificate Authority i:C = US, ST = California, L = San Francisco, O = BadSSL, CN = BadSSL Untrusted Root Certificate Authority Verify return code: 19 (self-signed certificate in certificate chain)The leaf at position 0 carries an issuer of its own. The self-signature sits one level up, on the root at position 1, so the code is 19 and not 18.
- Step 5.
Prove the signature belongs to the key inside the same certificate.
openssl s_client -connect self-signed.badssl.com:443 -servername self-signed.badssl.com </dev/null 2>/dev/null | openssl x509 -outform PEM > leaf.pem && openssl verify -CAfile leaf.pem leaf.pemleaf.pem: OKA certificate that validates while acting as its own trust anchor cannot have been signed by anything else.
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| subject and issuer are identical | The certificate signed itself | Replace it before the host is public, or pin it if the host stays internal |
| Verify return code: 18 | A self-signed leaf | The code to quote in the ticket, because it names the exact failure |
| Verify return code: 19 | A real chain ending at a root the local store does not hold | Install the private root on the clients, or move the host to a public CA |
| Verify return code: 0 (ok) with different names | A publicly trusted certificate | Nothing here. Read expiry and SAN coverage next |
| openssl verify -CAfile leaf.pem leaf.pem prints OK | Confirmed self-signature | Step 1 is settled, no further parsing needed |
Common mistakes
What to check next
- How to check certificate chain: once the issuer differs from the subject, the next question is whether the server sends the intermediates.
- How to check SSL certificate expiry: a replacement certificate that is trusted but stale fails the user in the same way.
- How to check the TLS handshake: reads the rest of what step 2 printed, including protocol and cipher.
- How to check SSL certificate in chrome: the browser view of the same certificate, with the error code to paste into the bug.
- SSL certificate checker: runs the subject, issuer and chain read of this page against a host you type.
FAQ
Can a self-signed certificate still be valid?
It is valid in the sense that the signature verifies and the dates are current. It is not trusted, because no party outside the server vouches for it. Clients reject it unless that exact certificate was installed in their trust store first.
Is a self-signed certificate the same as an untrusted root?
No. A self-signed leaf has no issuer above it and returns code 18. A leaf from a private CA has a real chain and returns code 19. The two need different fixes, which is why step 4 is on this page.
Why does an internal service work in the browser but fail in tests?
Browsers on managed machines usually carry the internal root through device policy. A test container starts with the public root store only. Point the test client at the same root file the browser trusts, or the run keeps failing on trust rather than on your code.
Does a self-signed certificate encrypt traffic?
Yes. The key exchange works and the bytes on the wire are encrypted. What is missing is proof of who holds the key, so anyone in the network path can present a self-signed certificate of their own and the client cannot tell the two apart.
Verified
Verified by Maks Vernyopenssl 3.1.1
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
- Checker: tls-certificate TLS versions offered, certificate chain, expiry, SANs
- TLS and certificate review
- All security headers and tls checks
basic5 minpublished updated Maks Verny