W Nginx, w jaki sposób mogę przepisać wszystkie żądania HTTP na https, zachowując subdomenę?

508

Chcę przepisać wszystkie żądania HTTP na moim serwerze internetowym na żądania https, zacząłem od następujących czynności:

serwer {
    słuchaj 80;

    Lokalizacja / {
      przepisać ^ (. *) https: //mysite.com$1 permanent;
    }
...


Jednym z problemów jest to, że usuwa wszystkie informacje z subdomeny (np. Node1.mysite.com/folder). Jak mogę przepisać powyższe, aby przekierować wszystko do https i utrzymać subdomenę?

MikeN
źródło
2
Rozważ przeniesienie „zaakceptowanej odpowiedzi” na serverfault.com/a/171238/90758 . To jest poprawne.
olafure
Wystarczy użyć $ server_name zamiast na stałe mysite.com
Fedir RYKHTIK

Odpowiedzi:

748

Prawidłowy sposób w nowych wersjach nginx

Okazało się, że moja pierwsza odpowiedź na to pytanie była w pewnym momencie poprawna, ale zmieniła się w kolejną pułapkę - aby być na bieżąco, sprawdź opodatkowanie pułapki przepisania

Zostałem poprawiony przez wielu użytkowników SE, więc zasługa należy do nich, ale co ważniejsze, oto poprawny kod:

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

server {
       listen         443 ssl;
       server_name    my.domain.com;
       # add Strict-Transport-Security to prevent man in the middle attacks
       add_header Strict-Transport-Security "max-age=31536000" always; 

       [....]
}
Saif Bechan
źródło
3
Musiałbyś to jednak zrobić dla poszczególnych domen - nie? Co jeśli chcesz zastosować go do każdej domeny na swoim serwerze?
JM4
30
@ JM4: jeśli użyjesz $ host $ w przepisywaniu zamiast server_name i dodasz default_server do dyrektywy listen, będzie działał dla każdej domeny na twoim serwerze.
Klaas van Schelven
5
Należy wspomnieć, że 301 jest przechowywany w lokalnej pamięci podręcznej bez daty ważności. Niezbyt przydatne, gdy zmienia się konfiguracja
Trefex
9
@ każdy Użyj przekierowania 307, aby zachować zawartość POST.
Mahmoud Al-Qudsi
10
Pamiętaj, że musisz użyć $hostzamiast, $server_namejeśli używasz subdomen.
Sum
276

UWAGA: Najlepszym sposobem na to jest https://serverfault.com/a/401632/3641 - ale powtórzono tutaj:

server {
    listen         80;
    return 301 https://$host$request_uri;
}

W najprostszym przypadku twój host zostanie ustawiony jako usługa, do której chcesz je wysłać - spowoduje to przekierowanie 301 do przeglądarki i adres URL przeglądarki zostanie odpowiednio zaktualizowany.

Poniżej znajduje się poprzednia odpowiedź, która jest nieefektywna z powodu wyrażenia regularnego, prosty 301 jest świetny, jak pokazuje @kmindi

Używam nginx 0.8.39 i nowszych, i użyłem następujących:

 server {
       listen 80;
       rewrite ^(.*) https://$host$1 permanent;
 }

Wysyła stałe przekierowanie do klienta.

Michael Neale
źródło
15
Myślę, że powinno to być 80 - ponieważ nasłuchuje http, a następnie każe klientowi wrócić jako https (443).
Michael Neale,
3
To powinna być najlepsza odpowiedź!
Nathan
3
To najbardziej podatna odpowiedź.
Sprawa
1
jest to najłatwiejszy, ale najmniej bezpieczny - w ten sposób pozwalasz serwerowi przekierować użytkownika na dowolną stronę, bez sprawdzania, czy można go nawet używać na serwerze. Jeśli Twój serwer obsługuje mydomain.co, złośliwi użytkownicy mogą nadal używać go do przekierowywania użytkowników do innych domen, takich jak mydomain.co, takich jak google.com.
friedkiwi
10
@ cab0lt nie ma tutaj problemu bezpieczeństwa. Udostępnienie przekierowania nie stanowi zagrożenia bezpieczeństwa. Jeśli istnieją wymagania dotyczące kontroli dostępu, należy je sprawdzić w punkcie, w którym przeglądarka żąda nowego adresu URL. Przeglądarka nie uzyska dostępu po prostu na podstawie przekierowania, ani nie potrzebuje przekierowania, aby zażądać nowego adresu URL.
mc0e
125

Myślę, że najlepszym i jedynym sposobem powinno być użycie HTTP 301 Przeniesione Trwale przekierowanie w następujący sposób:

server {
    listen         [::]:80;
    return 301 https://$host$request_uri;
}

HTTP 301 Przeniesiony stałe przekierowania jest najbardziej skuteczne, ponieważ nie ma regex być ocenione, zgodnie z wymienionymi pitfails .


Nowy przeniesiony HTTP 308 na stałe zachowuje metodę żądania i jest obsługiwany przez główne przeglądarki . Na przykład użycie 308uniemożliwia przeglądarkom zmianę metody żądania z POSTna GETdla żądania przekierowania.


Jeśli chcesz zachować nazwę hosta i subdomenę, to jest właśnie ten sposób.

To nadal działa, jeśli nie masz DNS , ponieważ używam go również lokalnie. Proszę na przykład o http://192.168.0.100/index.phpi zostaniesz przekierowany do dokładnie https://192.168.0.100/index.php.

Używam listen [::]:80na moim hoście, ponieważ bindv6onlyustawiłem false, więc wiąże się również z gniazdem ipv4. zmień na, listen 80jeśli nie chcesz IPv6 lub chcesz powiązać gdzie indziej.

Rozwiązanie firmy Saif Bechan korzysta z tego, server_namektóry w moim przypadku jest hostem lokalnym, ale nie jest osiągalny przez sieć.

Rozwiązanie Michaela Neale'a jest dobre, ale według pułapek istnieje lepsze rozwiązanie z przekierowaniem 301;)

kmindi
źródło
Dobrze, że próbujesz to zacytować, ale 301 nie działa na HTTPS.
Sprawa
5
co nie działa podana sekcja serwera dotyczy niezaszyfrowanego ruchu http (bez s), który zostanie trwale przekierowany na zaszyfrowany serwer (ta sekcja, która nasłuchuje na 443 (https) nie jest wymieniona)
kmindi
Sprawdziłem, że działa świetnie z https i wszystkim - @kmindi zaktualizowałem swoją odpowiedź w odniesieniu do twojej - ponieważ uważam, że jest to właściwa droga i to wciąż się pojawia! Dobra robota.
Michael Neale
Podczas korzystania z żądania domeny (nie ip) nie działa, chyba że zmienię „[::]: 80” na „80”.
Joseph Lust,
takie może być oczekiwane zachowanie: trac.nginx.org/nginx/ticket/345 . Zaktualizowałem odpowiedź, aby opisać opcję słuchania.
kmindi,
20

W bloku serwera możesz również wykonać następujące czynności:

# Force HTTPS connection. This rules is domain agnostic
if ($scheme != "https") {
    rewrite ^ https://$host$uri permanent;
}
Oriol
źródło
2
Ta konfiguracja spowodowała, że ​​mój serwer utworzył pętlę przekierowania
Corkscreewe
Może dlatego, że istnieje inne przekierowanie lub https nie jest włączony w Twojej witrynie / aplikacji
Oriol
1
Wydawało się, że żaden z pozostałych nie działa, oprócz tego. Korzystanie z wersji nginx: nginx / 1.10.0 (Ubuntu)
ThatGuy343
głosowano za https: // $ host $ uri
AMB
4
To jest właściwa droga, jeśli jesteś za loadbalancerem!
Antwan
17

Powyższe nie działało w przypadku tworzenia nowych subdomen przez cały czas. np. AAA.example.com BBB.example.com dla około 30 subdomen.

W końcu otrzymałem konfigurację działającą z następującymi elementami:

server {
  listen 80;
  server_name _;
  rewrite ^ https://$host$request_uri? permanent;
}
server {
  listen  443;
  server_name example.com;
  ssl on;
  ssl_certificate /etc/ssl/certs/myssl.crt;
  ssl_certificate_key /etc/ssl/private/myssl.key;
  ssl_prefer_server_ciphers       on;
# ...
# rest of config here
# ...
}
Aleck Landgraf
źródło
Dziękuję Ci! nginx zwróci 301 https://*/lub przedwcześnie anuluje żądanie w innych odpowiedziach tutaj. server_name _;z $hostodpowiedzią, która załatwiła sprawę. +1
zamnuts
1
Ten jest optymalny! Jednak polecam niektórym zastąpienie _rzeczywistej domeny, np. .domain.comMiałem dwa serwery, a nginx przypadkowo przekierował jeden z moich serwerów na serwer domyślny.
zzz
1
To jedyna odpowiedź, która zadziałała dla mnie, dzięki!
Bałwan
Wielkie dzięki kolego .. Wypróbowałem wiele rozwiązań, ale się nie udało. Te rozwiązania są niesamowite i zadziałały dla mnie. Nazwa serwera _; do czego to służy ... Nie rozumiałem. Proszę wytłumacz mi to.
Pavan Kumar
6

Dawno, dawno temu zamieściłem komentarz do poprawnej odpowiedzi z bardzo ważną korektą, ale uważam, że konieczne jest podkreślenie tej korekty we własnej odpowiedzi. Żadna z poprzednich odpowiedzi nie jest bezpieczna w użyciu, jeśli w dowolnym momencie skonfigurowałeś niezabezpieczony HTTP i oczekujesz treści użytkownika, masz formularze, hostujesz interfejs API lub skonfigurowałeś dowolną stronę internetową, narzędzie, aplikację lub narzędzie do komunikowania się z twoją witryną.

Problem występuje, gdy POSTżądanie jest wysyłane do serwera. Jeśli odpowiedź serwera z prostym 30xprzekierowaniem zawartość POST zostanie utracona. Dzieje się tak, że przeglądarka / klient uaktualni żądanie do SSL, ale obniży je POSTdo GETżądania. Te POSTparametry zostaną utracone i nieprawidłowe żądanie zostanie wykonana na serwerze.

Rozwiązanie jest proste. Musisz użyć HTTP 1.1 307przekierowania. Jest to szczegółowo opisane w RFC 7231 S6.4.7:

  Note: This status code is similar to 302 (Found), except that it
  does not allow changing the request method from POST to GET.  This
  specification defines no equivalent counterpart for 301 (Moved
  Permanently) ([RFC7238], however, defines the status code 308
  (Permanent Redirect) for this purpose).

Rozwiązaniem, zaadaptowanym z przyjętego rozwiązania, jest użycie 307w kodzie przekierowania:

server {
       listen         80;
       server_name    my.domain.com;
       return         307 https://$server_name$request_uri;
}

server {
       listen         443 ssl;
       server_name    my.domain.com;
       # add Strict-Transport-Security to prevent man in the middle attacks
       add_header Strict-Transport-Security "max-age=31536000"; 

       [....]
}
Mahmoud Al-Qudsi
źródło
4

Udało mi się to zrobić w następujący sposób:

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

https://stackoverflow.com/a/36777526/6076984

stamster
źródło
Zaktualizowana wersja bez IF: paste.debian.net/plain/899679
stamster
4

Używam ngnix za AWS ELB. ELB rozmawia z ngnix przez http. Ponieważ ELB nie ma możliwości wysyłania przekierowań do klientów, sprawdzam nagłówek X-Forwarded-Proto i przekierowuję:

if ($http_x_forwarded_proto != 'https') {
    return 301 "https://www.exampl.com";
}
MANCHUCK
źródło
1

Jeśli return 301 https://$host$request_uri;domyślną odpowiedzią jest port 80, serwer może prędzej czy później uzyskać listę otwartych serwerów proxy [1] i zacząć być wykorzystywany do wysyłania ruchu w inne miejsce w Internecie. Jeśli twoje dzienniki wypełnią się takimi komunikatami, to wiesz, że to się stało:

42.232.104.114 - - [25/Mar/2018:04:50:49 +0000] "GET http://www.ioffer.com/i/new-fashion-fine-gold-bracelet-versaec-bracelet-641175733 HTTP/1.1" 301 185 "http://www.ioffer.com/" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; Hotbar 4.1.8.0; RogueCleaner; Alexa Toolbar)"

