SSL on Localhost — HTTPS for Development
Most development happens over plain HTTP on localhost. But sometimes you need HTTPS locally: testing service workers (which require HTTPS), working with OAuth callbacks that demand HTTPS redirect URLs, testing mixed-content scenarios, using HTTP/2, or APIs that refuse non-HTTPS requests. The challenge is getting a certificate that your browser trusts without those annoying security warnings.
The Tool: mkcert
mkcert is the easiest way to get trusted HTTPS on localhost. It creates a local Certificate Authority (CA), installs it in your system's trust store, and generates certificates signed by that CA. Your browser trusts them automatically — no warnings, no clicking through scary pages.
Install mkcert
# Mac
brew install mkcert
brew install nss # Required for Firefox support
# Windows (with Chocolatey)
choco install mkcert
# Windows (with Scoop)
scoop install mkcert
# Linux
sudo apt install libnss3-tools
# Then download from https://github.com/FiloSottile/mkcert/releases
Set Up the Local CA
# Install the local CA in your system trust store (one-time setup)
mkcert -install
# You'll see: "The local CA is now installed in the system trust store!"
Generate Certificates
# Generate cert for localhost
mkcert localhost 127.0.0.1 ::1
# Creates two files:
# localhost+2.pem (certificate)
# localhost+2-key.pem (private key)
# For custom domains too
mkcert localhost 127.0.0.1 myapp.local api.local
Use with Dev Servers
Vite (Port 5173)
// vite.config.js
import fs from 'fs';
export default {
server: {
https: {
key: fs.readFileSync('./localhost+2-key.pem'),
cert: fs.readFileSync('./localhost+2.pem'),
}
}
}
// → https://localhost:5173
Next.js (Port 3000)
// Create a custom server.js or use the experimental flag:
// package.json
{
"scripts": {
"dev": "next dev --experimental-https"
}
}
// Next.js 13.5+ generates its own self-signed cert automatically
Express.js (Node)
const https = require('https');
const fs = require('fs');
const express = require('express');
const app = express();
const options = {
key: fs.readFileSync('localhost+2-key.pem'),
cert: fs.readFileSync('localhost+2.pem')
};
https.createServer(options, app).listen(3000, () => {
console.log('HTTPS server running on https://localhost:3000');
});
Nginx
# /etc/nginx/sites-available/localhost-ssl
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /path/to/localhost+2.pem;
ssl_certificate_key /path/to/localhost+2-key.pem;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
}
}
Apache
<VirtualHost *:443>
ServerName localhost
SSLEngine on
SSLCertificateFile /path/to/localhost+2.pem
SSLCertificateKeyFile /path/to/localhost+2-key.pem
DocumentRoot /var/www/html
</VirtualHost>
When You Need HTTPS Locally
| Scenario | Why HTTPS Required |
|---|---|
| Service Workers / PWA | Browsers only register service workers on HTTPS (or localhost plain HTTP as an exception, but not always reliable) |
| OAuth / Social Login | Many OAuth providers (Google, Facebook) require HTTPS callback URLs, even in development |
| Secure Cookies | Cookies with Secure flag only sent over HTTPS |
| Mixed Content Testing | Need to verify your site works fully on HTTPS before production |
| HTTP/2 | Browsers only support HTTP/2 over HTTPS |
| WebRTC | Camera/microphone access requires HTTPS (or localhost) |
| CORS with credentials | Some auth flows require matching protocol between frontend and backend |
Alternatives to mkcert
| Tool | Pros | Cons |
|---|---|---|
| mkcert | Simple, trusted by browser, one command | Must install on each machine |
| openssl (self-signed) | Available everywhere, no install | Browser shows security warning every time |
| Vite --https | Zero config for Vite users | Self-signed, shows warning |
| Caddy (reverse proxy) | Automatic HTTPS, including local | Adds another tool to your stack |
Troubleshooting
Browser still shows "Not Secure": Run mkcert -install again. Close and reopen the browser. For Firefox, you may need the nss package installed before running mkcert -install.
"NET::ERR_CERT_AUTHORITY_INVALID": The local CA isn't trusted. This happens if you generated certs on one machine and copied them to another. Run mkcert -install on each machine that needs to trust the certificates.
Port conflict with port 443: Port 443 requires admin/root privileges. Use a higher port like 8443 for development, or run your dev server behind a reverse proxy.