Remova “www” e redirecione para “https” com nginx

57

Eu quero criar uma regra no nginx que faça duas coisas:

  1. Remove o "www". do URI da solicitação
  2. Redireciona para "https" se o URI da solicitação for "http"

Existem muitos exemplos de como fazer cada uma dessas coisas individualmente, mas não consigo descobrir uma solução que faça as duas coisas corretamente (ou seja, não crie um loop de redirecionamento e lide com todos os casos corretamente).

Ele precisa lidar com todos esses casos:

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

Todos eles devem terminar em https://example.com/path (nº 4) sem loop. Alguma ideia?

Devin
fonte
Acabei de redirecionar www.mydomain.com para mydomain.com no nível DNS e adicionei um 301 para não-https a https no nginx. Parece que deve ser fino ¯ \ _ (ツ) _ / ¯
jonathanbell

Respostas:

94

A melhor maneira de fazer isso é usar três blocos de servidor: um para redirecionar http para https, um para redirecionar o https www-name para no-www e outro para realmente manipular solicitações. O motivo para usar blocos extras de servidor em vez de ifs é que a seleção do servidor é realizada usando uma tabela de hash e é muito rápida. Usar um if no nível do servidor significa que o if é executado para cada solicitação, o que é um desperdício. Além disso, capturar a uri solicitada na reescrita é um desperdício, pois o nginx já possui essas informações nas variáveis ​​$ uri e $ request_uri (sem e com a string 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
fonte
2
O bloco do meio é necessário? O primeiro bloco já não está reescrevendo de www para não www?
Pbreitenbach
3
O primeiro bloco lida apenas com http. O bloco do meio é necessário para redirecionar solicitações https de https: // www.example.com/ para https: // example.com/. (Desculpe para os espaços extras, eu não posso fazê-lo mostrar o https outra forma)
kolbyjack
11
apenas uma pequena nota de formatação - se você quiser evitar criar um link, pode colocar o texto do comentário entre aspas `, a que está em til. Ele apareceria como:https://example.com/
Ciclope
9
o segundo bloco também precisa de informações de certificação.
Ricka
3
Tentando esta resposta, encontrei outro problema. Pensei que eu poderia 301 redirecionar de www.sub.example.compara sub.example.come, em seguida, obter apenas um certificado SSL para sub.example.comAgora eu sei que a verificação de certificado SSL acontece antes do redirecionamento 301, para que não funcione. Mais explicações aqui: serverfault.com/a/358625/144811
Gruzzles
11

Isso funciona para mim:

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
}

Tenha em mente que tanto yourdomain.com e www.yourdomain.com deve estar em seu certificado SSL. Isso é possível com um certificado curinga ou com um Nome alternativo do servidor, conforme explicado aqui . Verifique https://www.startssl.com para obter certificados legais e gratuitos que fazem isso. ( Edith : a partir da versão 56 do Chrome, os certificados openssl não serão mais confiáveis. Tente https://letsencrypt.org/ .)

e18r
fonte
Este realmente funciona, mas achei que poderia ser feito de maneira mais clara, sem muitas linhas de configuração duplicadas.
Zloynemec 18/09
@zloynemec Você pode colocar o material SSL em um arquivo .conf separado e usar a includeregra para adicioná-lo aos dois blocos do servidor SSL.
Igettäjä
Além disso, se você estiver usando o cloudflare, precisará pagar o certificado de US $ 10 / mês para poder redirecionar e fazer proxy dos 2 subdomínios (www + alguma coisa). Deixe-me saber se há uma solução alternativa.
Freedo 24/09
7

Depois de passar tanto tempo com centenas de casos semelhantes, criei o seguinte snippet. É curto e pode ser facilmente ajustado para caber em qualquer coisa.

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, mas ifé mau !

Sim pode ser. Mas existe por uma razão e não deve prejudicar quem sabe usá-lo adequadamente. ;)

emyller
fonte
Eu gosto disso, mas você tem algum dado sobre o desempenho atingido? Obrigado!
Freedo 24/09
11
Honestamente, nunca comparei isso, mas acredito que dificilmente haveria um impacto comparado a regras separadas, já que o efeito é praticamente o mesmo.
emyller
referência no redirecionamento? não é realmente pertinente não? (pergunta verdadeira, não um troll ^^)
Matrix
3

Prefiro retornar com um código de resposta para que o navegador saiba que você está redirecionando para outro URL.

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

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

outro bloco de configurações do servidor para o https

server {
        listen   443 ssl;
        server_name  example.com;
        ...
    }
montss
fonte
0

Que tal criar um bloco de servidor para esse fim:

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

reiniciando o nginx

anthonysomerset
fonte
Eu recebo um erro "nome do servidor conflitante" ao reiniciar. Além disso, esse comando não vai escutar na porta 443 para SSL e eu precisa se preocupar com o redirecionamento https://www.example.compara https://example.combem.
Devin
0

Eu acho que isso deve funcionar.

Na sua definição simples de servidor HTTP, sugerimos algo como anthonysomerset, ou seja:

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

Em seguida, na sua definição de servidor SSL:

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

Dessa forma, o redirecionamento deve ocorrer apenas uma vez por solicitação, independentemente do URL para o qual o usuário acessa originalmente.

Eduardo Ivanec
fonte
Isso funcionou, obrigado. Eu tive que mudar sua condição, if ($host = 'www.example.com') {já que seu regex não estava funcionando para mim. Não faço ideia do porquê, como parece correto.
Devin
Observe que se é mau e geralmente é melhor usar uma maneira declarativa.
Blaise
0

Aqui está o exemplo completo que acabou funcionando para mim. O problema era que eu não tinha os detalhes do ssl ( ssl_certificate, etc.) no bloco de redirecionamento www. Lembre-se de verificar seus logs ( 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;
    }
}

fonte