Eliminar "www" y redirigir a "https" con nginx

57

Quiero crear una regla en nginx que haga dos cosas:

  1. Elimina el "www". de la solicitud URI
  2. Redirige a "https" si el URI de solicitud es "http"

Hay muchos ejemplos de cómo hacer cada una de esas cosas individualmente, pero no puedo encontrar una solución que haga ambas cosas correctamente (es decir, no crea un bucle de redireccionamiento y maneja todos los casos correctamente).

Necesita manejar todos estos casos:

1. http://www.example.com/path
2. https://www.example.com/path
3. http://example.com/path
4. https://example.com/path

Todo esto debería terminar en https://example.com/path (# 4) sin bucle. ¿Algunas ideas?

Devin
fuente
Acabo de redirigir www.midominio.com a midominio.com a nivel DNS y agregué un 301 para no https a https en nginx. Parece que eso debería estar bien ¯ \ _ (ツ) _ / ¯
jonathanbell

Respuestas:

94

La mejor manera de lograr esto es utilizando tres bloques de servidor: uno para redirigir http a https, uno para redirigir el nombre https www a no-www y uno para manejar las solicitudes. La razón para usar bloques de servidor adicionales en lugar de ifs es que la selección del servidor se realiza mediante una tabla hash y es muy rápida. El uso de un nivel de servidor if significa que se ejecuta if para cada solicitud, lo cual es un desperdicio. Además, capturar el uri solicitado en la reescritura es un desperdicio, ya que nginx ya tiene esta información en las variables $ uri y $ request_uri (sin y con cadena de consulta, respectivamente).

server {
    server_name www.example.com example.com;
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl;
    ssl_certificate /path/to/server.cert;
    ssl_certificate_key /path/to/server.key;
    server_name www.example.com;
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl;
    ssl_certificate /path/to/server.cert;
    ssl_certificate_key /path/to/server.key;
    server_name example.com;

    <locations for processing requests>
}
kolbyjack
fuente
2
¿Es necesario el bloque del medio? ¿El primer bloque ya no está reescribiendo de www a no www?
pbreitenbach
3
El primer bloque solo maneja http. El bloque del medio es necesario para redirigir las solicitudes https desde https: // www.example.com/ a https: // example.com/. (Perdón por los espacios adicionales, no puedo hacer que muestre el https de lo contrario)
kolbyjack
1
solo una nota de formato menor: si desea evitar hacer un enlace, puede poner el texto del comentario dentro de las comillas ", el que está debajo de tilde. Aparecería como:https://example.com/
Cyclops
99
el segundo bloque también necesita información cert.
ricka
3
Al intentar esta respuesta, me encontré con otro problema. Pensé que podría 301 redirección desde www.sub.example.comque sub.example.comy sólo obtener un certificado SSL para sub.example.comAhora sé que cheque certificado SSL ocurre antes de la redirección 301, por lo que no puede funcionar. Más explicaciones aquí: serverfault.com/a/358625/144811
Gruzzles el
11

Esto funciona para mi:

server {
    listen              80;
    server_name         www.yourdomain.com yourdomain.com;
    return              301 https://yourdomain.com$request_uri;
}

server {
    listen              443 ssl;
    server_name         www.yourdomain.com;
    ssl_certificate     /path/to/certificate.crt;
    ssl_certificate_key /path/to/private/key.pem;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    return              301 https://yourdomain.com$request_uri;
}

server {
    listen              443 ssl;
    server_name         yourdomain.com;
    ssl_certificate     /path/to/certificate.crt;
    ssl_certificate_key /path/to/private/key.pem;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;

    # do the proper handling of the request
}

Tenga en cuenta que ambos yourdomain.com y www.yourdomain.com deben estar en su certificado SSL. Esto es posible con un certificado comodín o con un Nombre alternativo del servidor como se explica aquí . Consulte https://www.startssl.com para obtener certificados agradables y gratuitos que lo hagan. ( Edith : a partir de Chrome versión 56, ya no se confiará en los certificados startssl. Pruebe https://letsencrypt.org/ en su lugar).

e18r
fuente
Este realmente funciona, pero pensé que podría hacerse de una manera más clara sin muchas líneas de configuración duplicadas.
zloynemec
@zloynemec Puede colocar el contenido SSL en un archivo .conf separado y usar la includeregla para agregarlo a ambos bloques del servidor SSL.
Igettäjä
Además, si está utilizando cloudflare, debe pagar el certificado de $ 10 / mes para poder redirigir y proxy los 2 subdominios (www + algo). Avíseme si hay una solución alternativa.
Freedo
7

