HTTPS for Local Development
How to set up trusted HTTPS certificates for local development using mkcert
Some browser features only work over HTTPS – geolocation, camera access, service workers, secure cookies, and Web Push notifications all require a "secure context." Most browsers treat plain localhost as secure by default, but if you're using a custom hostname, testing on another device, or need real HTTPS behavior, you'll need to set up certificates.
The easiest way is mkcert, a tool that creates locally-trusted certificates with zero configuration.
Why not self-signed certificates?
You can create a certificate with openssl and it will technically work, but every browser will show a "Your connection is not private" warning. You'd have to click through it every time, and some features still won't work properly. mkcert avoids this by installing a local Certificate Authority (CA) that your system trusts.
Installing mkcert
- Install via Homebrew:
brew install mkcert - Install the local CA into your system trust store:
This creates a root certificate and adds it to macOS Keychain. You only need to do this once. Browsers that use the system trust store (Chrome, Edge, Safari) will automatically trust certificates created by mkcertmkcert -install
For Firefox, also install nss:
brew install nss
Then run mkcert -install again so it adds the CA to Firefox's trust store too.
Generating certificates
Navigate to your project directory (or a certs/ folder) and run:
mkcert localhost 127.0.0.1 ::1
This creates two files in the current directory:
localhost+2.pem– the certificatelocalhost+2-key.pem– the private key
The +2 means you included two extra names beyond the first (127.0.0.1 and ::1). If you also need a custom hostname:
mkcert localhost myapp.local 127.0.0.1 ::1
Using with dev servers
Vite:
// vite.config.ts
import fs from 'node:fs'
import path from 'node:path'
export default defineConfig({
server: {
https: {
key: fs.readFileSync(path.resolve(__dirname, './localhost+2-key.pem')),
cert: fs.readFileSync(path.resolve(__dirname, './localhost+2.pem')),
},
},
})
Next.js:
The simplest option is the built-in flag:
next dev --experimental-https
This generates a self-signed cert automatically. For a mkcert cert, use a custom server or set NODE_EXTRA_CA_CERTS.
Express:
import https from 'node:https'
import fs from 'node:fs'
import express from 'express'
const app = express()
https.createServer({
key: fs.readFileSync('./localhost+2-key.pem'),
cert: fs.readFileSync('./localhost+2.pem'),
}, app).listen(3000)
webpack-dev-server:
// webpack.config.js
module.exports = {
devServer: {
server: {
type: 'https',
options: {
key: './localhost+2-key.pem',
cert: './localhost+2.pem',
},
},
},
}
Caddy as an alternative
If you use Caddy as a reverse proxy, it handles HTTPS automatically – generating and trusting certificates for you with no extra setup. Point Caddy at your dev server and access it via HTTPS:
myapp.localhost {
reverse_proxy localhost:3000
}
Caddy is a great option if you're already using it or want to avoid managing certificate files.
Frequently Asked Questions
Do I really need HTTPS for local development?▾
For most work, no. Browsers treat plain http://localhost as a secure context, so APIs like geolocation and camera still work. You need actual HTTPS when using a custom local hostname (like myapp.local), testing service workers with non-localhost origins, accessing your dev server from another device, or working with secure cookies.
What's wrong with self-signed certificates?▾
They work for encryption, but browsers don't trust them. You'll see a "Your connection is not private" warning every time, and some features (like service workers) may not work at all. mkcert solves this by creating a local CA that your OS and browsers trust.
Will mkcert certificates work on my phone?▾
Not automatically. mkcert only installs the CA on the machine where you ran mkcert -install. To trust it on a phone, you need to transfer the CA certificate (find it with mkcert -CAROOT) to the device and manually install it. On iOS, you also need to enable full trust in Settings > General > About > Certificate Trust Settings.
Do I need to regenerate certificates when they expire?▾
mkcert certificates are valid for about 2 years by default. If they expire, just run the mkcert command again to generate new ones. You don't need to run mkcert -install again – the CA is still trusted.
I'm getting "Your connection is not private" on localhost. What do I do?▾
If you're using plain http://localhost without any HTTPS setup, this usually means something is redirecting you to HTTPS. Check for HSTS headers in your app or browser. If you set up HTTPS with self-signed certificates, switch to mkcert instead. If you're already using mkcert, make sure you ran mkcert -install and restarted your browser. See our guide on connection not private errors for more details.