test.ssl.sh
SSL/TLS Test Lab: 20 broken and modern HTTPS test sites
Each subdomain of test.ssl.sh is broken (or unusually modern) in exactly one way. Open it in a browser to see the real error, or check it with ssl.sh to see what the checker should report.
How to use the lab
Open a scenario in the browser or app you are testing and compare what you see with the expected result below. Use the matching error page to explain the problem to someone else.
To test the checker itself, run curl ssl.sh/<scenario>.test.ssl.sh. Tools and libraries can be tested against the same hosts: a client that connects to expired, self-signed or wrong-host without complaining is not validating certificates.
curl ssl.sh/expired.test.ssl.sh
curl ssl.sh/pq-hybrid-only.test.ssl.sh
curl -I https://hsts.test.ssl.sh/
openssl s_client -connect tls-v1-0.test.ssl.sh:443 -tls1_3
Certificate errors
expired.test.ssl.sh
Fails- Certificate
- Let's Encrypt, 6-day, never renewed
- Server setup
A Let's Encrypt 6-day certificate (
shortlivedprofile) that was issued once and never renewed, so it expired 160 hours after issuance.- In browsers
Chrome and Edge show
NET::ERR_CERT_DATE_INVALID; Firefox showsSEC_ERROR_EXPIRED_CERTIFICATE; Safari says the connection is not private.curl ssl.sh/expired.test.ssl.shFails with the
expiredfinding: notAfter is in the past anddays_leftis negative.- See also
- NET::ERR_CERT_DATE_INVALIDSEC_ERROR_EXPIRED_CERTIFICATESSL Checker
self-signed.test.ssl.sh
Fails- Certificate
- Self-signed
- Server setup
A self-signed certificate generated by
sslsh testlab gen: the issuer is the certificate itself, no CA involved.- In browsers
Chrome and Edge show
NET::ERR_CERT_AUTHORITY_INVALID; Firefox showsMOZILLA_PKIX_ERROR_SELF_SIGNED_CERT.curl ssl.sh/self-signed.test.ssl.shFails with
self_signed(the chain is not trusted). Plain curl reportscurl: (60) SSL certificate problem: self-signed certificate.- See also
- MOZILLA_PKIX_ERROR_SELF_SIGNED_CERTNET::ERR_CERT_AUTHORITY_INVALIDcurl: (60) SSL certificate problemSelf-Signed Certificate
untrusted-root.test.ssl.sh
Fails- Certificate
- Private lab CA
- Server setup
Leaf → intermediate → a private root (“ssl.sh Test Lab Untrusted Root”) that is in no trust store and is never published. The server sends the leaf and the intermediate.
- In browsers
Chrome and Edge show
NET::ERR_CERT_AUTHORITY_INVALID; Firefox showsSEC_ERROR_UNKNOWN_ISSUER.curl ssl.sh/untrusted-root.test.ssl.shFails with
untrusted_root. Plain curl reportscurl: (60) SSL certificate problem: self-signed certificate in certificate chainorunable to get local issuer certificate.- See also
- NET::ERR_CERT_AUTHORITY_INVALIDSEC_ERROR_UNKNOWN_ISSUERx509: certificate signed by unknown authorityRoot Certificate
wrong-host.test.ssl.sh
Fails- Certificate
- ssl.sh's own certificate
- Server setup
Serves ssl.sh's own valid Let's Encrypt certificate, which only covers
ssl.shandwww.ssl.sh, notwrong-host.test.ssl.sh.- In browsers
Chrome and Edge show
NET::ERR_CERT_COMMON_NAME_INVALID; Firefox showsSSL_ERROR_BAD_CERT_DOMAIN.curl ssl.sh/wrong-host.test.ssl.shFails with
hostname_mismatch; the SAN list showsssl.shandwww.ssl.sh. Plain curl reportsno alternative certificate subject name matches target host name.- See also
- NET::ERR_CERT_COMMON_NAME_INVALIDSSL_ERROR_BAD_CERT_DOMAINSubject Alternative Name
incomplete-chain.test.ssl.sh
Depends on client- Certificate
- Let's Encrypt, 6-day
- Server setup
A valid Let's Encrypt certificate, but the server sends only the leaf (
cert.peminstead offullchain.pem), so the intermediate is missing.- In browsers
Usually loads: Chrome, Edge and Safari fetch the missing intermediate through the AIA URL, and Firefox ships known intermediates. Older Android devices, apps and command-line tools fail.
curl ssl.sh/incomplete-chain.test.ssl.shFails with
incomplete_chain. curl, OpenSSL, Python and Node.js reportunable to get local issuer certificate.- See also
- Missing intermediate certificatecurl: (60) SSL certificate problemPython CERTIFICATE_VERIFY_FAILEDChain Builder
revoked.test.ssl.sh
Depends on client- Certificate
- Let's Encrypt, 90-day, revoked
- Server setup
A Let's Encrypt 90-day certificate (
classicprofile, which carries a CRL URL) revoked right after issuance with reason keyCompromise. It is re-issued and revoked about every 80 days.- In browsers
Firefox (CRLite) shows
SEC_ERROR_REVOKED_CERTIFICATE. Chrome and Edge showNET::ERR_CERT_REVOKEDonly once the serial reaches Chrome's CRLSet; until then the page loads. Safari depends on Apple's revocation service.curl ssl.sh/revoked.test.ssl.shReports
revokedwhen the checker consults the CA's CRL. Plain curl and OpenSSL don't check revocation and connect normally.- See also
- NET::ERR_CERT_REVOKEDOCSP and CRLCRL
client-auth-eku.test.ssl.sh
Fails- Certificate
- Private lab CA
- Server setup
A certificate from the lab CA whose Extended Key Usage contains both serverAuth and clientAuth: the multi-purpose combination publicly trusted CAs phased out in 2025–2026.
- In browsers
Without the lab root:
NET::ERR_CERT_AUTHORITY_INVALID/SEC_ERROR_UNKNOWN_ISSUER. With the name-constrained lab root imported it loads: browsers don't reject the extra clientAuth EKU.curl ssl.sh/client-auth-eku.test.ssl.shShows the
client_auth_ekufinding (a public CA won't renew this certificate with the same EKUs) plusuntrusted_root.- See also
- unsupported certificate purpose (clientAuth EKU removed)Extended Key UsageClient Certificate
Protocol versions and key exchange
tls-v1-0.test.ssl.sh
Fails- Certificate
- Let's Encrypt, 6-day
- Server setup
Only TLS 1.0 is enabled (
ssl_protocols TLSv1with@SECLEVEL=0, because OpenSSL 3 refuses TLS 1.0 at its default security level).- In browsers
Every current browser refuses: Chrome and Edge show
ERR_SSL_VERSION_OR_CIPHER_MISMATCH, Firefox showsSSL_ERROR_UNSUPPORTED_VERSION.curl ssl.sh/tls-v1-0.test.ssl.shThe quick check fails with
tls_handshake_failed; a full check should list TLS 1.0 as the only enabled protocol.- See also
- ERR_SSL_VERSION_OR_CIPHER_MISMATCHERR_SSL_PROTOCOL_ERRORTLS 1.3
tls-v1-1.test.ssl.sh
Fails- Certificate
- Let's Encrypt, 6-day
- Server setup
Only TLS 1.1 is enabled (
ssl_protocols TLSv1.1with@SECLEVEL=0).- In browsers
Same as TLS 1.0:
ERR_SSL_VERSION_OR_CIPHER_MISMATCHin Chrome and Edge,SSL_ERROR_UNSUPPORTED_VERSIONin Firefox.curl ssl.sh/tls-v1-1.test.ssl.shThe quick check fails with
tls_handshake_failed; a full check should list TLS 1.1 as the only enabled protocol.- See also
- ERR_SSL_VERSION_OR_CIPHER_MISMATCHERR_SSL_PROTOCOL_ERROR
tls-v1-2-only.test.ssl.sh
Loads- Certificate
- Let's Encrypt, 6-day
- Server setup
Only TLS 1.2 is enabled (
ssl_protocols TLSv1.2).- In browsers
Loads normally in every current browser, negotiated as TLS 1.2.
curl ssl.sh/tls-v1-2-only.test.ssl.shPasses with
tls_versionTLSv1.2; a full check notes that TLS 1.3 is not offered (informational, not an error).- See also
- TLS 1.3TLS Config Generator
tls-v1-3-only.test.ssl.sh
Loads- Certificate
- Let's Encrypt, 6-day
- Server setup
Only TLS 1.3 is enabled (
ssl_protocols TLSv1.3).- In browsers
Loads in every current browser. Clients limited to TLS 1.2 (Android 9 and older, Windows 10's built-in TLS stack, old Java 8 builds) fail with a protocol error.
curl ssl.sh/tls-v1-3-only.test.ssl.shPasses with
tls_versionTLSv1.3; a full check shows TLS 1.2 disabled.- See also
- TLS 1.3ERR_SSL_VERSION_OR_CIPHER_MISMATCH
pq-hybrid-only.test.ssl.sh
Depends on client- Certificate
- Let's Encrypt, 6-day
- Server setup
TLS 1.3 only, and the only key-exchange group is the post-quantum hybrid X25519MLKEM768 (
ssl_ecdh_curve X25519MLKEM768; nginx must be linked against OpenSSL 3.5 or newer).- In browsers
Recent Chrome, Edge, Firefox and Safari 26 connect because they send an X25519MLKEM768 key share by default. Older browsers and most HTTP libraries fail the handshake (Chrome:
ERR_SSL_VERSION_OR_CIPHER_MISMATCH).curl ssl.sh/pq-hybrid-only.test.ssl.shPasses with
key_exchange_groupX25519MLKEM768 when the checker's OpenSSL is 3.5+; with an older OpenSSL it fails withtls_handshake_failed.- See also
- X25519MLKEM768Post-quantum TLSML-KEM
HTTP security headers and page content
hsts.test.ssl.sh
Loads- Certificate
- Let's Encrypt, 6-day
- Server setup
Valid certificate plus
Strict-Transport-Security: max-age=31536000; includeSubDomains.- In browsers
Loads normally. After the first visit the browser rewrites http:// to https:// on its own (Chrome DevTools shows a “307 Internal Redirect”), and certificate errors on this host can no longer be clicked through.
curl ssl.sh/hsts.test.ssl.shPasses; a full check reports HSTS with max-age 31536000.
- See also
- HSTSHSTS CheckerHSTS Preload
no-hsts.test.ssl.sh
Loads- Certificate
- Let's Encrypt, 6-day
- Server setup
Valid certificate, no HSTS header; plain HTTP is redirected to HTTPS with a 301 only.
- In browsers
Loads normally and looks fine. The weakness is invisible: every first request over plain HTTP can be intercepted (SSL stripping).
curl ssl.sh/no-hsts.test.ssl.shPasses the certificate checks; a full check adds the
no_hstsfinding.- See also
- HSTSHSTS CheckerHSTS
mixed-content.test.ssl.sh
Depends on client- Certificate
- Let's Encrypt, 6-day
- Server setup
An HTTPS page that loads an image and a script over plain
http://.- In browsers
Chrome and Edge block the script and upgrade the image to HTTPS; the DevTools console shows “Mixed Content” messages. Firefox also blocks the script. The page itself tells you whether the HTTP script ran.
curl ssl.sh/mixed-content.test.ssl.shPasses: the TLS setup is fine. Mixed content is a page problem, not a certificate problem; scan the page instead.
- See also
- Mixed content warningMixed Content ScannerMixed Content
Keys and certificate lifetimes
long-validity.test.ssl.sh
Fails- Certificate
- Private lab CA
- Server setup
A lab-CA certificate valid for 3,650 days. Publicly trusted certificates have been capped at 200 days since 15 March 2026 (CA/Browser Forum ballot SC-081), falling to 47 days in 2029.
- In browsers
Without the lab root:
NET::ERR_CERT_AUTHORITY_INVALID. With the lab root imported, Chrome and Firefox load it (lifetime caps only apply to public roots), while Safari and other Apple clients reject TLS certificates valid longer than 825 days even from user-installed roots.curl ssl.sh/long-validity.test.ssl.shFails with
untrusted_root;validity_daysis 3650, far above the 200-day public maximum.- See also
- Certificate validity timelineSC-081v347-day certificatesValidity Calculator
short-lived-6-days.test.ssl.sh
Loads- Certificate
- Let's Encrypt, 6-day
- Server setup
A Let's Encrypt
shortlivedcertificate: valid for 160 hours (about 6.7 days) and renewed automatically every 2–3 days.- In browsers
Loads normally.
curl ssl.sh/short-lived-6-days.test.ssl.shPasses;
validity_daysis 7 (160 hours rounded up) anddays_leftis between 0 and 6. A short remaining lifetime is expected here, not a renewal failure.- See also
- 47-day certificatesARILet's Encrypt timeline
ecc-p256.test.ssl.sh
Loads- Certificate
- Let's Encrypt, 6-day
- Server setup
A Let's Encrypt certificate with an ECDSA P-256 key.
- In browsers
Loads normally.
curl ssl.sh/ecc-p256.test.ssl.shPasses;
key_typeis “ECDSA P-256”.- See also
- ECDSACSR Generator
rsa-4096.test.ssl.sh
Loads- Certificate
- Let's Encrypt, 6-day, RSA 4096
- Server setup
A Let's Encrypt certificate with a 4096-bit RSA key.
- In browsers
Loads normally; the handshake costs the server more CPU than ECDSA P-256 but the difference is not visible to visitors.
curl ssl.sh/rsa-4096.test.ssl.shPasses;
key_typeis “RSA 4096”.- See also
- RSACSR Generator
Client certificates
mtls-required.test.ssl.sh
Fails- Certificate
- Let's Encrypt, 6-day
- Server setup
The server requires a client certificate issued by the lab client CA (
ssl_verify_client on). A test client certificate is published at test.ssl.sh/downloads/.- In browsers
Without a client certificate nginx answers “400 Bad Request — No required SSL certificate was sent”. Import
client.p12(passwordssl.sh) and the browser asks you to pick it; the page then loads.curl ssl.sh/mtls-required.test.ssl.shThe server certificate passes. An HTTP request without a client certificate gets 400;
curl --cert client.pem --key client.key https://mtls-required.test.ssl.sh/gets 200.- See also
- Mutual TLS (mTLS)mTLSClient Certificate
Downloads
These files live at https://test.ssl.sh/downloads/. They are public on purpose; never use them outside testing.
- ssl.sh-test-lab-root.pem: the lab root, name-constrained so it can only issue for names under
test.ssl.sh. Import it temporarily to see howclient-auth-ekuandlong-validitybehave once trusted, then remove it. - client.p12 (password
ssl.sh), client.pem, client.key: the test client certificate formtls-required. - ssl.sh-test-lab-client-ca.pem: the CA that issued the client certificate.
Scenarios we do not run
Some classic test cases cannot be served by a current OpenSSL 3 build, or would only reproduce an error another scenario already shows.
- RC4
RFC 7465 has prohibited RC4 in TLS since 2015 and every browser removed it in 2016. OpenSSL 3 keeps RC4 only in the legacy provider, so a stock nginx cannot offer it, and the browser result would be the same cipher-mismatch error as
tls-v1-0.- RSA-1024
Public CAs have not issued 1024-bit RSA certificates for over a decade, and OpenSSL 3 at the security level most distributions ship refuses to load such a key. A private-CA version would only show the untrusted-issuer error, which
untrusted-rootalready covers.- SSLv3 / SSLv2
Browsers removed SSLv3 after POODLE (2014), and OpenSSL 1.1.0 and later no longer ship SSLv2 at all; distribution builds also disable SSLv3.
- SHA-1 signatures
Browsers stopped trusting SHA-1 certificates in 2017 and OpenSSL 3 rejects SHA-1 signatures at its default security level; a private-CA version would again only show the untrusted-issuer error.
How the lab is built
Every scenario runs on one nginx server. Certificate scenarios differ only in the certificate they send. TLS versions, key-exchange groups and client-certificate requests are settled before nginx knows which hostname was requested, so those scenarios each get their own listener and an SNI router on port 443 (ssl_preread) forwards connections to it.
Publicly trusted scenarios use Let's Encrypt 6-day certificates, so they fail only for the reason they are meant to show. Private certificates come from sslsh testlab gen.
The ssl.sh HSTS header deliberately omits includeSubDomains, so visiting ssl.sh does not affect the *.test.ssl.sh scenarios: certificate-error scenarios still offer a “proceed anyway” button, and HTTP requests in no-hsts and mixed-content are not upgraded because of the main site's HSTS.