Self-hosted service
chromium/badssl.com avatar
chromium/badssl.com

chromium/badssl.com: a test site for clients that handle TLS badly

:lock: Memorable site for testing clients against bad SSL configs.

3,050 stars204 forksHTMLApache-2.0

At a glance

What is it?
badssl.com is a set of deliberately misconfigured HTTPS endpoints, plus the nginx, Jekyll and certificate tooling that generates them. It is built for manual testing of client security UI, not for automated test suites.
Who is it for?
Adopt it if you are checking how a browser, an HTTP client or an SSL library presents certificate errors to a human, and you want a known-bad endpoint you did not have to build. Do not adopt it as a dependency of an automated test suite: the README's disclaimer says most subdomains are likely to have stable functionality but anything could change without notice, and it asks you to file an issue if you need a documented guarantee.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 122 days ago.
What is it written in?
Mainly HTML, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What badssl.com is for, and who actually needs it

The project hosts a list of test subdomains, each one broken in a specific way: self-signed.badssl.com, expired.badssl.com, mixed.badssl.com, rc4.badssl.com, hsts.badssl.com. The point is the client's reaction, not the server's correctness. If you are writing the code that decides whether to show a padlock, whether to let a user click through a warning, or whether to fail a connection outright, you need endpoints that trigger each of those paths, and you need them to be reachable without setting up your own certificate authority and DNS.

The README is explicit about the audience: badssl.com is meant for manual testing of security UI in web clients. That phrasing matters more than it looks. Manual means a person is looking at the result. Web clients means browsers and browser-like things. A command line tool that returns exit code 60 is not the target; the dialog, the interstitial page and the error text are. The repository also carries a disclaimer that it is not an official Google product and is offered AS-IS, despite being hosted under the chromium organisation.

How the test domains are generated: certs, Jekyll and nginx

The repository is a build system around three moving parts. Certificates are generated by a Makefile under certs/, with a test set and a production set, and the generated material is copied into common/certs/ so the server can serve it. Jekyll builds the site, and the Makefile passes the domain in as an environment variable: jekyll-test runs with DOMAIN=${TEST_DOMAIN} and jekyll-prod with DOMAIN=${PROD_DOMAIN}, where those variables are badssl.test and badssl.com respectively. nginx then serves the result, with the configuration split across nginx.conf, fallback.conf and fallback-common.conf plus an nginx-includes/ directory.

The separation between test and production is deliberate. certs-test and certs-prod generate different certificate sets, and the copy step differs too: the test set copies ca-root.crt, ca-untrusted-root.crt, client.p12 and client.pem into common/certs, while the production set copies the untrusted root and the client files but not the trusted root. That is a sensible boundary, because the trusted root only exists so that a local machine can be told to trust the generated CA and get the rest of the subdomains working. Shipping it in production would defeat the exercise.

There is also a domains-local-only/ directory alongside domains/, which is where locally served-only test cases live. The README does not explain the distinction, so treat it as a repository layout observation rather than documented behaviour.

Running badssl.com locally with Docker and make serve

The README gives a Docker-based path for testing and development. You install Docker, clone the repository, add the subdomains to your hosts file, and start the container. The Makefile defines serve as an alias for test, and test chains certs-test, docker-build and docker-run in that order, with .NOTPARALLEL set so the steps do not race.

Start by cloning and generating the hosts entries:

bash
git clone https://github.com/chromium/badssl.com && cd badssl.com
make list-hosts

The make list-hosts output is meant to be copied into /etc/hosts. After that, bring the server up:

bash
make serve

At this point navigating to badssl.test in a browser should show a certificate error. That is the expected first result, not a failure. To get the other subdomains working you add the generated root certificate at certs/sets/test/gen/crt/ca-root.crt to your machine's trusted certificates. On macOS the README gives a command line shortcut:

sh
security add-trusted-cert -r trustRoot -p ssl \
  -k "$HOME/Library/Keychains/login.keychain" certs/sets/test/gen/crt/ca-root.crt

The Dockerfile itself starts from ubuntu:24.04, exposes ports 80 and 443, installs build-essential, git, jekyll, libffi-dev, make, nginx and ruby, and runs make inside-docker at build time. The container's CMD starts nginx and tails the access and error logs. Note that the Makefile comment says certificates are generated outside the docker container, for persistence, which is why the README includes a step to copy the generated client and root certificates into pregen/ before running make clean.

