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)


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

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

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

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.


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

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.


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.


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


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.