Jak wymusić lub przekierować na SSL w Nginx?

222

Mam stronę rejestracji w subdomenie, takiej jak: https://signup.example.com

Powinien być dostępny tylko przez HTTPS, ale obawiam się, że ludzie mogą natknąć się na to przez HTTP i dostać 404.

Mój blok html / server w nginx wygląda następująco:

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

Co mogę dodać, aby osoby, które http://signup.example.comzostaną przekierowane https://signup.example.com? (Wiem, że istnieją wtyczki Rails, które mogą wymusić, SSLale miałem nadzieję tego uniknąć)

Callmeed
źródło

Odpowiedzi:

145

Według pułapek nginx , nieco lepiej jest pominąć niepotrzebne przechwytywanie, używając $request_urizamiast tego. W takim przypadku dodaj znak zapytania, aby nginx nie podwoił argumentów zapytania.

server {
    listen      80;
    server_name signup.mysite.com;
    rewrite     ^   https://$server_name$request_uri? permanent;
}
Pratik Amin
źródło
68
Lub, zgodnie z linkiem do witryny, „LEPIEJ” :return 301 http://domain.com$request_uri;
nh2
13
jeden komentarz. $ nazwa_serwera $ pobiera pierwszą zmienną nazwa_serwera. Pamiętaj więc o tym, jeśli w konfiguracji nie ma nazw FQN
inżynier
2
@ nh2 Jest to kolejny przypadek błędnej dokumentacji, ponieważ użycie return 301...powoduje błąd „zbyt wielu przekierowań”, podczas gdy metoda przepisywania faktycznie działa.
Mike Bethany
1
Jest to teraz udokumentowane jako „również ZŁE”. @ MikeBethany return 301działa, chyba że (chyba) uruchamiasz go również dla poprawnych adresów URL, nasłuchując na obu portach (przykład konfiguracji wyzwalającej problem: weź pierwszą odpowiedź serverfault.com/a/474345/29689 i pomiń if ).
Blaisorblade,
1
Zastanawiam się, co zmieniło się przez lata i czy ta inna odpowiedź jest lepsza: serverfault.com/a/337893/119666
Ryan
256

Najlepszym sposobem opisanym w oficjalnym poradniku jest zastosowanie returndyrektywy:

server {
    listen      80;
    server_name signup.mysite.com;
    return 301 https://$server_name$request_uri;
}
VBart
źródło
5
najkrótsza odpowiedź i działała idealnie w moim przypadku
mateusz.fiolka
1
Jest to ogólnie zalecane, ponieważ zwraca a 301 Moved Permanently(twoje linki zostały trwale przeniesione), a także ponownie pisze
sgb
1
To nie działa, ponieważ powoduje błąd „zbyt wielu przekierowań”, nawet jeśli ustawiłeśproxy_set_header X-Forwarded-Proto https;
Mike Bethany
1
@MikeBethany definiujesz listen 443;w tym samym bloku?
Joe B,
2
To powinna być zaakceptowana odpowiedź.
sjas
119

Jest to poprawny i najbardziej wydajny sposób, jeśli chcesz zachować wszystko w jednym bloku serwera:

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

Wszystko inne powyżej, używając „przepisz” lub „jeśli ssl_protocol” itp. Jest wolniejsze i gorsze.

Jest to to samo, ale jeszcze bardziej wydajne, ponieważ uruchamiając przepisywanie tylko w protokole http, unikasz konieczności sprawdzania zmiennej $ schemat przy każdym żądaniu. Ale tak na poważnie, to tak drobna rzecz, że nie trzeba ich rozdzielać.

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
źródło
8
Świetnie, niektórzy tchórzowie odrzucili tę odpowiedź, nie mówiąc dlaczego, mimo że ta odpowiedź jest poprawna. Może inny z tych kultystów „jeśli jest zły”. Jeśli zechcesz przeczytać dokumentację Nginx na temat If, będziesz wiedział, że IfIsNOTEvil, tylko NIEKTÓRE używa go w kontekście lokalizacji {}, z których nic nie robimy tutaj. Moja odpowiedź jest absolutnie poprawna.
DELETEDACC
2
Nie oddałem głosu, ale chciałbym zauważyć, że w najnowszych wersjach zmieniono domyślny na „default_server”.
spuder
Pierwsze rozwiązanie nie może być najbardziej wydajne, jeśli drugie jest jeszcze bardziej wydajne. I nawet opisałeś, dlaczego nie powinieneś używać if, jeśli: „unika się konieczności sprawdzania zmiennej $ schemat przy każdym żądaniu”. Istotą nieużywania ifs jest nie tylko wydajność, ale także deklaratywność, a nie konieczność.
pepkin88
+1 za if ($ schemat = http)
Fernando Kosh
Powinien użyć $ host tutaj, jak wspomniano w innych odpowiedziach.
Artem Russakovskii
56

