Cómo forzar redirección HTTPS del puerto 80 al 443 en Nginx

Aprende a configurar Nginx para forzar redirección permanente del puerto 80 al 443, garantizando HTTPS en todas las solicitudes con 301 y HSTS.

Cuando un servidor web acepta conexiones en HTTP (puerto 80) y HTTPS (puerto 443) simultáneamente, el tráfico no cifrado sigue siendo viable — cualquier cliente que escriba el dominio sin prefijo, haga clic en un enlace antiguo o siga un marcador http:// va a llegar al puerto 80 y transmitir credenciales en texto plano. La corrección es forzar una redirección permanente 301 del puerto 80 al puerto 443 en Nginx, garantizando que toda solicitud sea terminada en TLS.

Este tutorial muestra la configuración correcta de redirección HTTP → HTTPS en Nginx, incluyendo el manejo del desafío http-01 de Let’s Encrypt (que necesita HTTP puro para validar), activación progresiva de HSTS y validación de la configuración antes del reload. Se aplica a Nginx 1.18+ en cualquier distribución Linux (Ubuntu, Debian, Rocky, Alma).

Tiempo de ejecución: cerca de 10 minutos, incluyendo pruebas. Persona objetivo: developer o sysadmin con Nginx ya configurado sirviendo el sitio en ambos puertos, que necesita consolidar todo en HTTPS sin romper la renovación de certificado.

Prerrequisitos

Antes de empezar

Necesitas Nginx 1.18+ corriendo en una VPS Linux, con certificado TLS válido ya instalado (Let’s Encrypt, ZeroSSL o comercial), acceso sudo vía SSH y el dominio respondiendo en los puertos 80 y 443 actualmente. Confirma con nginx -v y curl -I https://tudominio.com.

Puerto origen 80 (HTTP)
Puerto destino 443 (HTTPS)
Status code 301 Permanent
Archivo config /etc/nginx/sites-available/

Antes de tocar Nginx, haz un backup de la configuración actual. Un error de sintaxis en un server block deja el servicio sin levantar hasta que lo corrijas, y tocar producción sin red de seguridad es invitación al downtime.

sudo cp -r /etc/nginx /etc/nginx.bak.$(date +%Y%m%d)

El comando duplica todo el directorio con timestamp — si algo sale mal, basta con revertir con sudo rm -rf /etc/nginx && sudo mv /etc/nginx.bak.YYYYMMDD /etc/nginx.

Estructura recomendada: dos server blocks

El enfoque correcto separa la redirección HTTP en un server block propio, dejando el HTTPS en un segundo block. Esto evita la lógica condicional (if ($scheme = http)) que el autor de Nginx desaconseja explícitamente — el if dentro de location en Nginx tiene comportamiento contraintuitivo e históricamente fue fuente de bugs.

01

Abre el archivo de configuración del sitio en sites-available. En distribuciones basadas en Debian/Ubuntu, el estándar es /etc/nginx/sites-available/tudominio.com. En Rocky/Alma, puede estar en /etc/nginx/conf.d/tudominio.conf:

sudo nano /etc/nginx/sites-available/tudominio.com

Si aún no tienes un archivo separado por sitio y todo está en nginx.conf, esta es una buena oportunidad para extraer los server blocks a archivos individuales — facilita el mantenimiento y la auditoría.

02

Sustituye el contenido por la plantilla a continuación. Dos server blocks: el primero escucha en el puerto 80 y redirige todo (excepto el desafío ACME) a HTTPS; el segundo termina el TLS y sirve el sitio:

# Server block HTTP — redirect 301 a HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name tudominio.com www.tudominio.com;

    # Mantiene el desafío de Let's Encrypt funcionando
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
        default_type "text/plain";
        allow all;
    }

    # Redirect catch-all a HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

# Server block HTTPS — termina TLS y sirve el sitio
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name tudominio.com www.tudominio.com;

    ssl_certificate /etc/letsencrypt/live/tudominio.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/tudominio.com/privkey.pem;

    # HSTS — empieza con 300s, sube después de validar
    add_header Strict-Transport-Security "max-age=300" always;

    root /var/www/tudominio.com;
    index index.html index.php;

    location / {
        try_files $uri $uri/ =404;
    }
}

