Skip to main content

listhen vs local-ssl-proxy vs mkcert: Local HTTPS Development in Node.js (2026)

·PkgPulse Team

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

Featurelisthenmkcertlocal-ssl-proxy
Starts server❌ (certs only)❌ (proxy only)
Auto self-signed❌ (trusted)✅ (self-signed)
Trusted certs❌ (self-signed)❌ (self-signed)
Custom certsGenerates them
Tunnel (public URL)
Open browser
Clipboard copy
Works with any server❌ (Node.js)
Code changes neededYes (handler)Yes (cert config)No
LanguageJavaScriptGoJavaScript
Weekly downloads~5MCLI tool~50K

# 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.

Compare development server and HTTPS tooling on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.