SSD Nodes Learn 🎉 VPS od $5.50/mies.
Przewodniki Matt ConnorAutor: Matt Connor

Konfiguracja nginx jako reverse proxy krok po kroku

Dowiedz się, jak poprawnie skonfigurować nginx jako reverse proxy. Artykuł wyjaśnia użycie proxy_pass, przekazywanie nagłówków, obsługę websockets oraz limitów dla przesyłanych plików.

Działanie reverse proxy w nginx

Reverse proxy w nginx przyjmuje żądania docierające na port 80 oraz 443 i przekazuje każde z nich do aplikacji nasłuchującej na lokalnym porcie, a następnie zwraca odpowiedź aplikacji do przeglądarki. Konfiguracja składa się z pojedynczego bloku server i jest krótka. Większość trudności sprowadza się do pięciu lub sześciu linii, które informują aplikację o tym, kim był rzeczywisty klient oraz jakiego protokołu użył.

Poniższa treść bazuje na czystej instalacji Ubuntu 24.04, z wykorzystaniem pakietu nginx z repozytoriów dystrybucji. Punktem wyjścia jest aplikacja, która odpowiada już na 127.0.0.1:3000. Jeśli wybór proxy nie został jeszcze dokonany, warto najpierw zapoznać się z porównaniem porównanie nginx z Caddy i Traefik. Poniżej przedstawiono strukturę konfiguracji nginx, linia po linii.

Wprowadź te konfiguracje na własnym serwerze. Każda zmiana musi zostać przetestowana za pomocą sudo nginx -t przed przeładowaniem; należy również przeanalizować komunikaty zwracane przez to polecenie.

Gdzie nginx przechowuje konfigurację w systemie Ubuntu

sudo apt update
sudo apt install -y nginx
ls -l /etc/nginx/sites-enabled/

