listhen vs local-ssl-proxy vs mkcert: Local HTTPS Development in Node.js (2026)
TL;DR
listhen is the UnJS HTTP server launcher — starts a local server with auto HTTPS (self-signed certs), clipboard URL copy, open-in-browser, graceful shutdown, and tunnel support. local-ssl-proxy creates an HTTPS proxy in front of your HTTP dev server — run your app on HTTP, proxy adds SSL termination. mkcert generates locally-trusted SSL certificates — creates a local CA, installs it in your system trust store, no browser warnings. In 2026: listhen for quick dev server launches with auto HTTPS, mkcert for trusted certificates in any setup, local-ssl-proxy for adding HTTPS to existing HTTP servers.
Key Takeaways
- listhen: ~5M weekly downloads — UnJS, auto HTTPS, dev server launcher, tunnel support
- local-ssl-proxy: ~50K weekly downloads — HTTPS proxy, wraps any HTTP server
- mkcert: CLI tool (Go binary) — creates trusted local certificates, no browser warnings
- The problem: many APIs require HTTPS (OAuth, cookies, WebAuthn, geolocation)
- Self-signed certs trigger browser warnings — mkcert creates truly trusted certs
- listhen is the quickest path:
npx listhen ./handler.ts→ HTTPS server running
Why Local HTTPS?
APIs that require HTTPS:
🔒 OAuth 2.0 callbacks — most providers require HTTPS redirect URIs
🔒 Secure cookies — SameSite=None requires Secure flag (HTTPS only)
🔒 WebAuthn/Passkeys — only works on HTTPS origins
🔒 Geolocation API — requires secure context
🔒 Service Workers — only register on HTTPS
🔒 Web Crypto API — some features require secure context
🔒 Clipboard API — requires secure context
Development without HTTPS:
localhost is treated as secure context in most browsers
But subdomain.localhost or custom domains are NOT secure
And API callbacks need real HTTPS URLs
listhen
listhen — dev server launcher:
Quick start
# Start a server with auto HTTPS:
npx listhen ./server.ts
# Output:
# ✅ Listening on:
# → http://localhost:3000
# → https://localhost:3001
# 📋 URL copied to clipboard
# With options:
npx listhen ./server.ts --port 8080 --https --open
Programmatic usage
import { listen } from "listhen"
// Start server with handler:
const listener = await listen(
(req, res) => {
res.end("Hello from HTTPS!")
},
{
port: 3000,
https: true, // Auto-generate self-signed cert
open: true, // Open browser
clipboard: true, // Copy URL to clipboard
showURL: true, // Print URL to console
}
)
console.log(listener.url) // http://localhost:3000
console.log(listener.https?.url) // https://localhost:3001
// Graceful shutdown:
await listener.close()
With H3/Nitro handler
import { listen } from "listhen"
import { createApp, eventHandler, toNodeListener } from "h3"
const app = createApp()
app.use("/api/health", eventHandler(() => ({ status: "ok" })))
app.use("/api/data", eventHandler(() => ({ packages: 466 })))
const listener = await listen(toNodeListener(app), {
https: true,
port: 3000,
})
Tunnel support
import { listen } from "listhen"
// Expose local server to the internet:
const listener = await listen(handler, {
tunnel: true, // Creates a public tunnel URL
// → https://abc123.tunnel.dev
})
console.log(listener.tunnel?.url)
// Useful for: webhook testing, mobile testing, sharing demos
Certificate options
import { listen } from "listhen"
// Auto self-signed (default):
await listen(handler, { https: true })
// Custom certificate:
await listen(handler, {
https: {
cert: "./certs/localhost.pem",
key: "./certs/localhost-key.pem",
},
})
// Use mkcert certificates for trusted HTTPS:
await listen(handler, {
https: {
cert: "./certs/localhost+2.pem", // mkcert generated
key: "./certs/localhost+2-key.pem",
},
})
mkcert
mkcert — trusted local certificates:
Setup
# Install mkcert:
# macOS:
brew install mkcert
# Windows:
choco install mkcert
# Linux:
# Download from GitHub releases
# Install local CA (one-time setup):
mkcert -install
# → Created a new local CA 🎉
# → The local CA is now installed in the system trust store
Generate certificates
# Generate cert for localhost:
mkcert localhost
# → localhost.pem, localhost-key.pem
# Multiple domains:
mkcert localhost 127.0.0.1 ::1 myapp.local
# → localhost+3.pem, localhost+3-key.pem
# Wildcard:
mkcert "*.local.dev" local.dev localhost
# → _wildcard.local.dev+2.pem, _wildcard.local.dev+2-key.pem
Use with Node.js
import https from "node:https"
import { readFileSync } from "node:fs"
// Use mkcert certificates — NO browser warnings:
const server = https.createServer({
cert: readFileSync("./certs/localhost+2.pem"),
key: readFileSync("./certs/localhost+2-key.pem"),
}, (req, res) => {
res.end("Trusted HTTPS!")
})
server.listen(3000)
// Browser shows 🔒 padlock — no warning!
Use with Express/Fastify
import express from "express"
import https from "node:https"
import { readFileSync } from "node:fs"
const app = express()
app.get("/", (req, res) => res.json({ secure: true }))
const server = https.createServer({
cert: readFileSync("./localhost+2.pem"),
key: readFileSync("./localhost+2-key.pem"),
}, app)
server.listen(3000, () => {
console.log("Trusted HTTPS on https://localhost:3000")
})
Use with Vite
// vite.config.ts
import { readFileSync } from "node:fs"
import { defineConfig } from "vite"
export default defineConfig({
server: {
https: {
cert: readFileSync("./certs/localhost.pem"),
key: readFileSync("./certs/localhost-key.pem"),
},
},
})
Why mkcert is special
Self-signed certificates:
❌ Browser shows "Your connection is not private"
❌ Must click "Advanced → Proceed" every time
❌ Some APIs refuse to work (Service Workers, WebAuthn)
mkcert certificates:
✅ Browser shows green padlock 🔒
✅ No warnings — cert is trusted by your system
✅ All HTTPS-only APIs work correctly
✅ One-time CA install — all future certs are trusted
local-ssl-proxy
local-ssl-proxy — HTTPS proxy:
Quick usage
# Your app runs on HTTP:
node server.js # Listening on http://localhost:3000
# In another terminal, proxy with HTTPS:
npx local-ssl-proxy --source 3001 --target 3000
# Now both work:
# http://localhost:3000 → Your app (HTTP)
# https://localhost:3001 → Your app (HTTPS via proxy)
With custom certificates
# Use mkcert certs for trusted proxy:
npx local-ssl-proxy \
--source 3001 \
--target 3000 \
--cert ./localhost.pem \
--key ./localhost-key.pem
package.json setup
{
"scripts": {
"dev": "node server.js",
"dev:ssl": "concurrently \"npm run dev\" \"local-ssl-proxy --source 3001 --target 3000\""
}
}
When to use local-ssl-proxy
Best for:
✅ Adding HTTPS to an app you can't modify
✅ Quick HTTPS for testing (no code changes)
✅ Proxying any HTTP server (Express, Fastify, Django, Rails)
Limitations:
❌ Extra process running
❌ Different port (3001 vs 3000)
❌ Self-signed certs by default (use mkcert for trusted)
❌ Not needed if your framework has HTTPS built-in
Feature Comparison
| Feature | listhen | mkcert | local-ssl-proxy |
|---|---|---|---|
| Starts server | ✅ | ❌ (certs only) | ❌ (proxy only) |
| Auto self-signed | ✅ | ❌ (trusted) | ✅ (self-signed) |
| Trusted certs | ❌ (self-signed) | ✅ | ❌ (self-signed) |
| Custom certs | ✅ | Generates them | ✅ |
| Tunnel (public URL) | ✅ | ❌ | ❌ |
| Open browser | ✅ | ❌ | ❌ |
| Clipboard copy | ✅ | ❌ | ❌ |
| Works with any server | ❌ (Node.js) | ✅ | ✅ |
| Code changes needed | Yes (handler) | Yes (cert config) | No |
| Language | JavaScript | Go | JavaScript |
| Weekly downloads | ~5M | CLI tool | ~50K |
Recommended Setup for 2026
# Best combo: mkcert + your framework's HTTPS support
# 1. One-time: install mkcert and generate certs
mkcert -install
mkcert localhost 127.0.0.1 ::1
# 2. Use certs in your dev server (Vite, Next.js, etc.)
# Or with listhen:
npx listhen ./handler.ts --https.cert localhost+2.pem --https.key localhost+2-key.pem
When to Use Each
Use listhen if:
- Need a quick dev server with auto HTTPS
- Building with H3/Nitro (UnJS ecosystem)
- Want tunnel support for webhook testing
- Need clipboard copy and auto-open browser
Use mkcert if:
- Need trusted HTTPS certificates (no browser warnings)
- Working with OAuth, WebAuthn, or Service Workers
- Want to use trusted certs with any framework
- One-time setup — all future certs are trusted
Use local-ssl-proxy if:
- Need to add HTTPS to a server you can't modify
- Quick testing without any code changes
- Proxying non-Node.js servers (Python, Ruby, Go)
- Don't want to configure HTTPS in your app
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on listhen v1.x, mkcert v1.x, and local-ssl-proxy v2.x.