¿Cómo forzar o redirigir a SSL en nginx?

222

Tengo una página de registro en un subdominio como: https://signup.example.com

Solo debería ser accesible a través de HTTPS, pero me preocupa que la gente pueda tropezar con él a través de HTTP y obtener un 404.

Mi bloque html / server en nginx se ve así:

html {
  server {
    listen 443;
    server_name signup.example.com;

    ssl                        on;
    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;

    ssl_session_timeout 30m;

    location / {
      root /path/to/my/rails/app/public;
      index index.html;
        passenger_enabled on;
    }
  }
}

¿Qué puedo agregar para que las personas que van a http://signup.example.comser redirigidas https://signup.example.com? (Para su información, sé que hay complementos de Rails que pueden forzar, SSLpero esperaba evitar eso)

Callmeed
fuente

Respuestas:

145

De acuerdo con las trampas de nginx , es un poco mejor omitir la captura innecesaria, utilizando en su $request_urilugar. En ese caso, agregue un signo de interrogación para evitar que nginx duplique cualquier argumento de consulta.

server {
    listen      80;
    server_name signup.mysite.com;
    rewrite     ^   https://$server_name$request_uri? permanent;
}
Pratik Amin
fuente
68
O, según el sitio que haya vinculado, "MEJOR" :return 301 http://domain.com$request_uri;
nh2
13
un comentario. $ server_name $ recoge la primera variable nombre_servidor. Así que tenga en cuenta esto si tiene nombres que no son FQN en su configuración
engineerDave
2
@ nh2 Este es otro caso de que la documentación es incorrecta ya que el uso return 301...causa un error de "demasiados redireccionamientos" mientras el método de reescritura realmente funciona.
Mike Bethany
1
Eso ahora está documentado como "también MALO". @MikeBethany return 301funciona, a menos que (supongo) lo esté activando también para las URL correctas, escuchando en ambos puertos (ejemplo de configuración que desencadena el problema: tome la primera respuesta de serverfault.com/a/474345/29689 y omita el if )
Blaisorblade
1
Me pregunto qué ha cambiado con los años y si esta otra respuesta es mejor: serverfault.com/a/337893/119666
Ryan
256

La mejor manera como se describe en el manual oficial es mediante el uso de la returndirectiva:

server {
    listen      80;
    server_name signup.mysite.com;
    return 301 https://$server_name$request_uri;
}
VBart
fuente
55
respuesta más corta y funcionó perfectamente en mi caso
mateusz.fiolka
1
Esto generalmente se recomienda porque devuelve un 301 Moved Permanently(sus enlaces se han movido permanentemente), así como reescribir
sgb
1
Esto no funciona, ya que causa un error de "demasiados redireccionamientos" incluso si ha configuradoproxy_set_header X-Forwarded-Proto https;
Mike Bethany
1
@MikeBethany, ¿estás definiendo listen 443;en el mismo bloque?
Joe B
2
Esta debería ser la respuesta aceptada.
sjas
119

Esta es la forma correcta y más eficiente si desea mantener todo en un bloque de servidor:

server {
    listen   80;
    listen   [::]:80;
    listen   443 default_server ssl;

    server_name www.example.com;

    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;

    if ($scheme = http) {
        return 301 https://$server_name$request_uri;
    }
}

Todo lo demás anterior, usando "rewrite" o "if ssl_protocol", etc. es más lento y peor.

Aquí es lo mismo, pero aún más eficiente, al ejecutar solo la reescritura en el protocolo http, evita tener que verificar la variable $ esquema en cada solicitud. Pero en serio, es algo tan pequeño que no es necesario separarlos.

server {
    listen   80;
    listen   [::]:80;

    server_name www.example.com;

    return 301 https://$server_name$request_uri;
}
server {
    listen   443 default_server ssl;

    server_name www.example.com;

    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;
}
DELETEDACC
fuente
8
Genial, algún cobarde rechazó esta respuesta sin decir por qué, a pesar de que esta respuesta es correcta. Tal vez otro de esos cultistas "si es malo". Si te molestas en leer la documentación de Nginx sobre If, ​​sabrás que IfIsNOTEvil, solo CIERTOS los usa dentro de un contexto de ubicación {}, ninguno de los cuales hacemos aquí. ¡Mi respuesta es absolutamente la forma correcta de hacer las cosas!
DELETEDACC
2
No rechacé votar esto, pero me gustaría señalar que el valor predeterminado se ha cambiado a 'default_server' en las versiones más recientes.
Spuder
La primera solución no puede ser la más eficiente, si la segunda es aún más eficiente. Incluso describió por qué no debería usar un if allí: "evita tener que verificar la variable $ esquema en cada solicitud". El punto de no usar ifs no solo se trata del rendimiento, sino también de ser declarativo y no imperativo.
pepkin88
+1 para if ($ esquema = http)
Fernando Kosh
Debería usar $ host aquí, como se menciona en las otras respuestas.
Artem Russakovskii
56

