Las APIs web modernas (service workers, geolocalización, portapapeles, cámara) requieren HTTPS, incluso durante el desarrollo. Esta guía explica cómo obtener HTTPS confiable en localhost sin advertencias del navegador.
Por qué no puedes usar Let’s Encrypt para localhost
Let’s Encrypt valida la propiedad del dominio verificando un archivo en tu servidor o un registro DNS. localhost no es un dominio real: solo resuelve a 127.0.0.1 en tu máquina. Nadie puede verificar que tú «posees» localhost, por lo que ninguna CA pública emitirá un certificado para él.
Tus opciones para HTTPS en localhost:
- mkcert — crea certificados confiables localmente (recomendado)
- Certificado autofirmado — funciona pero muestra advertencias del navegador
- Servicio de túnel — ngrok, Cloudflare Tunnel (dominio real, certificado real)
Opción 1: mkcert (recomendado)
mkcert crea una Autoridad Certificadora local en tu máquina, la añade al almacén de confianza de tu sistema y emite certificados firmados por esa CA. Los navegadores confían en estos certificados sin advertencias.
Instalar mkcert
# macOS
brew install mkcert
# Linux (requires libnss3-tools for Firefox)
sudo apt install libnss3-tools
brew install mkcert # or download from GitHub releases
# Windows
choco install mkcert
# or: scoop install mkcert
Configurar la CA local
mkcert -install
# Output: Created a new local CA
# The local CA is now installed in the system trust store
Esto añade un certificado raíz a los almacenes de confianza de tu sistema y navegadores. Hazlo una sola vez: funciona para todos los certificados futuros.
Generar certificados
# For localhost
mkcert localhost 127.0.0.1 ::1
# For a custom local domain
mkcert myapp.test "*.myapp.test" localhost 127.0.0.1
# Output:
# Created a new certificate valid for the following names:
# - "localhost"
# - "127.0.0.1"
# - "::1"
# The certificate is at "./localhost+2.pem" and the key at "./localhost+2-key.pem"
Usar con tu servidor de desarrollo
Node.js/Express:
const https = require('https');
const fs = require('fs');
const app = require('./app');
https.createServer({
key: fs.readFileSync('./localhost+2-key.pem'),
cert: fs.readFileSync('./localhost+2.pem'),
}, app).listen(3000);
Nginx (local):
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /path/to/localhost+2.pem;
ssl_certificate_key /path/to/localhost+2-key.pem;
# ...
}
Vite / webpack dev server:
// vite.config.js
import fs from 'fs';
export default {
server: {
https: {
key: fs.readFileSync('./localhost+2-key.pem'),
cert: fs.readFileSync('./localhost+2.pem'),
},
},
};
Advertencia de seguridad
Nunca compartas la clave raíz de mkcert (rootCA-key.pem en mkcert -CAROOT). Cualquier persona que la tenga puede crear certificados de confianza para cualquier dominio en tu máquina, lo que equivale a una capacidad de intermediario (man-in-the-middle) local.
mkcert es solo para desarrollo. Para producción, usa Let’s Encrypt.
Opción 2: Certificado autofirmado
Si no puedes instalar mkcert, genera un certificado autofirmado con OpenSSL:
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 \
-keyout localhost-key.pem -out localhost-cert.pem \
-days 365 -nodes -subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
Desventaja: Los navegadores muestran una advertencia de seguridad porque los certificados autofirmados no son confiables para ninguna CA. Tendrás que hacer clic para pasar la advertencia cada vez (o añadir una excepción).
Opción 3: Túnel (dominio real + certificado real)
Usa un servicio de túnel para exponer tu servidor local con un dominio y certificado reales:
# ngrok
ngrok http 3000
# Gives you: https://abc123.ngrok.io
# Cloudflare Tunnel
cloudflared tunnel --url http://localhost:3000
# Gives you: https://random-name.trycloudflare.com
Cuándo tiene sentido: probar webhooks, compartir con un compañero de equipo, probar callbacks OAuth que requieren HTTPS.
mkcert vs autofirmado vs túnel
| mkcert | Autofirmado | Túnel | |
|---|---|---|---|
| Advertencias del navegador | Ninguna | Sí (cada sesión) | Ninguna |
| Esfuerzo de configuración | Bajo (una instalación) | Bajo (un comando) | Bajo |
| Funciona sin conexión | Sí | Sí | No |
| Rendimiento | Velocidad local | Velocidad local | Latencia de red |
| Dominios personalizados | Sí (*.test, etc.) | Sí | Asignado por el proveedor |
| Mejor para | Desarrollo diario | Uso rápido puntual | Webhooks, compartir |
Preguntas frecuentes
¿Puedo usar el mismo certificado mkcert en todo mi equipo?
No compartas la clave raíz de la CA. Cada desarrollador debe ejecutar mkcert -install en su propia máquina para crear su propia CA local. Puedes compartir los certificados generados (los archivos .pem), pero cada persona necesita tener la CA instalada para confiar en ellos.
¿mkcert funciona con Docker?
Sí. Monta los archivos de certificado en el contenedor y referencialos en la configuración del servidor. El contenedor en sí no necesita tener mkcert instalado, solo los archivos .pem. Tu máquina host (donde se ejecuta el navegador) necesita tener la CA de mkcert instalada.
¿Qué dominio local debo usar?
Usa .test (por ejemplo, myapp.test): está reservado por IETF para pruebas y nunca entrará en conflicto con un dominio real. Evita .dev (propiedad de Google) y .local (usado por mDNS). Añade una entrada en /etc/hosts para apuntar tu dominio .test a 127.0.0.1.
¿Cómo cambio de HTTPS en localhost a HTTPS en producción?
Son completamente independientes. Tu certificado de localhost (mkcert/autofirmado) permanece en tu máquina de desarrollo. Para producción, obtén un certificado real de Let’s Encrypt vía GetHTTPS. Tu código debe leer las rutas de los certificados desde variables de entorno para que el cambio sea solo una cuestión de configuración.