La directiva return 301 https://$host$request_uri preserva el dominio accedido ($host) y la ruta completa con query string ($request_uri), entonces http://tudominio.com/blog?id=42 se convierte en https://tudominio.com/blog?id=42 sin pérdida de contexto.

03

Garantiza que el directorio del desafío ACME existe y tiene permiso de lectura para el usuario de Nginx:

sudo mkdir -p /var/www/letsencrypt/.well-known/acme-challenge
sudo chown -R www-data:www-data /var/www/letsencrypt
sudo chmod -R 755 /var/www/letsencrypt

En Rocky/Alma el usuario es nginx, no www-data — ajusta según tu distribución. Ese directorio necesita estar accesible en HTTP puro para que Certbot renueve el certificado cada 60-90 días.

Sintaxis antes del reload

Nunca ejecutes nginx -s reload sin probar la sintaxis primero. Un error de configuración derriba todos los sitios servidos por el mismo daemon, no solo el que estás editando.

04

Prueba la sintaxis de la configuración antes de aplicar:

sudo nginx -t

La salida esperada es:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Si ves un error, lee el mensaje — Nginx indica el archivo y la línea exacta. Corrige antes de continuar.

05

Aplica la nueva configuración con reload (no restart) — reload hace un graceful swap, manteniendo las conexiones activas:

sudo systemctl reload nginx

En sistemas sin systemd: sudo nginx -s reload. El servicio sigue respondiendo durante el cambio; las conexiones en curso terminan en el worker viejo y las nuevas solicitudes van al worker con la config nueva.

Verificación

Confirma que la redirección está activa probando vía curl sin abrir el navegador (la caché del navegador puede enmascarar problemas):

curl -I http://tudominio.com

La respuesta debe mostrar:

HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://tudominio.com/

Para confirmar que el destino HTTPS responde correctamente, usa -L para seguir la redirección:

curl -IL http://tudominio.com

Verás dos respuestas: primero el 301 de Nginx, después el 200 OK del HTTPS. Si la segunda es 502, 503 u otro error, el problema no es la redirección — es el backend HTTPS.

Prueba también que el desafío ACME sigue accesible en HTTP:

sudo mkdir -p /var/www/letsencrypt/.well-known/acme-challenge
echo "prueba-ok" | sudo tee /var/www/letsencrypt/.well-known/acme-challenge/prueba
curl http://tudominio.com/.well-known/acme-challenge/prueba

La respuesta debe ser prueba-ok en texto puro — no una redirección 301. Si viene una redirección, el orden de los location está equivocado.

Resolución de problemas

Loop de redirección infinito

Síntoma: el navegador muestra el error “ERR_TOO_MANY_REDIRECTS”. Causa común: la aplicación detrás de Nginx (PHP, Node) detecta $_SERVER['HTTPS'] como vacío y redirige a HTTPS de nuevo, pero Nginx ya redirigió — ciclo infinito.

Solución: asegúrate que el server block HTTPS pase el header correcto al upstream:

proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

La aplicación debe confiar en ese header en vez de inspeccionar $_SERVER['HTTPS'] directamente.

HSTS atascado en HTTPS roto

HSTS es difícil de deshacer

Si activas max-age=31536000 (1 año) y después descubres que el certificado tiene problema, el navegador va a rechazar HTTP durante un año entero aunque revirtas la config. La única salida es hacer que el usuario limpie HSTS manualmente en chrome://net-internals/#hsts (cada navegador tiene su path).

Por eso la plantilla empieza con max-age=300. Después de una semana validando que todo funciona, sube a max-age=31536000; includeSubDomains — ese es el valor para producción estabilizada.

Renovación de Let’s Encrypt falla con 301

Síntoma: certbot renew retorna el error “Failed authorization procedure” después de activar la redirección. Causa: la directiva location ^~ /.well-known/acme-challenge/ está debajo del location / en el archivo, entonces el catch-all redirige primero.

