Como forçar ou redirecionar para SSL no nginx?

222

Eu tenho uma página de inscrição em um subdomínio como: https://signup.example.com

Ele só deve ser acessível via HTTPS, mas estou preocupado que as pessoas possam, de alguma forma, encontrá-lo via HTTP e obter um 404.

Meu bloco html / server no nginx é assim:

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;
    }
  }
}

O que posso adicionar para que as pessoas que serão http://signup.example.comredirecionadas https://signup.example.com? (FYI, eu sei que existem plugins Rails que podem forçar, SSLmas esperava evitar isso)

Callmeed
fonte

Respostas:

145

De acordo com as armadilhas do nginx , é um pouco melhor omitir a captura desnecessária, usando em seu $request_urilugar. Nesse caso, acrescente um ponto de interrogação para impedir que o nginx dobre quaisquer argumentos de consulta.

server {
    listen      80;
    server_name signup.mysite.com;
    rewrite     ^   https://$server_name$request_uri? permanent;
}
Pratik Amin
fonte
68
Ou, de acordo com o site que você vinculou, "MELHOR" :return 301 http://domain.com$request_uri;
nh2 14/08/12
13
um comentário. o $ server_name $ seleciona a primeira variável server_name. Então, estar ciente disto se você tiver nomes Fqn não em sua configuração
engineerDave
2
@ nh2 Este é outro caso de a documentação estar errada, pois o uso return 301...causa um erro "muitos redirecionamentos" enquanto o método de reescrita realmente funciona.
Mike Bethany
1
Agora está documentado como "também MAU". O @MikeBethany return 301funciona, a menos que (eu acho) você esteja ativando também para URLs corretos, ouvindo nas duas portas (exemplo de configuração. Acionando o problema: pegue a primeira resposta do serverfault.com/a/474345/29689 e omita a if )
Blaisorblade
1
Pergunto-me o que mudou ao longo dos anos e se esta outra resposta é melhor: serverfault.com/a/337893/119666
Ryan
256

A melhor maneira descrita no manual oficial é usando a returndiretiva:

server {
    listen      80;
    server_name signup.mysite.com;
    return 301 https://$server_name$request_uri;
}
VBart
fonte
5
resposta mais curta e funcionou perfeitamente no meu caso
mateusz.fiolka
1
Isso geralmente é recomendado porque retorna a 301 Moved Permanently(seus links foram movidos permanentemente) e também reescreveu
sgb
1
Isso não funciona, uma vez que provoca um erro "demasiados redirecionamentos", mesmo se tiver definidoproxy_set_header X-Forwarded-Proto https;
Mike Bethany
1
@ MikeBethany você está definindo listen 443;no mesmo bloco?
Joe B
2
Essa deve ser a resposta aceita.
sjas
119

Esta é a maneira correta e mais eficiente se você quiser manter tudo em um bloco 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;
    }
}

Tudo o mais acima, usar "reescrever" ou "se ssl_protocol" etc é mais lento e pior.

Aqui está o mesmo, mas ainda mais eficiente, ao executar apenas a reescrita no protocolo http, ele evita a necessidade de verificar a variável do esquema $ em cada solicitação. Mas, falando sério, é uma coisa tão pequena que você não precisa separá-los.

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
fonte
8
Ótimo, algum covarde votou nessa resposta sem dizer o porquê, mesmo que ela esteja correta. Talvez outro daqueles cultistas do "se é mau". Se você se incomodar em ler a documentação do Nginx sobre o If, você saberá que o IfIsNOTEvil apenas CERTAIN o utiliza em um contexto de local {}, nenhum dos quais fazemos aqui. Minha resposta é absolutamente a maneira correta de fazer as coisas!
DELETEDACC
2
Não votei negativamente, mas gostaria de salientar que o padrão foi alterado para 'default_server' nas versões mais recentes.
Spuder # 22/13
A primeira solução não pode ser a mais eficiente, se a segunda for ainda mais eficiente. E você mesmo descreveu, por que você não deve usar um if there: "evita ter que verificar a variável do esquema $ em cada solicitação". O objetivo de não usar ifs não é apenas sobre desempenho, mas também sobre ser declarativo e não imperativo.
pepkin88
+1 para if ($ esquema = http)
Fernando Kosh 27/11
Deve usar $ host aqui, como mencionado nas outras respostas.
Artem Russakovskii
56

Se você estiver usando a nova definição de servidor HTTP e HTTPS duplo, poderá usar o seguinte:

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;
    }
}

Isso parece funcionar para mim e não causa loops de redirecionamento.

Editar:

Substituído:

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

com a linha de reescrita de Pratik.

David Pashley
fonte
2
@DavidPashley, sua solução funcionou como um encanto para mim. Obrigado
Jayesh Gopalan
1
If you are using the new dual HTTP and HTTPS server definitionentão você deve separá-lo.
Vbart
2
elegante e funciona perfeito!
jacktrade
2
Essa foi a única solução que funcionou para mim com minha configuração do Laravel / Homestead Nginx.
Jared Eitnier
1
Além disso, a linha de reescrita deve ser return 301 https://$server_name$request_uri;como este é o método preferido.
Jared Eitnier
27

Outra variante, que preserva o cabeçalho Host: request e segue o exemplo "GOOD" nas armadilhas do 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;
}

Aqui estão os resultados. Observe que o uso em $server_namevez de $hostsempre redirecionaria para 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
fonte
Note that using $server_name instead of $host would always redirect to https://site1não $request_urié para isso que serve?
Jürgen Paul
2
$request_urinão contém um host ou nome de domínio. Em outras palavras, sempre começa com um caractere "/".
Peter
2
Melhor resposta de longe.
Ashesh
3
Não sei por que essa resposta é tão baixa em votos. É o único que vale a pena usar.
zopieux 8/12/15
2
Não posso acreditar que tantas pessoas usam $ server_name esta é a maneira correta de fazê-lo
Greg Ennis
3

Certifique-se de definir 'seguro' em qualquer cookie, caso contrário, eles serão enviados na solicitação HTTP e poderão ser capturados por uma ferramenta como o Firesheep.

W. Andrew Loe III
fonte
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;
}

Isso funciona melhor, eu acho. xxxx refere-se ao IP do seu servidor. Se você estiver trabalhando com o Plesk 12, poderá fazer isso alterando o arquivo "nginx.conf" no diretório "/var/www/vhosts/system/domain.tld/conf" para o domínio que desejar. Não esqueça de reiniciar o serviço nginx depois de salvar a configuração.

Caner SAYGIN
fonte
rewrite ^ https://$host$request_uri? permanent; seria uma solução melhor que você pode ter vários nomes de servidor em um vhost
0

Eu acho que essa é a solução mais simples. Força o tráfego não HTTPS e não WWW apenas para HTTPS e www.

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;
    }
}

EDIT - Abr 2018: Solução sem IF pode ser encontrada no meu post aqui: https://stackoverflow.com/a/36777526/6076984

stamster
fonte
1
As condições SE não são consideradas más e ineficientes no mundo nginx?
PKHunter
Sim, eles são, em geral. Mas para essas verificações simples, acho que não. Eu tenho um arquivo de configuração adequado que envolve mais escrita de código, mas evita completamente o IF.
Stamster
O Google recomenda usar o 301 em vez do 303. Fonte: support.google.com/webmasters/answer/6073543?hl=pt-BR
dylanh724 17/04/19
@DylanHunt - deixei o 303 apenas para teste, observe que o primeiro manipulador foi definido como 301, apenas o segundo esqueci de mudar :) Além disso, a solução sem IF: stackoverflow.com/a/36777526/6076984
stamster