Local HTTPS Done Right: Wildcard SSL for Modern Development
Set Up Trusted Local HTTPS with Wildcard Certificates, mkcert, DNS Routing, and Zero Browser Warnings
Senior Developer

Modern web development demands HTTPS. If you are building an application on http://localhost:3000, you will inevitably hit a wall:
OAuth providers (Google, GitHub, Apple) often refuse HTTP redirect URIs.
Stripe webhooks require a secure context.
Cross-domain cookies (
SameSite=None) are strictly blocked by browsers without theSecureflag.Service Workers and modern Web APIs (Geolocation, Clipboard) fail silently.
The historical solution was generating self-signed certificates with openssl. But modern browsers hate self-signed certs. They throw terrifying red "Your connection is not private" screens, forcing you to click through hidden "Proceed anyway" menus.
Today, we are going to fix this the right way. We will become our own Certificate Authority (CA) and generate a Wildcard SSL Certificate so you can develop locally on https://app.mylocal.dev with a perfectly green, trusted padlock.
The Secret Weapon: mkcert
mkcert is an incredibly clever tool. Instead of generating a standalone self-signed certificate, it creates a local Certificate Authority (CA) and injects it into your operating system's and browser's trust stores.
Because your OS explicitly trusts this local CA, any certificate signed by it is treated as entirely valid.
Step 1: Install mkcert
macOS (via Homebrew):
brew install mkcert
# If you use Firefox, it manages its own trust store, so we need nss
brew install nss Windows (via Chocolatey):
choco install mkcertUbuntu/Debian:
sudo apt install libnss3-tools
brew install mkcert # via Linuxbrew, or download the binary from GitHubStep 2: Initialize Your Local CA
Run the following command once per machine:
mkcert -installYour operating system will prompt you for an admin password. You are giving mkcert permission to add a root certificate to your keychain.
Step 3: Generate the Wildcard Certificate
Choose a Top-Level Domain (TLD) for local development. Do not use .dev or .app—Google owns these and strictly enforces HSTS (HTTPS routing) at the DNS level, which can cause annoying conflicts.
The internet standard for local development is .test or .loc. Let's use *.myapp.test.
mkdir -p ~/local-certs && cd ~/local-certs
mkcert "*.myapp.test"This generates two files:
_wildcard.myapp.test.pem(The Public Certificate)_wildcard.myapp.test-key.pem(The Private Key)
Step 4: Map Local DNS
We need to tell your computer that api.myapp.test and app.myapp.test should resolve to localhost (127.0.0.1).
Edit your hosts file:
Mac/Linux:
sudo nano /etc/hostsWindows: Open Notepad as Administrator, then open
C:\Windows\System32\drivers\etc\hosts
Add your domains to the bottom:
127.0.0.1 myapp.test
127.0.0.1 app.myapp.test
127.0.0.1 api.myapp.testStep 5: Implementation
You have the trusted certificates and the DNS mapping. Now you just need to serve them. You can do this at the application level or the proxy level.
Approach A: Using Docker and Nginx (Recommended)
This closely mirrors a production environment. Nginx handles the SSL termination and passes standard HTTP traffic to your Node/Python app.
docker-compose.yml:
version: '3.8'
services:
web:
image: node:18
command: npm run dev
# Notice we don't expose ports directly
proxy:
image: nginx:alpine
ports:
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
# Mount the folder containing our certificates
- ~/local-certs:/etc/nginx/certs:ro
nginx.conf:
events {}
http {
server {
listen 443 ssl;
server_name app.myapp.test api.myapp.test;
ssl_certificate /etc/nginx/certs/_wildcard.myapp.test.pem;
ssl_certificate_key /etc/nginx/certs/_wildcard.myapp.test-key.pem;
location / {
proxy_pass http://web:3000;
proxy_set_header Host $host;
}
}
}
Approach B: Native Node.js (Next.js / Vite)
If you don't use Docker, you can pass the certificates directly to your dev server.
For Vite, add this to vite.config.ts:
import fs from 'fs';
export default defineConfig({
server: {
https: {
key: fs.readFileSync('/Users/bhavya/local-certs/_wildcard.myapp.test-key.pem'),
cert: fs.readFileSync('/Users/bhavya/local-certs/_wildcard.myapp.test.pem'),
},
host: 'app.myapp.test'
}
})
Crucial Tip: Node.js Backend-to-Backend Requests
If your Node.js backend tries to fetch data from your own secure local API (e.g., Server-Side Rendering hitting https://api.myapp.test), Node will throw an UNABLE_TO_VERIFY_LEAF_SIGNATURE error.
Why? Because Node.js does not use the operating system's trust store. It uses its own hardcoded list of root authorities.
To tell Node.js to trust your mkcert CA, you must pass an environment variable when starting your Node script:
# Find where mkcert put your root CA
export NODE_EXTRA_CA_CERTS="$(mkcert -CAROOT)/rootCA.pem"
# Then run your app
npm run devConclusion
Boot up your application and navigate to https://app.myapp.test.
You will be greeted by a flawless, green padlock. No warnings, no "Proceed to unsafe" buttons. Your cookies will adhere to strict security policies, third-party APIs will accept your webhooks, and your local development environment will finally mirror production parity.
Comments (0)
Login to post a comment.