El modificador ^~ en la plantilla tiene prioridad sobre location /, pero si editaste y perdiste el ^~, el orden importa. Garantiza que el location del ACME aparezca antes del catch-all O usa ^~ que da prioridad independientemente del orden.

Próximos pasos

Con HTTPS forzado y validación confirmada, profundiza en la seguridad y performance del servidor:

  • Hardening TLS: ajusta ssl_protocols TLSv1.2 TLSv1.3 y ssl_ciphers para remover suites legadas — usa el generador de Mozilla SSL Config como base.
  • OCSP Stapling: activa ssl_stapling on para reducir la latencia de validación de certificado en la primera solicitud.
  • HSTS preload: después de 1 año con max-age=31536000; includeSubDomains; preload, envía el dominio en hstspreload.org para entrar en la lista de los navegadores.
  • HTTP/3: Nginx 1.25+ soporta QUIC nativo — vale activarlo para clientes con latencia alta.
  • Logs separados: configura un access_log específico del server block HTTPS para facilitar el análisis de tráfico.

Si estás ejecutando esta config en producción, una VPS Hostini ya viene con Ubuntu/Debian optimizado y SSH endurecido — buena base para levantar Nginx con TLS sin tunear el kernel manualmente.

Preguntas frecuentes

¿Por qué usar 301 y no 302 en la redirección HTTPS?

El 301 es permanente — le indica al navegador y a los buscadores que cacheen el destino HTTPS indefinidamente, transfiriendo PageRank correctamente. El 302 es temporal y obliga al cliente a rehacer la solicitud HTTP cada vez, desperdiciando latencia y perjudicando el SEO. Para HTTPS forzado en producción, siempre 301.

¿Puedo usar `return 301` o necesito `rewrite`?

Usa `return 301` — es más económico para Nginx (no compila regex) y más explícito. `rewrite ... permanent` hace lo mismo pero fuerza el parser de regex e históricamente fue causa de loops cuando se combinó con `if`. Desde Nginx 0.9.1 el `return` es el estándar recomendado para redirecciones literales.

¿La redirección rompe el `http-01` de Let's Encrypt?

Lo rompe si no excluyes la ruta `/.well-known/acme-challenge/` de la redirección. Certbot necesita servir archivos vía HTTP puro en el puerto 80 durante la validación. La configuración mostrada en el tutorial usa un `location` específico que sirve el desafío en HTTP antes de la redirección catch-all, manteniendo la renovación automática funcionando.

¿Debo activar HSTS junto con la redirección?

Sí, pero con cuidado. HSTS (Strict-Transport-Security) hace que el navegador recuerde acceder solo por HTTPS durante N segundos, evitando la primera solicitud en HTTP. Comienza con `max-age=300` (5 min) para probar; solo después sube a `max-age=31536000` (1 año) con `includeSubDomains`. Equivocarse con HSTS deja el dominio bloqueado en HTTPS roto por meses en la caché del navegador.

¿La redirección funciona si el cliente accede por IP en lugar de dominio?

No con la configuración `server_name ejemplo.com`. Nginx solo responde con ese server block cuando el Host header coincide. Para capturar solicitudes por IP, agrega un server block default con `listen 80 default_server` que retorne 444 (cerrar conexión sin respuesta) o redirija al dominio canónico. Esto bloquea escáneres aleatorios.

¿Cómo verifico si la redirección está activa sin abrir el navegador?

Usa `curl -I http://tudominio.com` — la respuesta debe mostrar `HTTP/1.1 301 Moved Permanently` y el header `Location: https://tudominio.com/`. Si ves 200 OK, la redirección no está activa. Agrega `-L` para seguir la redirección y confirmar que el HTTPS responde 200 en el destino.

Temas:
Próximos pasos Cloud Ryzen con NVMe y protección DDoS siempre activa.Pon en producción en un VPS Hostini →
¿Te resultó útil este tutorial?
Hablar por WhatsApp