Problem polega na tym $host, że wywoła echo wszystkiego, co przeglądarka wyśle ​​w Hostnagłówku, a nawet nazwę hosta z wiersza otwierającego HTTP, na przykład:

GET http://www.ioffer.com/i/new-fashion-fine-gold-bracelet-versaec-bracelet-641175733 HTTP/1.1

Z powodu tego problemu niektóre inne odpowiedzi tutaj zalecają użycie $server_namezamiast $host. $server_namezawsze ocenia się co ty umieścić w server_namedeklaracji. Ale jeśli masz wiele subdomen lub używasz symbolu wieloznacznego, to nie zadziała, ponieważ $server_nameużywa tylko pierwszego wpisu po server_namedeklaracji, a co ważniejsze, po prostu wyśle ​​echo znaku zastępczego (nie rozwija go).

Jak więc obsługiwać wiele domen przy jednoczesnym zachowaniu bezpieczeństwa? W moich własnych systemach poradziłem sobie z tym dylematem, najpierw wymieniając default_serverblok, który nie używa $host, a następnie wymieniając blok symboli wieloznacznych, który:

server {
  listen 80 default_server;
  server_name example.com;
  return 301 https://example.com$request_uri;
}
server {
  listen 80;
  server_name *.example.com;
  return 301 https://$host$request_uri;
}

