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ąć)


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

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

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

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.


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

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.


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.


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


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.