Some browser features simply refuse to work without HTTPS — geolocation, Service Workers, WebRTC, and most OAuth flows among them. Deploying to a remote server every time you need to test one of these is slow and unnecessary.
With
mkcertandstunnel, you can have a fully trusted HTTPS environment running onlocalhostin under two minutes, no cloud hosting required.What this guide covers:
Generating a locally trusted certificate with
mkcertWrapping your dev server in a TLS tunnel with
stunnelFixing the most common setup errors
Why HTTPS on Localhost Matters
Running HTTP locally is fine for basic UI work, but it breaks down quickly once you start integrating with external services or using security-dependent browser APIs. A local HTTPS setup lets you:
Test secure API integrations (Stripe, Google Maps, Auth0) without deploying
Eliminate mixed-content warnings during frontend development
Use browser features that only activate over a secure connection
Replicate production conditions accurately from day one
Step 1: Install mkcert
mkcert is a zero-config tool that creates locally trusted development certificates. Unlike self-signed certificates, ones generated by mkcert are accepted by your browser without warnings because the tool installs its own local certificate authority.
On macOS with Homebrew:
brew install mkcert
mkcert -install
Once installed, generate a certificate for localhost:
mkcert -key-file ~/localhost-key.pem -cert-file ~/localhost-cert.pem localhost
This produces two files — a private key and a signed certificate — both scoped to localhost.
Step 2: Bundle the Certificate Files
Combine the key and certificate into a single file for easier use with stunnel:
cat ~/localhost-key.pem ~/localhost-cert.pem > ~/localhost-bundle.pem
Step 3: Set Up stunnel
stunnel is a lightweight proxy that wraps an existing TCP service in TLS encryption. It sits in front of your dev server and handles the HTTPS layer transparently.
Install it:
brew install stunnel
Then start it, pointing it at your dev server:
sudo stunnel3 -f -d 443 -r 8000 -p ~/localhost-bundle.pem
What each flag does:
-d 443— listens on the standard HTTPS port-r 8000— forwards decrypted traffic to your local server on port 8000-p— specifies the certificate bundle to use
Tip: If port 443 is already in use, swap
-d 443for-d 8443and access your site athttps://localhost:8443instead.
Step 4: Test the Setup
Start your local server on port 8000:
npm run dev --port 8000
Then open https://localhost in your browser. If everything is configured correctly, your site loads over HTTPS with no certificate warnings.
Troubleshooting
Certificate not trusted This usually means mkcert -install was skipped. Run it now — it registers the local CA with your system trust store and browsers pick it up automatically.
Port 443 already in use Switch to an alternate port:
sudo stunnel3 -f -d 8443 -r 8000 -p ~/localhost-bundle.pem
Then visit https://localhost:8443.
Command not found Confirm both tools are installed and on your $PATH:
which mkcert && which stunnel3
If either returns nothing, re-run the install step and open a new terminal session.
Key Takeaways
mkcertgenerates locally trusted certificates that browsers accept without warnings.stunnelwraps any local server in a TLS tunnel without modifying the server itself.Bundling the key and certificate into one file keeps the
stunnelcommand clean.Port 8443 is a reliable fallback when 443 is unavailable.
This entire setup takes under two minutes and requires no external hosting.
Conclusion
Local HTTPS is one of those things that feels like extra work until you set it up once and realize how little effort it actually takes. mkcert handles certificate trust so your browser never complains, and stunnel handles encryption so your server does not have to.
Next time a browser API refuses to cooperate or an OAuth flow demands a secure origin, this setup has you covered — no deployment, no workarounds.
Have a question about the setup or want to see a version using nginx instead of stunnel? Leave a comment below.




