Come forzare o reindirizzare a SSL in nginx?


222

Ho una pagina di iscrizione su un sottodominio come: https://signup.example.com

Dovrebbe essere accessibile solo tramite HTTPS, ma sono preoccupato che le persone possano in qualche modo inciampare su HTTP e ottenere un 404.

Il mio blocco html / server in nginx è simile al seguente:

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

Cosa posso aggiungere in modo che le persone che vanno a http://signup.example.comessere reindirizzate a https://signup.example.com? (FYI So che ci sono plugin di Rails che possono forzare SSLma speravo di evitarlo)


Risposte:


145

Secondo le insidie ​​di nginx , è leggermente meglio omettere l'acquisizione non necessaria, usando $request_uriinvece. In tal caso, aggiungere un punto interrogativo per impedire a nginx di raddoppiare qualsiasi argomento di query.

server {
    listen      80;
    server_name signup.mysite.com;
    rewrite     ^   https://$server_name$request_uri? permanent;
}

68
Oppure, secondo il sito che hai collegato, "MEGLIO" :return 301 http://domain.com$request_uri;
nh2

13
un commento. $ nome_server $ prende la prima variabile nome_server. Quindi sii consapevole di questo se hai nomi non FQN nella tua configurazione
engineerDave

2
@ nh2 Questo è un altro caso in cui la documentazione è errata poiché l'utilizzo return 301...causa un errore "troppi reindirizzamenti" mentre il metodo di riscrittura funziona davvero.
Mike Bethany,

1
Questo è ora documentato come "anche MALE". @MikeBethany return 301funziona, a meno che (immagino) non lo stia attivando anche per gli URL corretti, ascoltando su entrambe le porte (esempio di configurazione che attiva il problema: prendi la prima risposta di serverfault.com/a/474345/29689 e ometti il ​​se ).
Blaisorblade,

1
Mi chiedo cosa sia cambiato negli anni e se questa altra risposta sia migliore: serverfault.com/a/337893/119666
Ryan

256

Il modo migliore, come descritto nella procedura ufficiale, è utilizzare la returndirettiva:

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

5
risposta più breve e ha funzionato perfettamente nel mio caso
mateusz.fiolka

1
Questo è generalmente raccomandato perché restituisce un 301 Moved Permanently(i tuoi link sono stati spostati in modo permanente) e riscritto
sgb

1
Questo non funziona perché provoca un errore "troppi reindirizzamenti" anche se è stato impostatoproxy_set_header X-Forwarded-Proto https;
Mike Bethany,

1
@MikeBethany stai definendo listen 443;nello stesso blocco?
Joe B,

2
Questa dovrebbe essere la risposta accettata.
sjas,

119

Questo è il modo corretto ed efficiente se vuoi conservare tutto in un blocco server:

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

Tutto il resto sopra, usando "rewrite" o "if ssl_protocol" etc è più lento e peggiore.

Qui è lo stesso, ma ancora più efficiente, eseguendo solo la riscrittura sul protocollo http evita di dover controllare la variabile $ schema su ogni richiesta. Ma sul serio, è una cosa così piccola che non è necessario separarli.

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
Fantastico, alcuni codardi hanno votato questa risposta senza dire perché, anche se questa risposta è corretta. Forse un altro di quei cultisti "if is evil". Se ti preoccupi di leggere la documentazione di Nginx su If, saprai che IfIsNOTEvil, solo CERTI ne utilizza in un contesto di posizione {}, nessuno dei quali facciamo qui. La mia risposta è assolutamente il modo corretto di fare le cose!
ELIMINATO il

2
Non ho espresso il mio voto negativo, ma vorrei sottolineare che il default è stato cambiato in "default_server" nelle versioni più recenti.
spuder

La prima soluzione non può essere la più efficiente, se la seconda è ancora più efficiente. E hai anche descritto il motivo per cui non dovresti usare un if qui: "evita di dover controllare la variabile $ schema su ogni richiesta". Il punto di non usare ifs non riguarda solo le prestazioni, ma anche l'essere dichiarativo e non imperativo.
pepkin88,

+1 per if ($ schema = http)
Fernando Kosh,

Dovrebbe usare $ host qui, come menzionato nelle altre risposte.
Artem Russakovskii,

56

Se si utilizza la nuova definizione del doppio server HTTP e HTTPS, è possibile utilizzare quanto segue:

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

Questo sembra funzionare per me e non causa loop di reindirizzamento.

Modificare:

sostituito:

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

con la riga di riscrittura di Pratik.


2
@DavidPashley la tua soluzione ha funzionato come un incanto per me. Grazie
Jayesh Gopalan

1
If you are using the new dual HTTP and HTTPS server definitionallora dovresti separarlo.
V parte del

2
elegante e funziona perfettamente!
jacktrade,

2
Questa è stata l'unica soluzione che ha funzionato per me con la mia configurazione Nginx Laravel / Homestead.
Jared Eitnier,

1
Anche la riga di riscrittura dovrebbe essere return 301 https://$server_name$request_uri;poiché questo è il metodo preferito.
Jared Eitnier,

27

Ancora un'altra variante, che conserva l'intestazione Host: request e segue l'esempio "BUONO" nelle insidie ​​di 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;
}

Ecco i risultati Si noti che l'utilizzo $server_nameanziché anziché $hostreindirizzerebbe sempre 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

Note that using $server_name instead of $host would always redirect to https://site1non è quello che $request_uriserve?
Jürgen Paul,

2
$request_urinon contiene un host o un nome di dominio. In altre parole, inizia sempre con un carattere "/".
Peter,

2
La migliore risposta di gran lunga.
Ashesh,

3
Non sono sicuro del motivo per cui questa risposta è così bassa nei voti. È l'unico che vale la pena usare.
zopieux,

2
Non posso credere che così tante persone usino $ nome_server, questo è il modo corretto di farlo
Greg Ennis,

3

Assicurati di impostare "sicuro" su tutti i cookie, altrimenti verranno inviati sulla richiesta HTTP e potrebbero essere catturati da uno strumento come 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;
}

Funziona meglio, penso. xxxx si riferisce all'IP del tuo server. Se lavori con Plesk 12, puoi farlo modificando il file "nginx.conf" nella directory "/var/www/vhosts/system/domain.tld/conf" per qualunque dominio tu voglia. Non dimenticare di riavviare il servizio nginx dopo aver salvato la configurazione.


rewrite ^ https://$host$request_uri? permanent; sarebbe una soluzione migliore in quanto potresti avere diversi nomi di server su un vhost

0

Penso che questa sia la soluzione più semplice. Forza solo il traffico non HTTPS e non WWW verso 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 - Apr 2018: la soluzione senza IF è disponibile nel mio post qui: https://stackoverflow.com/a/36777526/6076984


1
Le condizioni IF non sono considerate malvagie e inefficienti nel mondo nginx?
PKHunter,

Sì, lo sono, in generale. Ma per questi semplici controlli immagino di no. Ho un file di configurazione corretto che comporta però più scrittura di codice, ma evita completamente gli IF.
Criceto

Google consiglia di utilizzare 301 anziché 303. Fonte: support.google.com/webmasters/answer/6073543?hl=it
dylanh724

@DylanHunt - ho lasciato 303 solo per il test, prendere una nota che prima handler è stato fissato a 301, solo 2 ° ho dimenticato di cambiare :) Inoltre, la soluzione w / o IF di: stackoverflow.com/a/36777526/6076984
stamster
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.