Jeśli używasz nowej definicji podwójnego serwera HTTP i HTTPS, możesz użyć następujących opcji:

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

To wydaje się działać dla mnie i nie powoduje pętli przekierowań.

Edytować:

Zamieniono:

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

z linią przepisywania Pratik.

David Pashley
źródło
2
@DavidPashley twoje rozwiązanie działało dla mnie jak urok. Dzięki
Jayesh Gopalan
1
If you are using the new dual HTTP and HTTPS server definitionto powinieneś to rozdzielić.
VBart
2
elegancki i działa idealnie!
jacktrade
2
To było jedyne rozwiązanie, które działało dla mnie z moją konfiguracją Laravel / Homestead Nginx.
Jared Eitnier,
1
Powinna być również linia przepisywania, return 301 https://$server_name$request_uri;ponieważ jest to preferowana metoda.
Jared Eitnier,
27

Jeszcze inny wariant, który zachowuje nagłówek żądania Host: i podąża za przykładem „DOBRY” w pułapkach 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;
}

Oto wyniki. Pamiętaj, że użycie $server_namezamiast $hostzawsze przekierowuje do 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
Piotr
źródło
Note that using $server_name instead of $host would always redirect to https://site1czy nie po to $request_urijest?
Jürgen Paul
2
$request_urinie zawiera nazwy hosta ani domeny. Innymi słowy, zawsze zaczyna się od znaku „/”.
Peter
2
Jak dotąd najlepsza odpowiedź.
Ashesh
3
Nie jestem pewien, dlaczego ta odpowiedź jest tak niska w głosowaniu. Jest to jedyny warty użycia.
zopieux,
2
Nie mogę uwierzyć, że tak wielu ludzi używa $ nazwa_serwera, to jest właściwy sposób, aby to zrobić
Greg Ennis,
3

Upewnij się, że ustawiłeś opcję „bezpieczne” dla plików cookie, w przeciwnym razie zostaną one wysłane na żądanie HTTP i mogą zostać przechwycone przez narzędzie takie jak Firesheep.

W. Andrew Loe III
źródło
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;
}

Myślę, że to działa lepiej. xxxx oznacza adres IP twojego serwera. Jeśli pracujesz z Plesk 12, możesz to zrobić, zmieniając plik „nginx.conf” w katalogu „/var/www/vhosts/system/domain.tld/conf” dla dowolnej domeny. Nie zapomnij zrestartować usługi nginx po zapisaniu konfiguracji.

Caner SAYGIN
źródło
rewrite ^ https://$host$request_uri? permanent; byłoby lepszym rozwiązaniem, ponieważ możesz mieć kilka nazw serwerów na
0

Myślę, że to najprostsze rozwiązanie. Wymusza ruch zarówno inny niż HTTPS, jak i inny niż WWW tylko dla HTTPS i 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;
    }
}

EDYCJA - kwi 2018: Rozwiązanie bez IF można znaleźć w moim poście tutaj: https://stackoverflow.com/a/36777526/6076984

stamster
źródło
1
Czy warunki JEŻELI nie są uważane za złe i nieefektywne w świecie Nginx?
PKHunter
Tak, ogólnie są. Ale dla tych prostych kontroli nie sądzę. Mam odpowiedni plik konfiguracyjny, który wymaga więcej pisania kodu, ale całkowicie unika IF.
stamster
Google zaleca użycie 301 zamiast 303. Źródło: support.google.com/webmasters/answer/6073543?hl=pl
dylanh724
@DylanHunt - Zostawiłem 303 tylko do testowania, zwróć uwagę, że pierwszy moduł obsługi został ustawiony na 301, tylko drugi zapomniałem zmienić :) Również rozwiązanie bez IF: stackoverflow.com/a/36777526/6076984
stamster