(Możesz również podać więcej niż jedną domenę w drugim bloku).

Dzięki tej kombinacji niedopasowane domeny zostaną przekierowane gdzieś na stałe (zawsze example.com), a domeny, które pasują do Twojej, trafią we właściwe miejsce. Twój serwer nie będzie przydatny jako otwarty serwer proxy, więc nie będziesz przyciągał problemów.

Jeśli czujesz się jak w domu, przypuszczam, że możesz również default_serverdopasować blok do żadnej z twoich legalnych domen i podać coś obraźliwego. . . .

[1] Technicznie „proxy” jest niewłaściwym słowem, ponieważ twój serwer nie wychodzi i nie spełnia wymagań klientów, po prostu wysyła przekierowanie, ale nie jestem pewien, jakie byłoby właściwe słowo. Nie jestem również pewien, jaki jest cel, ale wypełnia on dzienniki hałasem oraz zużywa procesor i przepustowość, więc równie dobrze możecie położyć temu kres.

Paul A Jungwirth
źródło
0

Wygląda na to, że nikt tak naprawdę nie miał 100% racji. Aby mieć port 80 wnioski pójść do ich 443 ekwiwalentów na cały serwer, trzeba użyć wysłuchać dyrektywą, nie dyrektywy server_name określić catch-all nazwy. Zobacz także https://nginx.org/en/docs/http/request_processing.html

serwer {
    słuchaj 80 domyślnie;
    Listen [::]: 80 domyślnie;
      zwraca 307 https: // $ host $ request_uri;
}
  • $ host przechwytuje nazwy subdomen.
  • 307 i 308 obejmują zarówno identyfikatory URI żądań POST, jak i GET.
  • 307 jest tymczasowy, zmień na stały 308 po dokładnych testach:

I upewnij się, że sprawdziłeś, co jest już w /etc/nginx/conf.d/, ponieważ najczęściej miałem problemy z tym, że default.conf zwrócił jakiś istniejący vhost. Moja kolejność pracy z problemami nginx zawsze zaczyna się od przeniesienia domyślnego pliku, umieszczając go z powrotem, komentując wiersz po wierszu, aby zobaczyć, gdzie idzie źle.

Juliusz
źródło
-1
rewrite ^!https https://$host$request_uri permanent;
nntb2a
źródło