Si está utilizando la nueva definición de servidor dual HTTP y HTTPS, puede utilizar lo siguiente:

server {
    listen   80;
    listen   [::]:80;
    listen   443 default ssl;

    server_name www.example.com;

    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;

    if ($ssl_protocol = "") {
       rewrite ^   https://$server_name$request_uri? permanent;
    }
}

Esto parece funcionar para mí y no causa bucles de redireccionamiento.

Editar:

Reemplazado:

rewrite ^/(.*) https://$server_name/$1 permanent;

con la línea de reescritura de Pratik.

David Pashley
fuente
2
@DavidPashley su solución funcionó de maravilla para mí. Gracias
Jayesh Gopalan
1
If you are using the new dual HTTP and HTTPS server definitionentonces deberías separarlo.
VBart
2
elegante y funciona perfecto!
Jacktrade
2
Esta fue la única solución que funcionó para mí con mi configuración de Laravel / Homestead Nginx.
Jared Eitnier
1
Además, la línea de reescritura debería ser, return 301 https://$server_name$request_uri;ya que este es el método preferido.
Jared Eitnier
27

Otra variante más, que conserva el encabezado Host: request y sigue el ejemplo "BUENO" en las trampas de nginx :

server {
    listen   10.0.0.134:80 default_server;

    server_name  site1;
    server_name  site2;
    server_name  10.0.0.134;

    return 301 https://$host$request_uri;
}

Aquí están los resultados. Tenga en cuenta que usar en $server_namelugar de $hostsiempre redirigiría a https://site1.

# curl -Is http://site1/ | grep Location
Location: https://site1/

# curl -Is http://site2/ | grep Location
Location: https://site2/


# curl -Is http://site1/foo/bar | grep Location
Location: https://site1/foo/bar

# curl -Is http://site1/foo/bar?baz=qux | grep Location
Location: https://site1/foo/bar?baz=qux
Peter
fuente
Note that using $server_name instead of $host would always redirect to https://site1¿no es eso para lo que $request_urisirve?
Jürgen Paul
2
$request_urino contiene un host o nombre de dominio. En otras palabras, siempre comienza con un carácter "/".
Peter
2
La mejor respuesta con diferencia.
Ashesh
3
No estoy seguro de por qué esta respuesta es tan baja en votos. Es el único que vale la pena usar.
zopieux
2
No puedo creer que tanta gente use $ server_name, esta es la forma correcta de hacerlo
Greg Ennis
3

Asegúrese de configurar "seguro" en las cookies, de lo contrario, se enviarán en la solicitud HTTP y podrían ser capturadas por una herramienta como Firesheep.

W. Andrew Loe III
fuente
1
server {
    listen x.x.x.x:80;

    server_name domain.tld;
    server_name www.domian.tld;
    server_name ipv4.domain.tld;

    rewrite     ^   https://$server_name$request_uri? permanent;
}

Esto funciona mejor, creo. xxxx se refiere a la IP de su servidor. Si está trabajando con Plesk 12, puede hacerlo cambiando el archivo "nginx.conf" en el directorio "/var/www/vhosts/system/domain.tld/conf" para el dominio que desee. No olvide reiniciar el servicio nginx después de guardar la configuración.

Caner SAYGIN
fuente
rewrite ^ https://$host$request_uri? permanent; sería una mejor solución ya que podría tener varios nombres de servidor en un
0

Creo que esta es la solución más simple. Fuerza el tráfico no HTTPS y no WWW a HTTPS y www solamente.

server {
    listen 80;
    listen 443 ssl;

    server_name domain.tld www.domain.tld;

    # global HTTP handler
    if ($scheme = http) {
        return 301 https://www.domain.tld$request_uri;
    }

    # global non-WWW HTTPS handler
    if ($http_host = domain.tld) {
        return 303 https://www.domain.tld$request_uri;
    }
}

EDITAR - Abr 2018: la solución sin IF se puede encontrar en mi publicación aquí: https://stackoverflow.com/a/36777526/6076984

Stamster
fuente
1
¿No se consideran las condiciones IF malvadas e ineficientes en el mundo nginx?
PKHunter
Sí lo son, en general. Pero para estos controles simples, supongo que no. Sin embargo, tengo un archivo de configuración adecuado que implica más escritura de código, pero evita los IF por completo.
stamster
Google recomienda usar 301 en lugar de 303. Fuente: support.google.com/webmasters/answer/6073543?hl=es
dylanh724
@DylanHunt: dejé 303 solo para probar, tome nota de que el primer controlador se configuró en 301, solo que el 2 olvidé cambiar :) Además, la solución sin IF: stackoverflow.com/a/36777526/6076984
stamster