Trusted SSL Certificates for Localhost

Creating valid SSL certificates for localhost web development, supporting one or many local domains.

The best way I’ve found to add SSL certificates to a localhost environment is using mkcert, which is technically not a self-signed certificate at all.

Instead, it creates a local certificate signing authority, adds it to your machine, and uses that to sign certificates. The end result is a fully valid SSL certificate that is not self-signed, so it works with modern browsers and aligns with security protocols that block self-signed SSL certs.

What is mkcert?

mkcert is an open source command line tool written by Filippo Valsorda, a cryptographer who spent years on the Go security team at Google. As described on GitHub, it’s:

a simple tool for making locally-trusted development certificates that requires no configuration.

Under the hood it does two things. First, it generates a private certificate authority (CA) that lives only on your machine and installs it into your system and browser trust stores, the same lists of trusted authorities that let your browser trust certificates from Let’s Encrypt or DigiCert. Second, it signs leaf certificates with that CA for whatever hostnames you ask for. Because the signing authority is trusted by your machine, every certificate it issues is trusted too: real HTTPS, green padlock, no warnings, no exceptions to click through.

This is fundamentally different from the classic openssl self-signed certificate. A self-signed cert has no authority behind it, so browsers correctly treat it as untrustworthy. mkcert gives you the full chain of trust, just scoped privately to your computer. The private CA key never leaves your machine, which is also why this is safe: nobody else’s browser will ever trust certificates signed by your local CA.

Fixing “Not Secure” Warnings on Localhost

If you landed here trying to get rid of the “Your connection is not private” interstitial, the Not Secure label, or the red X through https:// in your address bar on a local site, this is the fix. Those warnings (usually NET::ERR_CERT_AUTHORITY_INVALID in Chrome or SEC_ERROR_UNKNOWN_ISSUER in Firefox) all mean the same thing: the certificate your local server presented isn’t signed by an authority your browser trusts.

The common workarounds are all worse than the disease. Clicking “Proceed anyway” trains you to ignore security warnings and has to be repeated constantly. The chrome://flags/#allow-insecure-localhost flag was removed from Chrome entirely. And plain HTTP isn’t a real option anymore either, because features like service workers, secure cookies, HTTP/2, and many modern browser APIs only work in a secure context.

mkcert gives your local environment full SSL, indistinguishable in the browser from a production site: valid certificate, padlock, no exceptions. Your dev environment behaves like production, which is the whole point of a dev environment.

Installation

mkcert supports Mac, Windows and Linux machines, so it should work with your setup.

Mac OS & Linux

brew install mkcert
brew install nss

The nss package is only needed if you use Firefox, which keeps its own certificate store separate from the operating system’s.

Windows

choco install mkcert

Adding Localhost SSL Certificates

Step 1: Create a Local Certificate Authority

Once you’ve installed the tool, create the local certificate authority (CA) and install it into your trust stores:

mkcert -install

Step 2: Add SSL Certificates for Local URL’s

Once you’ve set up a local authority for signing certificates, you can add SSL certificates for your localhost domains:

mkcert example.test "*.example.test" localhost 127.0.0.1 ::1

This will create two *.pem certificate files, ./example.test+4.pem and ./example.test+4-key.pem, for the following hostnames:

  • example.test
  • *.example.test
  • localhost
  • 127.0.0.1
  • ::1

These SSL certificates effectively cover the following URLs that are common localhost hosts:

  • https://example.test and any subdomain, like https://www.example.test
  • https://localhost

Root Stores

The mkcert -install step works by adding your local CA to every certificate root store it can find. It currently supports:

  • macOS system store
  • Windows system store
  • Linux variants that provide either update-ca-trust (Fedora, RHEL, CentOS), update-ca-certificates (Ubuntu, Debian, OpenSUSE, SLES), or trust (Arch)
  • Firefox (macOS and Linux only)
  • Chrome and Chromium
  • Java (when JAVA_HOME is set)

To install the local root CA into only a subset of them, set the TRUST_STORES environment variable to a comma-separated list. The options are system, java, and nss (which includes Firefox):

TRUST_STORES=system,nss mkcert -install

Configuring the Local Web Server

You’ll need to configure a host pointing example.test and www.example.test to your local web server. Once that’s set up, and you have the certificates added to your system, you might be done. Many localhost servers automatically load certificates from your machine’s certificate store. If you restart your browser and the URL loads successfully with a secure, valid SSL certificate attached (lock icon to the left of your URL) then you’re all set.

If you don’t see a secure connection yet, you may need to specifically point your local web server to use the *.pem files generated by mkcert in the previous steps.

Apache

Open your httpd.conf file and add or update the following to it:

<VirtualHost *:443>
  ServerName example.test
  SSLEngine on
  SSLCertificateFile "/path/to/example.test+4.pem"
  SSLCertificateKeyFile "/path/to/example.test+4-key.pem"
</VirtualHost>

Restart Apache to load the certificates, then re-check your browser to verify they’re working. You may need to restart your browser to get your site(s) working.

nginx

Open your nginx.conf file and add or update the following to the http block:

ssl_certificate "ssl/example.test+4.pem";
ssl_certificate_key "ssl/example.test+4-key.pem";

Restart nginx to load the certificates, then re-check your browser to verify they’re working. You may need to restart your browser to get your site(s) working.

How I Use It for Local Development

My setup is nginx configured to load the certificate files mkcert outputs, exactly as shown above. Every local project gets a .test domain, and they all share one certificate.

When I spin up a new project and need another domain covered, I don’t touch the CA at all. I just re-run mkcert with the full list of hostnames:

mkcert host.test host2.test host3.test

…point nginx at the newly generated .pem files if the filenames changed, and restart nginx:

sudo nginx -s reload

That’s the entire workflow. The new certificate is signed by the same local CA my machine already trusts, so the browser accepts it immediately with no warnings and no browser restart.

When do you need to re-run mkcert -install?

Adding domains never requires it. mkcert -install only puts the CA into your trust stores, and once it’s there, every certificate the CA signs is trusted automatically. You only need to re-run it when the set of trust stores changes or the CA itself does:

  • A new machine, or a fresh OS install, where the CA has never been trusted
  • A new browser or root store appears: you install Firefox (after adding nss), or set JAVA_HOME for the first time, and that store needs the CA added to it
  • The CA was regenerated: if you delete the mkcert -CAROOT directory or sync dotfiles to a new machine without it, a new CA gets created and must be installed before its certificates are trusted

If certificates that used to work suddenly throw authority warnings, re-running mkcert -install is the first thing to try; it’s idempotent and safe to run repeatedly.