Local HTTPS Development With mkcert in Under Ten Minutes
Your app works on plain http://localhost:3000, then breaks the moment it touches a secure cookie, a service worker or an OAuth callback that insists on HTTPS.

Your app works on plain http://localhost:3000, then breaks the moment it touches a secure cookie, a service worker or an OAuth callback that insists on HTTPS. Setting up local HTTPS development with mkcert fixes that in about ten minutes, and you never have to click through a browser warning again.
This guide walks through the setup on macOS, Linux and Windows, shows how to plug the certificate into the dev servers most small teams actually run, and covers the handful of mistakes that make people give up and go back to http.
Why Local HTTPS Development Matters More Than It Used To
A few years ago you could ignore TLS until staging. That is no longer true. Browsers treat a growing list of features as secure-context only: service workers, the clipboard API, geolocation, WebAuthn passkeys and cookies marked Secure with SameSite=None. If your production site depends on any of them, testing over plain http means you are testing a different app.
Localhost gets some special treatment, so a few of these features work on http://localhost. They stop working the moment you use a custom hostname like app.test or point a phone on your Wi-Fi at 192.168.1.40. That is exactly when most people discover the problem, usually at 6 pm the day before a demo.
The other common pain is third-party login. Providers increasingly reject non-HTTPS redirect URIs, or accept them only for the literal string localhost. If your app runs on multiple subdomains locally, you need real certificates.
What mkcert Actually Does
mkcert creates a small private certificate authority on your machine and adds it to your system trust store, plus the Firefox and Java stores if it finds them. It then signs certificates for whatever hostnames you ask for. Your browser trusts those certificates because it trusts the local CA.
Nothing leaves your machine. There is no account, no renewal cron job and no DNS challenge. Certificates it issues are valid for a little over two years, so you set this up once per laptop and forget it.
Setting Up mkcert Step by Step
- Install it. On macOS use Homebrew: brew install mkcert, plus brew install nss if you use Firefox. On Debian or Ubuntu install libnss3-tools first, then grab the mkcert binary from your package manager or the release page. On Windows, choco install mkcert or scoop install mkcert both work.
- Create and trust the local CA. Run mkcert -install. You will get a password prompt on macOS and a confirmation dialog on Windows. This is the only step that needs elevated rights.
- Issue a certificate. From your project folder, run mkcert app.test "*.app.test" localhost 127.0.0.1 ::1. You get two files, something like app.test+4.pem and app.test+4-key.pem. Rename them to cert.pem and key.pem to keep configs readable.
- Point the hostname at your machine. Add 127.0.0.1 app.test api.app.test to your hosts file. Wildcards do not work in the hosts file, so list each subdomain you use.
- Restart the browser. Chrome and Edge usually pick up the new CA immediately. Firefox needs a full restart.
On a typical laptop the whole sequence takes three to five minutes. If you want new teammates to repeat it without reading a wiki page, wrap steps 3 and 4 in a target, as shown in this guide on setting up a Makefile task runner for small projects.
Wiring the Certificate Into Your Dev Server
Most dev servers accept a cert and key path directly. A few examples that cover a lot of small projects:
| Tool | Where the cert goes | Notes |
|---|---|---|
| Vite | server.https with key and cert read from disk | Set server.host to app.test if you use a custom name |
| Next.js | the experimental HTTPS flag on next dev, plus key and cert flags | It can generate its own cert too, but sharing one mkcert cert keeps things consistent |
| Django | runserver_plus from django-extensions, with its cert file option | Plain runserver has no TLS support |
| Caddy or nginx | tls cert.pem key.pem in the site block | Useful when several services sit behind one local proxy |
If you run three or more services locally, the proxy route is usually cleaner. One Caddy instance terminates TLS for app.test, api.app.test and auth.app.test, and each service keeps listening on plain http on its own port. Your app code never touches a certificate.
The Advice to Commit Certificates Is Wrong
You will see blog posts and starter templates that commit cert.pem and key.pem to the repo so new developers "just get HTTPS". Do not do this. The key is only harmless if every developer's machine trusts a different CA, and committed certs only work on the machine that generated them anyway. On every other laptop the browser shows a warning, so the convenience is imaginary.
Worse, the rootCA-key.pem file that mkcert creates is genuinely sensitive. Anyone who has it can mint certificates your browser trusts for any domain, including your bank. It lives in the folder shown by mkcert -CAROOT, and it should never be copied, synced to cloud storage or shared on a team drive.
The better pattern: add *.pem to .gitignore, and commit a script that generates certs on first run. Each developer gets their own CA and their own leaf certificate. It is a little slower on day one and far safer for the next two years. If you already use hooks, a check in your pre-commit hooks for small teams can block any staged .pem file before it reaches a commit.
Common Problems and Quick Fixes
- Browser still says "not secure". The hostname you typed is not in the certificate. Run openssl x509 -in cert.pem -noout -text and check the Subject Alternative Name list.
- Node rejects the cert in server-side fetch calls. Node uses its own CA bundle. Set NODE_EXTRA_CA_CERTS to the rootCA.pem path from mkcert -CAROOT.
- Docker containers do not trust it. Mount rootCA.pem into the container and run update-ca-certificates in the image, or call the service over plain http inside the Docker network.
- Testing on a phone. Install rootCA.pem on the device as a trusted profile. On iOS you also have to enable full trust under Certificate Trust Settings, which most people miss.
Mismatched setups between laptops are one of the classic sources of "works on my machine" bugs, so it helps to read about local development environment drift before rolling this out to a whole team.
FAQ
Is mkcert safe to use on a work laptop?
Yes, as long as you treat the CA key as a secret. The risk is not the certificates themselves but the root key in the CAROOT folder. Keep it off shared drives and backups that other people can read.
Can I use Let's Encrypt for local HTTPS development instead?
Only if your dev hostname is a real public domain you control and you can complete a DNS challenge. For most local work that is extra setup with no benefit. mkcert certificates are trusted only on your machine, which is exactly what you want.
Do I need HTTPS for localhost at all?
Not always. Browsers treat http://localhost as a secure context for many APIs. You need real certificates once you use custom hostnames, test on other devices, or work with login providers and cookies that demand HTTPS.
How do I remove mkcert later?
Run mkcert -uninstall to remove the CA from your trust stores, then delete the CAROOT folder. Any certificates it issued stop being trusted immediately.
More in Dev Tools
Dev Tools
How to Set Up direnv for Per Project Environment Variables
You switch from one project to another and the API still points at the wrong database, because an environment variable from the...
Dev Tools
Makefile Task Runner Setup for Small Projects
Open the README of almost any small project and you will find a block of seven or eight shell commands under a heading like...
Dev Tools
Git Pre-Commit Hooks That Small Teams Actually Keep Using
A teammate pushes a commit with a stray console.log , a 40 MB video file and an AWS key in a config file.