Después de pasar tanto tiempo con cientos de casos similares, se me ocurrió el siguiente fragmento. Es corto y se puede ajustar fácilmente para adaptarse a cualquier cosa.

server {
    listen 80;
    listen 443 ssl;
    server_name example.com www.example.com;
    ssl_certificate /path/to/my/certs/example.com/fullchain.pem;
    ssl_certificate_key /path/to/my/certs/example.com/privkey.pem;

    # Redirect to the correct place, if needed
    set $https_redirect 0;
    if ($server_port = 80) { set $https_redirect 1; }
    if ($host ~ '^www\.') { set $https_redirect 1; }
    if ($https_redirect = 1) {
        return 301 https://example.com$request_uri;
    }

    location / {
    # ...
}

Oh pero ifes malvado !

Si puede ser. Pero existe por una razón, y no debería hacer daño a aquellos que saben cómo usarlo adecuadamente. ;)

emyller
fuente
Me gusta esto, pero ¿tiene alguna información sobre el impacto en el rendimiento? ¡Gracias!
Freedo
1
Honestamente, nunca lo comparé, pero creo que apenas habría un impacto en comparación con reglas separadas, ya que el efecto es más o menos el mismo.
emyller
punto de referencia en la redirección? no es realmente pertinente no? (verdadera pregunta, no un troll ^^)
Matrix
3

Prefiero volver con un código de respuesta para que el navegador sepa que lo está redirigiendo a otra URL.

server {
    listen   80;
    server_name  www.example.com;

    return 301 https://example.com$request_uri;
}

entonces otro bloque de configuraciones de servidor para el https

server {
        listen   443 ssl;
        server_name  example.com;
        ...
    }
Montss
fuente
0

¿Qué tal crear un bloque de servidor para este propósito?

server{
    listen 80;
    server_name www.example.net example.net;
    rewrite ^(.*) https://example.net$1 permanent;
}

luego reiniciando nginx

anthonysomerset
fuente
Recibo un error de "nombre de servidor en conflicto" al reiniciar. Además, esta orden no escucha en el puerto 443 para SSL y yo necesidad de preocuparse acerca de la redirección https://www.example.coma https://example.comtambién.
Devin
0

Creo que esto debería funcionar.

En su definición de servidor HTTP simple, se sugiere algo como anthonysomerset, es decir:

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

Luego, en la definición de su servidor SSL:

if ($host ~ /^www\./) {
  rewrite ^(.*) https://example.net$1 permanent;
}

De esta forma, la redirección solo debe ocurrir una vez por solicitud, sin importar a qué URL vaya originalmente el usuario.

Eduardo Ivanec
fuente
Eso funcionó, gracias. Sin if ($host = 'www.example.com') {embargo, tuve que cambiar tu condicional ya que tu expresión regular no funcionaba para mí. No tengo idea de por qué, ya que parece correcto.
Devin
Tenga en cuenta que si es malo y generalmente es mejor usar una forma declarativa.
Blaise
0

Aquí está el ejemplo completo que terminó funcionando para mí. El problema era que no tenía los detalles de SSL ( ssl_certificate, etc.) en el bloque de redireccionamiento de www. ¡Recuerde revisar sus registros ( sudo tail -f /var/log/nginx/error.log)!

# HTTP — redirect all traffic to HTTPS
server {
    listen 80;
    listen [::]:80 default_server ipv6only=on;
    return 301 https://$host$request_uri;
}

# HTTPS — redirects www to non-www
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.example.com;

    # Use the Let's Encrypt certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Include the SSL configuration from cipherli.st
    include snippets/ssl-params.conf;
    return 301 https://example.com$request_uri;
}

# HTTPS — proxy all requests to the app (port 3001)
server {
    # Enable HTTP/2
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com sub.example.com;

    # Use the Let's Encrypt certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Include the SSL configuration from cipherli.st
    include snippets/ssl-params.conf;

    # For LetsEncrypt:
    location ~ /.well-known {
        root /var/www/html;
        allow all;
    }

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-NginX-Proxy true;
        proxy_pass http://localhost:3001;
        proxy_ssl_session_reuse off;
        proxy_set_header Host $http_host;
        proxy_cache_bypass $http_upgrade;
        proxy_redirect off;
    }
}

fuente