The stability guarantee you do not get

The most important sentence in the README is the one about change. Most subdomains are likely to have stable functionality, but anything could change without notice. If you need a documented guarantee for a particular use case, the README says to file an issue, or to fork and host your own copy. That is a clear signal about what the hosted service is: a shared public resource maintained for manual inspection, not an API with a contract.

This makes it the wrong tool for a CI job that asserts a specific TLS failure. A test that pins behaviour to expired.badssl.com will break the day the certificate is renewed, and the renewal is the correct thing for the maintainers to do. The same applies to any subdomain whose failure depends on a certificate property with a lifetime. If you need a deterministic negative test, the repository's own answer is to fork it and control the certificates yourself, which is exactly what the certs/ tooling is for. The local Docker path exists for that reason.

A second limitation is scope. The README frames the project around manual testing of security UI in web clients. If your problem is validating that a backend service rejects a revoked certificate chain under load, badssl.com does not describe itself as addressing that.

Alternatives, and where the approach differs

The obvious alternative is not another public test site but your own local certificate authority. Tools in that space, such as mkcert or a small step-ca or openssl setup, generate certificates on your machine and install a local root into your trust store. The difference in approach is who owns the trust anchor. With badssl.com, the trust anchor is a real public CA hierarchy and the failures are real failures, which is what makes the browser UI behave the way it will behave in front of a user. With a local CA, you control every parameter, you can regenerate on demand, and you can put the whole thing in CI, but you are testing against a hierarchy you built, and some client behaviour around public roots will not reproduce.

A second alternative is a proxy-based tool such as mitmproxy, which is listed among the repository's topics. That inverts the problem: instead of serving a bad certificate, you intercept a good one. It is better suited to inspecting traffic than to checking how a client renders a warning. If your goal is the warning dialog, badssl.com is closer to the real thing. If your goal is what happens after a user clicks through, the proxy gives you more control over the response body.

Maintenance, licensing and what a fork costs

The repository is not archived, and the last push was on 2026-06-01. That is recent enough that the project is not abandoned, but the README offers no release history and no versioned artifacts, so there is no upgrade path in the usual sense: you track master. For a hosted test site that is fine. For a fork, it means you inherit the maintenance of the certificate generation tooling and the nginx configuration, plus the ongoing job of renewing certificates that deliberately expire.

The licence is Apache-2.0, which permits forking and self-hosting. The README itself suggests forking as the route to a documented guarantee. Nothing in the repository suggests trademark restrictions on the name, but the disclaimer that badssl.com is not an official Google product is worth reading before you present a fork as anything other than your own. The certificate acknowledgements section lists the organisations that issued specific certificates, including The SSL Store, DigiCert, Comodo and SSLMate, which is a reminder that some of the test cases depend on certificates that were issued through special processes and would be hard to reproduce from scratch.

Editorial conclusion

Adopt it if you are checking how a browser, an HTTP client or an SSL library presents certificate errors to a human, and you want a known-bad endpoint you did not have to build. Do not adopt it as a dependency of an automated test suite: the README's disclaimer says most subdomains are likely to have stable functionality but anything could change without notice, and it asks you to file an issue if you need a documented guarantee. Before relying on it, verify the specific subdomain you care about in a browser, and note that the repository's own local setup requires adding the generated ca-root.crt to your machine's trusted certificates, which is the opposite of what you want on a production host.

Frequently asked questions

What is badssl.com?

It is a site that hosts test subdomains with deliberately bad TLS configurations, such as self-signed.badssl.com, expired.badssl.com and mixed.badssl.com. The README describes it as a place for manual testing of security UI in web clients.

What is expired.badssl.com?

It is one of the listed test subdomains, and its purpose is to serve a certificate that has expired so a client's handling of that condition can be observed. The README lists it alongside self-signed.badssl.com, mixed.badssl.com, rc4.badssl.com and hsts.badssl.com without documenting each one individually.

Is badssl.com safe?

The site is designed to present certificate errors, so a browser warning on a badssl.com subdomain is the expected behaviour rather than a sign of compromise. The README states that the project is offered AS-IS and without any warranties, and that it is not an official Google product.

Official sources

  1. chromium/badssl.com on GitHub
  2. Issues
  3. License: Apache-2.0
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/chromium-badssl-com.svg)](https://hysenlabs.com/projects/chromium-badssl-com)