Główny plik to /etc/nginx/nginx.conf. Określa on opcje globalne wewnątrz bloku http { }, a następnie wczytuje dwa katalogi: /etc/nginx/conf.d/*.conf oraz /etc/nginx/sites-enabled/*. W systemach Ubuntu i Debian dla każdej witryny tworzy się osobny plik w /etc/nginx/sites-available/ i aktywuje go za pomocą dowiązania symbolicznego w /etc/nginx/sites-enabled/. Usunięcie dowiązania wyłącza witrynę, zachowując jednocześnie plik konfiguracyjny.

Dwie dyrektywy używane w dalszej części działają wyłącznie w kontekście http, nigdy wewnątrz bloku server: map oraz upstream. Należy umieścić je w osobnym pliku w katalogu /etc/nginx/conf.d/, ponieważ zawartość tego katalogu jest dołączana na poziomie http.

Pakiet dostarcza domyślnie włączoną witrynę o nazwie default. Jest ona oznaczona jako default_server, co oznacza, że odpowiada na każde żądanie, którego nagłówek Host nie pasuje do żadnego server_name w konfiguracji. Dopóki pozostaje ona włączona, żądania niedopasowane do zdefiniowanych nazw trafiają na tę stronę zamiast do Twojej aplikacji. Po poprawnej konfiguracji własnej witryny należy usunąć to dowiązanie symboliczne.

sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Najmniejszy blok serwera przekazujący ruch do jednej aplikacji

server {
    listen 80;
    listen [::]:80;
    server_name app.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

Zapisz to jako /etc/nginx/sites-available/app.example.com, a następnie włącz i załaduj konfigurację.

sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
curl -sI -H 'Host: app.example.com' http://127.0.0.1/

listen 80; wiąże się z IPv4, a listen [::]:80; z IPv6. Pomiń drugą linię, a użytkownik, którego zapytanie DNS (domain name system) zwróci rekord AAAA dla Twojego serwera, otrzyma odmowę połączenia, podczas gdy wszyscy korzystający z IPv4 zobaczą działającą stronę. Zgłoszenie błędu, które otrzymasz, będzie brzmiało: "u mnie działa".

server_name jest dopasowywane do nagłówka Host wysyłanego przez przeglądarkę. Można wymienić kilka nazw, oddzielając je spacjami. Jeśli żaden blok nie pasuje, nginx użyje bloku oznaczonego jako default_server, dlatego domyślna strona musiała zostać usunięta.

location / to dopasowanie prefiksu ścieżki żądania, a / pasuje do każdej ścieżki. proxy_pass to adres, z którym nginx nawiązuje połączenie. Utrzymuj aplikację powiązaną z 127.0.0.1, aby jedyną drogą dostępu był nginx. Jeśli aplikacja działa w kontenerze, publikuj ją jako 127.0.0.1:3000:3000, a nie jako 3000:3000, ponieważ Docker tworzy własne reguły i publikuje porty z pominięciem ufw, więc bezpośrednio opublikowany port jest dostępny z Internetu niezależnie od ustawień zapory sieciowej.

Linia curl wysyła poprawny nagłówek Host bezpośrednio z serwera, dzięki czemu możesz przetestować blok, zanim rekordy DNS zostaną skierowane na serwer.

Co nginx wysyła do backendu przy braku dodatkowej konfiguracji

proxy_pass bez dodatkowych parametrów ukrywa przed aplikacją cztery elementy.

Domyślnie nginx komunikuje się z backendem za pomocą protokołu HTTP/1.0 i wysyła Connection: close, co powoduje, że każde żądanie otwiera nowe połączenie upstream i uniemożliwia aktualizację protokołu.

Nagłówek Host jest nadpisywany wartością proxy_pass, czyli 127.0.0.1:3000. Aplikacja generująca linki bezwzględne na podstawie Host tworzy odnośniki, których użytkownicy spoza serwera nie mogą otworzyć.

Połączenie docierające do aplikacji pochodzi z nginx, więc aplikacja widzi adres klienta jako 127.0.0.1. Każdy wpis w dzienniku i każdy limit szybkości wewnątrz aplikacji rejestruje proxy zamiast odwiedzającego.

Aplikacja nie jest w stanie rozpoznać, że przeglądarka użyła HTTPS, ponieważ otrzymane połączenie to zwykły HTTP na adresie zwrotnym.

Cztery linie konfiguracji rozwiązują wszystkie te problemy.

Cztery nagłówki do ustawienia i informacje, które dzięki nim otrzymuje backend

location / {
    proxy_pass http://127.0.0.1:3000;

    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Host przenosi nazwę wpisaną przez użytkownika. $host to nazwa z żądania, z usuniętym numerem portu i zamienionymi na małe literami. Ustawienie tego nagłówka pozwala aplikacji na poprawne budowanie bezwzględnych adresów URL: przekierowań po zalogowaniu lub linków w wiadomościach e-mail z resetem hasła. Brak tego nagłówka sprawia, że adresy te wskazują na 127.0.0.1:3000, co powoduje, że po zalogowaniu przeglądarka jest kierowana na adres odrzucający połączenie. Jeśli aplikacja wymaga również numeru portu, ponieważ działa na porcie 8080, należy użyć $http_host, który zawiera nagłówek dokładnie w takiej formie, w jakiej wysłał go klient.

X-Real-IP przenosi jedną wartość: $remote_addr, czyli adres, z którego nginx przyjął połączenie. Aplikacje odczytują go na potrzeby własnych logów dostępu oraz mechanizmów ograniczania częstotliwości zapytań (rate limiting).

X-Forwarded-For przenosi listę. $proxy_add_x_forwarded_for dołącza $remote_addr do wartości, którą klient już umieścił w tym nagłówku, dzięki czemu wartość jest rozdzielona przecinkami, a wpis dodany przez nginx znajduje się na końcu. Ten szczegół decyduje o tym, czy nagłówek można uznać za zaufany: klient może wysłać dowolny nagłówek X-Forwarded-For, więc aplikacja odczytująca pierwszy wpis może otrzymać dowolny adres. Gdy nginx pełni rolę serwera brzegowego, należy użyć $remote_addr i odrzucić wersję przesłaną przez klienta. Jeśli przed serwerem znajduje się CDN lub inne proxy, należy użyć set_real_ip_from oraz real_ip_header z modułu realip, aby $remote_addr stało się rzeczywistym adresem klienta.

X-Forwarded-Proto przenosi http lub https. Frameworki odczytują tę wartość, aby zdecydować, czy oznaczyć pliki cookie jako Secure oraz czy wymusić przekierowanie na HTTPS. Pominięcie tego nagłówka w witrynie z TLS sprawia, że aplikacja skonfigurowana do wymuszania HTTPS widzi http, odpowiada przekierowaniem na adres HTTPS, otrzymuje kolejne żądanie przez nginx, nadal widzi http i ponownie wykonuje przekierowanie. Przeglądarka przerywa działanie i wyświetla ERR_TOO_MANY_REDIRECTS.

Powtarzanie tych czterech linii w każdej lokalizacji prowadzi do rozbieżności w konfiguracji. Należy umieścić je w jednym pliku i dołączać za pomocą dyrektywy include.

# /etc/nginx/snippets/proxy-headers.conf
proxy_set_header Host              $host;
proxy_set_header X-Real-IP         $remote_addr;
proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
location / {
    include snippets/proxy-headers.conf;
    proxy_pass http://127.0.0.1:3000;
}

Dziedziczenie w tym przypadku posiada pułapkę. Lokalizacja dziedziczy dyrektywy proxy_set_header z bloku serwera tylko wtedy, gdy w samej lokalizacji nie zdefiniowano żadnej z nich. Dodanie choćby jednego nagłówka proxy_set_header wewnątrz lokalizacji powoduje, że wszystkie nagłówki zdefiniowane na poziomie serwera są dla niej pomijane. Dlatego należy utrzymywać je na jednym poziomie lub używać include dla fragmentu konfiguracji w każdej lokalizacji, która wykonuje proxy.

Dlaczego moja aplikacja WebSocket łączy się, a następnie rozłącza?

Domyślne ustawienia zabraniają aktualizacji protokołu, a domyślny limit czasu odczytu zamyka bezczynny tunel po 60 sekundach. Połączenie WebSocket rozpoczyna się jako żądanie HTTP zawierające Upgrade: websocket oraz Connection: Upgrade. Są to nagłówki typu hop-by-hop, co oznacza, że proxy powinno je przetworzyć, a nie przekazywać dalej, a protokół HTTP/1.0 nie posiada mechanizmu aktualizacji. Oba nagłówki muszą zostać przywrócone ręcznie.

Mapę należy umieścić w kontekście http, w osobnym pliku.

# /etc/nginx/conf.d/websocket.conf
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

Następnie należy skonfigurować lokalizację.

location / {
    include snippets/proxy-headers.conf;
    proxy_pass http://127.0.0.1:3000;

    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}

Mapa istnieje po to, aby jedna lokalizacja mogła obsługiwać oba rodzaje ruchu. W przypadku zwykłego żądania zmienna $http_upgrade jest pusta, więc $connection_upgrade przyjmuje wartość close. W przypadku żądania aktualizacji zawiera ona websocket, więc nagłówek wysyłany do backendu to Connection: upgrade. Sztywne wpisanie proxy_set_header Connection "upgrade"; powoduje wysyłanie tego nagłówka również przy zwykłych żądaniach stron, co sprawia, że niektóre backendy odpowiadają na takie żądanie błędem 400.

proxy_read_timeout jest przyczyną zgłoszeń typu „ładuje się, a potem przestaje aktualizować”. Domyślnie wynosi 60 sekund i mierzy odstęp między dwoma odczytami z backendu, a nie czas trwania połączenia. WebSocket, który pozostaje nieaktywny przez 60 sekund, jest zamykany przez nginx, a konsola przeglądarki pokazuje zamknięcie gniazda z kodem 1006. Aplikacje wysyłające własne sygnały kontrolne (heartbeat) częściej niż raz na minutę nie napotykają tego problemu. Aplikacje, które tego nie robią, są rozłączane po minucie. Problem ten pojawia się najpierw w edytorach działających w czasie rzeczywistym oraz panelach sterowania, a samodzielnie hostowana instancja n8n za HTTPS jest tego częstym przykładem.

Dlaczego ukośnik na końcu w proxy_pass zmienia adresy URL?

Zasada jest jednowierszowa. Jeśli proxy_pass kończy się identyfikatorem URI (nawet samym /), nginx usuwa część ścieżki żądania, która pasowała do prefiksu location, i wstawia w to miejsce wspomniany URI. Jeśli proxy_pass kończy się na hoście i porcie, ścieżka żądania jest przekazywana bez zmian.

location /app/ {
    proxy_pass http://127.0.0.1:3000/;
}

Żądanie dla /app/status dociera do backendu jako /status.

location /app/ {
    proxy_pass http://127.0.0.1:3000;
}

Żądanie dla /app/status dociera do backendu jako /app/status.

Wybór formy zależy od aplikacji. Aplikacja z ustawieniem ścieżki bazowej lub podkatalogu wymaga drugiej formy, przy czym w ustawieniach należy wskazać /app. Aplikacja, która nie posiada wiedzy o prefiksach, wymaga pierwszej formy. Pierwsza forma wiąże się z natychmiastowym skutkiem: kod HTML zwracany przez aplikację nadal zawiera ścieżki bezwzględne, takie jak /static/main.css, przeglądarka szuka ich w katalogu głównym witryny, nie znajduje dopasowania lokalizacji, a strona renderuje się bez stylów. Zakładka sieci w narzędziach deweloperskich przeglądarki wykazuje błędy 404 dla tych zasobów. Rozwiązaniem jest konfiguracja ścieżki bazowej w samej aplikacji lub dodanie drugiej dyrektywy location /static/ wskazującej na ten sam backend.

Lokalizacja oparta na wyrażeniu regularnym nie może zawierać URI w proxy_pass. sudo nginx -t odrzuca taką konfigurację i podaje przyczynę: "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block.

Cała ta klasa problemów znika, gdy każda aplikacja otrzymuje własną nazwę, app.example.com, obsługiwaną przez proxy z location /. Ścieżki podrzędne są warte zachodu tylko wtedy, gdy nie ma możliwości dodania rekordów DNS.

Jak umieścić więcej niż jeden backend pod jedną nazwą?

Należy użyć bloku upstream. Należy on do kontekstu http, więc należy go umieścić powyżej bloku server w tym samym pliku lub w /etc/nginx/conf.d/.

upstream app_backend {
    least_conn;
    server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
    keepalive 32;
}

Następnie lokalizacja wskazuje go za pomocą: proxy_pass http://app_backend;.

Domyślną metodą jest round robin. least_conn wysyła każde żądanie do backendu z najmniejszą liczbą aktywnych połączeń, co sprawdza się przy żądaniach o różnym czasie trwania. ip_hash przypisuje jeden adres klienta do jednego backendu. ip_hash jest wymagane, gdy aplikacja przechowuje sesje w pamięci lokalnej, ponieważ round robin między dwoma takimi backendami powoduje losowe wylogowywanie użytkowników, gdy żądania trafiają na instancję, która nie posiada ich sesji. Lepszym rozwiązaniem jest przeniesienie sesji do współdzielonej pamięci.

max_fails=3 fail_timeout=30s oznacza, że trzy nieudane próby w ciągu 30 sekund wyłączają dany serwer na 30 sekund. Gdy wszystkie serwery w bloku znajdują się w tym stanie, klienci otrzymują błąd 502, a dziennik błędów zawiera no live upstreams while connecting to upstream.

keepalive 32 utrzymuje do 32 bezczynnych połączeń z backendami na proces roboczy, co eliminuje konieczność wykonywania handshake'u TCP dla większości żądań. Działa to tylko z proxy_http_version 1.1 i przy braku Connection: close w komunikacji upstream. Jeśli ta sama lokalizacja używa również mapy WebSocket, należy zmienić pusty przypadek z close na pusty ciąg znaków, aby zwykłe żądania nie zawierały nagłówka Connection, co pozwala na ponowne wykorzystanie puli połączeń.

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      '';
}

Nazwy wewnątrz bloku upstream są rozwiązywane w momencie uruchomienia nginx. Jeśli backendem jest kontener, który otrzymuje nowy adres po restarcie, nginx będzie używał starego adresu do momentu przeładowania konfiguracji. Wewnątrz sieci Docker można przenieść wyszukiwanie na czas żądania, korzystając z wbudowanego resolvera.

resolver 127.0.0.11 valid=10s;
set $backend http://app:3000;
proxy_pass $backend;

Gdy kontenery pojawiają się i znikają na tyle często, że ręczna edycja konfiguracji nginx staje się uciążliwa, lepszym narzędziem jest proxy odczytujące etykiety kontenerów. Traefik przed kilkoma aplikacjami Docker Compose automatycznie tworzy trasy na podstawie samych kontenerów.

Dlaczego przesyłanie plików kończy się błędem 413 Request Entity Too Large?

client_max_body_size domyślnie wynosi 1 megabajt. Większe żądania są odrzucane przez nginx, zanim aplikacja otrzyma jakiekolwiek dane, a błąd jest rejestrowany w client intended to send too large body. Należy zwiększyć tę wartość w bloku server lub w konkretnej lokalizacji, w której odbywa się przesyłanie plików.

client_max_body_size 512m;

Wartość 0 całkowicie wyłącza sprawdzanie rozmiaru. Aplikacja posiada również własne limity, więc jeśli błąd 413 nadal występuje po wprowadzeniu tej zmiany, oznacza to, że ograniczenie pochodzi z backendu i należy sprawdzić ustawienia przesyłania w samej aplikacji.

Domyślnie nginx odczytuje całe ciało żądania przed otwarciem połączenia z upstreamem, zapisując większe dane w pliku tymczasowym na dysku. Chroni to aplikację przed wolnymi klientami, ponieważ backend otrzymuje dane z pełną prędkością lokalną. W przypadku bardzo dużych plików można zastosować strumieniowanie.

proxy_request_buffering off;

Backend otrzymuje wtedy ciało żądania w miarę jego napływania i musi być w stanie je obsłużyć. nginx traci również możliwość ponowienia żądania do innego upstreamu, ponieważ ciało żądania zostało już przekazane.

client_body_timeout, domyślnie ustawione na 60 sekund, odnosi się do czasu między dwoma kolejnymi odczytami danych, a nie do całego procesu przesyłania. Wolne, ale stabilne przesyłanie nie zostanie przerwane. Zatrzymane połączenie zostanie zerwane.

Buforowanie odpowiedzi i ustawienie powodujące przerwanie strumieniowania danych wyjściowych

proxy_buffering jest domyślnie włączone i zazwyczaj jest to pożądane zachowanie. Nginx odczytuje odpowiedź z aplikacji tak szybko, jak aplikacja jest w stanie ją wygenerować, przechowuje ją, a następnie przekazuje do wolnego klienta w tempie dostosowanym do jego możliwości. Proces roboczy aplikacji kończy pracę szybciej, zamiast pozostawać zajętym przez cały czas trwania powolnego pobierania.

Powoduje to jednak przerwanie odpowiedzi strumieniowych. Zdarzenia przesyłane przez serwer (Server-sent events) oraz strumieniowe dane wyjściowe dzienników nie są widoczne dla odbiorcy, dopóki bufor nie zostanie zapełniony. W takiej lokalizacji należy wyłączyć buforowanie.

proxy_buffering off;

Jeśli masz kontrolę nad aplikacją, lepszym rozwiązaniem jest wysłanie nagłówka X-Accel-Buffering: no wyłącznie w odpowiedziach strumieniowych. Nginx odczytuje ten nagłówek dla każdej odpowiedzi i wyłącza buforowanie tylko dla niej, dzięki czemu zwykłe strony zachowują korzyści z tego mechanizmu.

Gdy dziennik błędów zawiera komunikat upstream sent too big header while reading response header from upstream, oznacza to, że nagłówki odpowiedzi nie zmieściły się w jednym buforze. proxy_buffer_size domyślnie przyjmuje wartość pojedynczej strony pamięci, czyli 4 lub 8 kilobajtów w zależności od platformy, a długie pliki cookie lub rozbudowane nagłówki uwierzytelniania powodują przepełnienie tego obszaru. Należy zwiększyć obie te wartości.

proxy_buffer_size 16k;
proxy_buffers 8 16k;

Gdzie w tej konfiguracji umieścić TLS?

W Nginx, przed wszystkimi powyższymi elementami. TLS (transport layer security) kończy się na proxy, a połączenie między Nginx a aplikacją pozostaje zwykłym HTTP przez adres loopback, gdzie nikt inny w sieci nie może go odczytać. Aplikacja dowiaduje się, że użytkownik użył HTTPS, z X-Forwarded-Proto, czwartego z czterech nagłówków.

Nie należy ręcznie wpisywać ścieżek do certyfikatów. Należy skierować rekord DNS na serwer, otworzyć zaporę sieciową i pozwolić Certbot na edycję tego samego bloku serwera: dodaje on linię listen 443 ssl ze ścieżkami ssl_certificate oraz przekierowanie z portu 80. Wystawianie certyfikatu Let's Encrypt dla Nginx za pomocą Certbot opisuje proces wystawiania oraz harmonogram odnawiania.

sudo ufw allow 'Nginx Full'
sudo ufw status

Nginx Full to profil aplikacji instalowany przez pakiet Nginx, który otwiera port 80 oraz port 443 jednocześnie. Port 80 musi pozostać otwarty dla wyzwania HTTP-01 podczas odnawiania, nawet po przekierowaniu wszystkich użytkowników na HTTPS.

Testowanie konfiguracji i przeładowanie

sudo nginx -t
sudo systemctl reload nginx

nginx -t analizuje wszystkie dołączone pliki i zgłasza powodzenie testu lub wskazuje plik oraz linię, w której wystąpił błąd. Należy zapoznać się z tym komunikatem przed przeładowaniem. Przeładowanie z błędną konfiguracją nie przynosi zmian: Nginx kontynuuje pracę z poprzednimi ustawieniami, dzięki czemu serwis pozostaje dostępny, mimo że wprowadzona zmiana nie została zastosowana. systemctl restart działa inaczej i jest bardziej ryzykowne, ponieważ restart najpierw zatrzymuje działający serwer, więc błąd w konfiguracji spowoduje całkowite wyłączenie Nginx. Zaleca się domyślne stosowanie przeładowania, a restart pozostawienie dla rzadkich przypadków, w których jest on wymagany.

sudo tail -f /var/log/nginx/error.log
sudo ss -lntp | grep -E ':(80|443|3000)'

Polecenie ss wyświetla procesy przypisane do poszczególnych portów, co pozwala potwierdzić, czy aplikacja faktycznie nasłuchuje tam, gdzie wskazuje proxy_pass.

Awarie, z którymi faktycznie się zetkniesz

502 Bad Gateway, z connect() failed (111: Connection refused) while connecting to upstream w dzienniku błędów. Pod adresem w proxy_pass nikt nie nasłuchuje. Aplikacja jest zatrzymana, przypisana do innego portu lub powiązana z adresem wewnętrznym kontenera, do którego host nie ma dostępu.

502 z no live upstreams while connecting to upstream. Każdy serwer w bloku upstream jest obecnie oznaczony przez max_fails jako niedziałający. Napraw backendy. Nginx ponowi próbę połączenia, gdy wygaśnie fail_timeout.

504 Gateway Time-out, z upstream timed out (110: Connection timed out) while reading response header from upstream. Backend zaakceptował połączenie, a następnie nie wysłał żadnych danych przez proxy_read_timeout sekund. Zwiększenie limitu czasu jest właściwe dla faktycznie wolno generowanego raportu, ale błędne w przypadku zawieszonej aplikacji.

Każda ścieżka zwraca 404 z poziomu aplikacji. Reguła dotycząca końcowego ukośnika nadpisała ścieżkę. Porównaj ścieżkę zapisaną w logach aplikacji ze ścieżką, o którą wysłano żądanie.

Odpowiada inna witryna. server_name nie pasuje do nagłówka Host, więc żądanie zostało przekierowane do bloku default_server.

Strona ładuje się, a następnie interfejs zawiesza się po około minucie. To przypadek związany z WebSocket: brakuje obsługi Upgrade lub proxy_read_timeout nadal wynosi 60 sekund.

FAQ

Dlaczego nginx zwraca błąd 502 Bad Gateway po dodaniu proxy_pass?

Nginx nie może nawiązać połączenia z adresem wskazanym w proxy_pass. Dziennik błędów w /var/log/nginx/error.log wskazuje przyczynę: connect() failed (111: Connection refused) while connecting to upstream oznacza, że pod tym adresem nikt nie nasłuchuje, a no live upstreams oznacza, że każdy serwer w bloku upstream został oznaczony jako niedostępny. Uruchom sudo ss -lntp | grep 3000, aby sprawdzić, który proces zajmuje port i do jakiego adresu jest przypisany. Aplikacja powiązana z adresem wewnętrznym kontenera lub z innym portem niż podany w konfiguracji zawsze wywoła ten błąd.

Dlaczego moja aplikacja rozłącza się po około minucie pracy za nginx?

Połączenie jest typu WebSocket, a proxy_read_timeout nadal ma domyślną wartość 60 sekund, która mierzy czas między dwoma odczytami z backendu. Nieaktywne gniazdo jest zamykane przez nginx, a konsola przeglądarki zgłasza kod zamknięcia 1006. Ustaw proxy_http_version 1.1, przekaż nagłówki Upgrade oraz Connection za pomocą map w $http_upgrade i zwiększ proxy_read_timeout do wartości takiej jak 3600s. Bez nagłówka Upgrade proces aktualizacji połączenia nigdy nie nastąpi, więc aplikacja przełączy się na odpytywanie (polling) lub przestanie wyświetlać aktualizacje na żywo.

Czy końcowy ukośnik w proxy_pass ma znaczenie?

Tak, zmienia on ścieżkę, którą otrzymuje backend. W przypadku location /app/ oraz proxy_pass http://127.0.0.1:3000/, żądanie dla /app/status dociera do backendu jako /status, ponieważ każdy URI po hoście i porcie zastępuje dopasowany prefiks lokalizacji. Usuń ten końcowy ukośnik, a to samo żądanie dotrze jako /app/status. Usunięcie prefiksu często powoduje błędy w linkach do zasobów aplikacji, które pozostają bezwzględne i zwracają błąd 404 w głównym katalogu strony, dlatego aplikację z ustawioną ścieżką bazową lepiej obsługiwać w formie, która przekazuje ścieżkę w całości.

Dlaczego moja aplikacja loguje 127.0.0.1 jako adres IP każdego odwiedzającego?

Ponieważ połączenie, które otrzymuje aplikacja, faktycznie pochodzi od nginx na adresie pętli zwrotnej (loopback). Adres odwiedzającego dociera do aplikacji tylko w nagłówku, który skonfigurujesz: proxy_set_header X-Real-IP $remote_addr; dla pojedynczej wartości oraz proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; dla dołączanego łańcucha. Aplikacja musi być skonfigurowana tak, aby ufać tym nagłówkom. Pamiętaj, że klient może wysłać własny nagłówek X-Forwarded-For, więc gdy nginx jest serwerem brzegowym, nadpisz go za pomocą $remote_addr zamiast dopisywać wartość.

Czy potrzebuję TLS na połączeniu między nginx a moją aplikacją?

Nie, jeśli aplikacja działa na tym samym serwerze i jest powiązana z 127.0.0.1, ponieważ ten ruch nigdy nie opuszcza maszyny. Zakończ TLS na nginx, utrzymuj proxy_pass na zwykłym HTTP przez loopback i wysyłaj X-Forwarded-Proto $scheme, aby aplikacja wiedziała, że odwiedzający użył HTTPS. Jeśli backend znajduje się na innym hoście w sieci, której nie kontrolujesz, ten odcinek wymaga własnej ochrony, albo poprzez HTTPS do backendu, albo poprzez prywatny tunel między dwiema maszynami.