codingstairs
NotesEDULifeContact
⌕Search⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

Get in touch

Send without signing in. Add your email if you'd like a reply.

  • Leave a message anonymously →
  • ✉ warragon112@gmail.com
  • KakaoTalk Open Chat ↗

© 2026 codingstairs

  • Notes
  • EDU
  • Search
  • Life
  • Contact
  • Legal
  • RSS
  • GitHub
Notes›infra

Local HTTPS — mkcert and a Self CA

Published 2026-04-28· Updated 2026-05-18·0 views

Local HTTPS — mkcert and a Self CA

Some modern web features simply do not work over HTTP. Service Worker, WebAuthn, parts of the media APIs, and the Secure flag for Cookie SameSite=None are the cases. In production HTTPS is naturally there, but local development needs explicit setup. This piece covers when local HTTPS is needed, the mkcert model, the OS trust stores, alternatives (openssl, ngrok, Caddy local CA), and subtle places like HSTS preload conflicts.

1. Where Local HTTPS is Needed

As browser security models keep tightening, more features only allow https:// or http://localhost.

Feature HTTP localhost HTTP non-localhost HTTPS
Service Worker, PWA OK No OK
WebAuthn OK No OK
getUserMedia (camera, mic) Partial No OK
SameSite=None Cookie Secure enforced Secure enforced OK
Cross-origin Cookie, CORS Limited Limited Natural

localhost itself is treated as a secure context, so some features work. Local HTTPS becomes necessary in these places.

  • When using virtual hosts (app.test, myapp.local).
  • Testing SameSite=None Secure cookies.
  • Verifying production-equivalent flows (HSTS, redirects, cookie behavior).
  • Trying WebRTC, camera, or microphone access over IP-based access.

2. About mkcert

A local CA tool published by Filippo Valsorda in 2018. A single binary written in Go.

The flow:

  1. On first run, generate a self-signed CA (root certificate) trusted only on the local machine.
  2. Register that CA in the OS and browser trust stores.
  3. Sign domain certificates with that CA.
# Mac
brew install mkcert nss   # nss for Firefox trust
mkcert -install
mkcert localhost 127.0.0.1 ::1 myapp.local "*.myapp.local"

# Linux
sudo apt install libnss3-tools mkcert
mkcert -install
mkcert localhost 127.0.0.1 myapp.local

# Windows (PowerShell, scoop or chocolatey)
scoop install mkcert
mkcert -install
mkcert localhost 127.0.0.1 myapp.local

The output is myapp.local+3.pem (certificate) and myapp.local+3-key.pem (private key). Point the dev server at these two files.

3. Vite, Next, or a Plain Node Server

// Vite
import fs from 'node:fs';
export default {
  server: {
    https: {
      cert: fs.readFileSync('./myapp.local+3.pem'),
      key:  fs.readFileSync('./myapp.local+3-key.pem'),
    },
  },
};
// Node http2
import http2 from 'node:http2';
http2.createSecureServer({
  cert: fs.readFileSync('./myapp.local+3.pem'),
  key:  fs.readFileSync('./myapp.local+3-key.pem'),
}, app).listen(8443);

4. OS Trust Stores

mkcert -install handles this automatically, but the internals differ per OS.

OS Store Tool
macOS System Keychain security add-trusted-cert
Windows Trusted Root Certification Authorities certmgr.msc or certutil
Linux (Debian / Ubuntu) /usr/local/share/ca-certificates/ + update-ca-certificates ca-certificates package
Linux (Fedora / RHEL) /etc/pki/ca-trust/source/anchors/ + update-ca-trust ca-certificates

Browsers:

  • Chrome, Edge, Safari — use the OS trust store as is.
  • Firefox — separate NSS database. mkcert auto-registers via the nss tool.

5. Other Paths

