Why you need HTTPS in development
Developing over HTTP in 2024 isn't just bad practice: it breaks real functionality. Modern APIs like Service Workers, Push Notifications, getUserMedia (camera/mic), Geolocation, and Clipboard require secure context (HTTPS) to work. Chrome and Firefox have blocked these features on HTTP for years.
Another problem: cookie behavior changed. SameSite=None (needed for cross-site cookies) requires Secure flag, which only works on HTTPS. If your app uses cookie-based authentication between subdomains or OAuth integrations, you'll have production bugs that don't appear in HTTP development.
Mixed Content is another headache: if your final app runs on HTTPS but you develop on HTTP, you won't detect blocked requests to HTTP resources (images, scripts, APIs). This creates silent bugs that only appear in production.
Finally, network differences between HTTP and HTTPS affect performance: HTTP/2 is only available over TLS, and features like Server Push and multiplexing behave differently. Testing over HTTP gives you false speed metrics.
mkcert: the tool you should use
Forget complicated OpenSSL commands. mkcert is a Go tool that generates locally-trusted certificates in one command, installing its own CA in your system's trust store.
Installation: brew install mkcert (Mac), choco install mkcert (Windows), or apt install mkcert (Linux). Then mkcert -install to install local CA (only once).
Generate certificate: mkcert localhost 127.0.0.1 ::1 creates two files (localhost.pem and localhost-key.pem) that work immediately in Chrome, Firefox, Safari and Edge without security warnings.
For wildcards: mkcert '*.myapp.local' covers all subdomains. This is useful for multi-tenant architectures or when testing with dynamic subdomains.
Advantages over OpenSSL: you don't have to edit config files, no manual CA imports in each browser, works out-of-the-box with tools like curl and wget, and certificates are automatically valid in all browsers that respect the system trust store.
For CI/CD: mkcert is not for production, only development. In pipelines, use self-signed certificates with OpenSSL and configure HTTP client to ignore SSL validation (only in tests, never in prod).
Certificates with Subject Alternative Names (SAN)
Modern certificates require SAN (Subject Alternative Names) to specify multiple domains. The CN (Common Name) field has been deprecated since 2017 and Chrome/Firefox ignore it.
To generate a cert with SAN in OpenSSL (without mkcert), you need a configuration file. Quick command: openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost' -addext 'subjectAltName=DNS:localhost,DNS:*.localhost,IP:127.0.0.1'
This creates a certificate valid for localhost, all its subdomains (*.localhost), and IP 127.0.0.1. Essential if your app uses both localhost and 127.0.0.1 interchangeably (common in Docker).
For wildcards: DNS:*.example.local covers sub.example.local but NOT example.local. You must include both: DNS:example.local,DNS:*.example.local.
Common error: forgetting to include IP. If your app makes requests to https://127.0.0.1:3000, you need IP:127.0.0.1 in SAN, not just DNS:localhost.
To verify SANs in existing certificate: openssl x509 -in cert.pem -text -noout | grep 'Subject Alternative Name' -A 1
Configuration in frameworks and servers
Node.js/Express: The native https module accepts options with key and cert. Load files with fs.readFileSync(). For development, use https-localhost (npm package) which generates certificates automatically.
Next.js: Create a custom server.js with https.createServer, or use the next-https package. In next.config.js there's no native HTTPS support in dev server, you need a wrapper.
Vite: In vite.config.js, add: server: { https: { key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem') } }. Better yet, install @vitejs/plugin-basic-ssl which generates certificates automatically.
React (Create React App): Set HTTPS=true in .env. CRA generates its own self-signed certificate, but you'll have browser warnings. To avoid them, generate one with mkcert and point with SSL_CRT_FILE and SSL_KEY_FILE.
Docker: Mount certificates as volumes: -v ./cert.pem:/app/cert.pem -v ./key.pem:/app/key.pem. In docker-compose, use volumes: - ./cert.pem:/app/cert.pem:ro (read-only for security).
Nginx: In your server block, ssl_certificate and ssl_certificate_key point to your files. Add ssl_protocols TLSv1.2 TLSv1.3; and ssl_ciphers HIGH:!aNULL:!MD5; for best practices.