# SSL/TLS Test Lab: 20 broken and modern HTTPS test sites

> Test sites under test.ssl.sh for expired, self-signed, wrong-host, incomplete-chain, revoked, TLS 1.0, TLS 1.3-only, post-quantum and mTLS setups, with the expected browser error and checker result for each.

Source: https://ssl.sh/test-lab/
Last updated: 2026-11-10

# SSL/TLS Test Lab: 20 broken and modern HTTPS test sites

Test sites under test.ssl.sh for expired, self-signed, wrong-host, incomplete-chain, revoked, TLS 1.0, TLS 1.3-only, post-quantum and mTLS setups, with the expected browser error and checker result for each.

## Certificate errors

### expired.test.ssl.sh

- URL: https://expired.test.ssl.sh/
- Certificate: Let's Encrypt, 6-day, never renewed
- Setup: A Let's Encrypt 6-day certificate (`shortlived` profile) that was issued once and never renewed, so it expired 160 hours after issuance.
- Browsers: Chrome and Edge show `NET::ERR_CERT_DATE_INVALID`; Firefox shows `SEC_ERROR_EXPIRED_CERTIFICATE`; Safari says the connection is not private.
- curl ssl.sh/expired.test.ssl.sh: Fails with the `expired` finding: notAfter is in the past and `days_left` is negative.

### self-signed.test.ssl.sh

- URL: https://self-signed.test.ssl.sh/
- Certificate: Private lab CA
- Setup: A self-signed certificate generated by `sslsh testlab gen`: the issuer is the certificate itself, no CA involved.
- Browsers: Chrome and Edge show `NET::ERR_CERT_AUTHORITY_INVALID`; Firefox shows `MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT`.
- curl ssl.sh/self-signed.test.ssl.sh: Fails with `self_signed` (the chain is not trusted). Plain curl reports `curl: (60) SSL certificate problem: self-signed certificate`.

### untrusted-root.test.ssl.sh

- URL: https://untrusted-root.test.ssl.sh/
- Certificate: Private lab CA
- 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.
- Browsers: Chrome and Edge show `NET::ERR_CERT_AUTHORITY_INVALID`; Firefox shows `SEC_ERROR_UNKNOWN_ISSUER`.
- curl ssl.sh/untrusted-root.test.ssl.sh: Fails with `untrusted_root`. Plain curl reports `curl: (60) SSL certificate problem: self-signed certificate in certificate chain` or `unable to get local issuer certificate`.

### wrong-host.test.ssl.sh

- URL: https://wrong-host.test.ssl.sh/
- Certificate: ssl.sh's own certificate
- Setup: Serves ssl.sh's own valid Let's Encrypt certificate, which only covers `ssl.sh` and `www.ssl.sh`, not `wrong-host.test.ssl.sh`.
- Browsers: Chrome and Edge show `NET::ERR_CERT_COMMON_NAME_INVALID`; Firefox shows `SSL_ERROR_BAD_CERT_DOMAIN`.
- curl ssl.sh/wrong-host.test.ssl.sh: Fails with `hostname_mismatch`; the SAN list shows `ssl.sh` and `www.ssl.sh`. Plain curl reports `no alternative certificate subject name matches target host name`.

### incomplete-chain.test.ssl.sh

- URL: https://incomplete-chain.test.ssl.sh/
- Certificate: Let's Encrypt, 6-day
- Setup: A valid Let's Encrypt certificate, but the server sends only the leaf (`cert.pem` instead of `fullchain.pem`), so the intermediate is missing.
- 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.sh: Fails with `incomplete_chain`. curl, OpenSSL, Python and Node.js report `unable to get local issuer certificate`.

### revoked.test.ssl.sh

- URL: https://revoked.test.ssl.sh/
- Certificate: Let's Encrypt, 90-day, revoked
- Setup: A Let's Encrypt 90-day certificate (`classic` profile, which carries a CRL URL) revoked right after issuance with reason keyCompromise. It is re-issued and revoked about every 80 days.
- Browsers: Firefox (CRLite) shows `SEC_ERROR_REVOKED_CERTIFICATE`. Chrome and Edge show `NET::ERR_CERT_REVOKED` only 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.sh: Reports `revoked` when the checker consults the CA's CRL. Plain curl and OpenSSL don't check revocation and connect normally.

### client-auth-eku.test.ssl.sh