openssl self-signed — enough for one-off, server-to-server, or local single-use cases (CA isn't trusted, so warnings every time):

openssl req -x509 -newkey rsa:4096 -nodes \
  -keyout key.pem -out cert.pem \
  -days 365 -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

ngrok, localhost.run, cloudflared tunnel — expose a local server as an external HTTPS URL:

Tool Note
ngrok (2013) The oldest. Free plus paid static domains.
Cloudflare Tunnel Combines a Cloudflare account and domain. Wide free tier.
localhost.run Expose via SSH alone, no signup.
Tailscale Funnel Expose via a Tailnet domain.

The advantage is no self CA required (the cert is already trusted). The drawback is the external service dependency.

6. Caddy's Self Local CA

On first run, Caddy can create its own local CA and register it in the OS trust store. The model is the same as mkcert, but it merges naturally with Caddy's reverse-proxy flow.

{
  local_certs
}

myapp.local {
  reverse_proxy 127.0.0.1:3000
}

This single block makes https://myapp.local work with a trusted certificate. No separate mkcert install.

7. Built-in Options

Some frameworks have their own HTTPS option:

  • next dev --experimental-https (Next 13.5 onwards).
  • Vite — set server.https = true and it auto-self-signs (browser warning shows).

Built-in is fast but the certificate isn't trusted. For real testing the recommended flow is mkcert or Caddy.

8. hosts File + mkcert

To test on a virtual host instead of localhost, add a hosts mapping:

# Mac / Linux: /etc/hosts
# Windows: C:\Windows\System32\drivers\etc\hosts
127.0.0.1 myapp.local
127.0.0.1 api.myapp.local
mkcert myapp.local "*.myapp.local"

Adding *.myapp.local as a wildcard reuses the same certificate as subdomains grow.

9. Team Sharing is Discouraged

mkcert's CA is safe only when trusted on a single machine. Sharing the same CA across a team is discouraged — if that CA leaks, every machine on the team is exposed. Each developer runs mkcert -install and issues on their own machine.

For HTTPS testing in CI, use a self-signed cert plus NODE_TLS_REJECT_UNAUTHORIZED=0, or an isolated environment like testcontainers. Installing mkcert on a CI machine creates a new CA each time and causes confusion.

10. Common Pitfalls

Firefox not recognizing the cert — without the NSS library (libnss3-tools, nss), Firefox's trust store doesn't get auto-registered. mkcert prints a warning.

HSTS preload conflicts — some TLDs like .dev, .app, .test are in the HSTS preload list, forcing HTTP to redirect to HTTPS. .local or .localhost are relatively safe.

Using a certificate from a machine that doesn't have mkcert — using the same cert on someone else's machine triggers a warning because the OS doesn't know the CA. Issue per machine.

Permissions on ports 80 / 443 — regular users can't bind below 1024 (Linux). Use a high port like 8443, setcap, or port forwarding.

Missing SAN in the certificate — modern browsers reject CN-only certs. Put every host and IP in subjectAltName.

Validity period — mkcert domain certs default to 825 days. Reissue after expiry.

Trust store reset — OS reinstall or login user change makes the CA disappear. Run mkcert -install again.

Closing thoughts

Most local development is fine with the http://localhost secure context. Only when production-equivalent flows like PWA or WebAuthn need to be tested as is, mkcert (single machine) or Caddy local_certs (combined with reverse proxy) becomes worth adopting. For CI, testcontainers is cleaner.

Next

  • cloud-emulator-stack
  • (end of infra)

Refer to mkcert on GitHub, Caddy Local Certs, openssl x509 docs, ngrok, Cloudflare Tunnel, Tailscale Funnel, MDN Secure Contexts, and HSTS Preload.

More in infra

All in this category →
  • Cloud Emulator Stack — Designing a 4th Environment
  • The Place of Single-server Operations
  • Loopback Binding and SSH Tunnel
  • Caddy and nginx — A Comparison
  • Docker Compose Patterns
  • Docker Basics