- URL: https://client-auth-eku.test.ssl.sh/
- Certificate: Private lab CA
- 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.
- 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.sh: Shows the `client_auth_eku` finding (a public CA won't renew this certificate with the same EKUs) plus `untrusted_root`.

## Protocol versions and key exchange

### tls-v1-0.test.ssl.sh

- URL: https://tls-v1-0.test.ssl.sh/
- Certificate: Let's Encrypt, 6-day
- Setup: Only TLS 1.0 is enabled (`ssl_protocols TLSv1` with `@SECLEVEL=0`, because OpenSSL 3 refuses TLS 1.0 at its default security level).
- Browsers: Every current browser refuses: Chrome and Edge show `ERR_SSL_VERSION_OR_CIPHER_MISMATCH`, Firefox shows `SSL_ERROR_UNSUPPORTED_VERSION`.
- curl ssl.sh/tls-v1-0.test.ssl.sh: The quick check fails with `tls_handshake_failed`; a full check should list TLS 1.0 as the only enabled protocol.

### tls-v1-1.test.ssl.sh

- URL: https://tls-v1-1.test.ssl.sh/
- Certificate: Let's Encrypt, 6-day
- Setup: Only TLS 1.1 is enabled (`ssl_protocols TLSv1.1` with `@SECLEVEL=0`).
- Browsers: Same as TLS 1.0: `ERR_SSL_VERSION_OR_CIPHER_MISMATCH` in Chrome and Edge, `SSL_ERROR_UNSUPPORTED_VERSION` in Firefox.
- curl ssl.sh/tls-v1-1.test.ssl.sh: The quick check fails with `tls_handshake_failed`; a full check should list TLS 1.1 as the only enabled protocol.

### tls-v1-2-only.test.ssl.sh

- URL: https://tls-v1-2-only.test.ssl.sh/
- Certificate: Let's Encrypt, 6-day
- Setup: Only TLS 1.2 is enabled (`ssl_protocols TLSv1.2`).
- Browsers: Loads normally in every current browser, negotiated as TLS 1.2.
- curl ssl.sh/tls-v1-2-only.test.ssl.sh: Passes with `tls_version` TLSv1.2; a full check notes that TLS 1.3 is not offered (informational, not an error).

### tls-v1-3-only.test.ssl.sh

- URL: https://tls-v1-3-only.test.ssl.sh/
- Certificate: Let's Encrypt, 6-day
- Setup: Only TLS 1.3 is enabled (`ssl_protocols TLSv1.3`).
- 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.sh: Passes with `tls_version` TLSv1.3; a full check shows TLS 1.2 disabled.

### pq-hybrid-only.test.ssl.sh

- URL: https://pq-hybrid-only.test.ssl.sh/
- Certificate: Let's Encrypt, 6-day
- 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).
- 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.sh: Passes with `key_exchange_group` X25519MLKEM768 when the checker's OpenSSL is 3.5+; with an older OpenSSL it fails with `tls_handshake_failed`.

## HTTP security headers and page content

### hsts.test.ssl.sh

- URL: https://hsts.test.ssl.sh/
- Certificate: Let's Encrypt, 6-day
- Setup: Valid certificate plus `Strict-Transport-Security: max-age=31536000; includeSubDomains`.
- 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.sh: Passes; a full check reports HSTS with max-age 31536000.

### no-hsts.test.ssl.sh

- URL: https://no-hsts.test.ssl.sh/
- Certificate: Let's Encrypt, 6-day
- Setup: Valid certificate, no HSTS header; plain HTTP is redirected to HTTPS with a 301 only.
- 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.sh: Passes the certificate checks; a full check adds the `no_hsts` finding.

### mixed-content.test.ssl.sh

- URL: https://mixed-content.test.ssl.sh/
- Certificate: Let's Encrypt, 6-day
- Setup: An HTTPS page that loads an image and a script over plain `http://`.
- 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.sh: Passes: the TLS setup is fine. Mixed content is a page problem, not a certificate problem; scan the page instead.

## Keys and certificate lifetimes

### long-validity.test.ssl.sh

- URL: https://long-validity.test.ssl.sh/
- Certificate: Private lab CA
- 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.
- 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.sh: Fails with `untrusted_root`; `validity_days` is 3650, far above the 200-day public maximum.

### short-lived-6-days.test.ssl.sh

- URL: https://short-lived-6-days.test.ssl.sh/
- Certificate: Let's Encrypt, 6-day
- Setup: A Let's Encrypt `shortlived` certificate: valid for 160 hours (about 6.7 days) and renewed automatically every 2–3 days.
- Browsers: Loads normally.
- curl ssl.sh/short-lived-6-days.test.ssl.sh: Passes; `validity_days` is 7 (160 hours rounded up) and `days_left` is between 0 and 6. A short remaining lifetime is expected here, not a renewal failure.

### ecc-p256.test.ssl.sh

- URL: https://ecc-p256.test.ssl.sh/
- Certificate: Let's Encrypt, 6-day
- Setup: A Let's Encrypt certificate with an ECDSA P-256 key.
- Browsers: Loads normally.
- curl ssl.sh/ecc-p256.test.ssl.sh: Passes; `key_type` is “ECDSA P-256”.

### rsa-4096.test.ssl.sh

- URL: https://rsa-4096.test.ssl.sh/
- Certificate: Let's Encrypt, 6-day, RSA 4096
- Setup: A Let's Encrypt certificate with a 4096-bit RSA key.
- 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.sh: Passes; `key_type` is “RSA 4096”.

## Client certificates

### mtls-required.test.ssl.sh

- URL: https://mtls-required.test.ssl.sh/
- Certificate: Let's Encrypt, 6-day
- 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/.
- Browsers: Without a client certificate nginx answers “400 Bad Request — No required SSL certificate was sent”. Import `client.p12` (password `ssl.sh`) and the browser asks you to pick it; the page then loads.
- curl ssl.sh/mtls-required.test.ssl.sh: The 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.

## Scenarios we do not run

- 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-root` already 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.
