Czym jest reverse proxy i dlaczego warto?
Reverse proxy to serwer, który stoi między klientem (przeglądarką) a serwerem aplikacji. Zamiast łączyć się bezpośrednio z Twoją aplikacją, użytkownik łączy się z Nginx, który przekazuje żądanie do właściwego serwisu i zwraca odpowiedź.
Brzmi jak niepotrzebna warstwa pośrednia? W praktyce reverse proxy rozwiązuje wiele problemów naraz:
- SSL termination — Nginx obsługuje szyfrowanie HTTPS, a aplikacja nie musi się tym zajmować
- Serwowanie plików statycznych — Nginx jest znacznie szybszy w serwowaniu obrazów, CSS i JS niż serwer aplikacji
- Load balancing — rozdzielanie ruchu między wiele instancji aplikacji
- Cache — przechowywanie odpowiedzi w pamięci, odciążając serwer aplikacji
- Rate limiting — ochrona przed nadmiernym ruchem i prostymi atakami DDoS
- Jeden punkt wejścia — wiele aplikacji na różnych portach dostępnych przez jedną domenę
Ten ostatni punkt jest szczególnie istotny, gdy na jednym serwerze hostujesz kilka serwisów webowych. Każdy działa na swoim porcie wewnętrznym, a Nginx kieruje ruch na podstawie nazwy domeny.
Podstawowa konfiguracja
Załóżmy, że masz aplikację webową działającą na porcie 8080 i chcesz, żeby była dostępna pod domeną example.com. Oto minimalna konfiguracja Nginx jako reverse proxy:
server {
listen 80;
server_name example.com www.example.com;
location / {
proxy_pass http://localhost:8080;
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;
}
}
Omówmy kluczowe elementy:
proxy_pass — najważniejsza dyrektywa. Wskazuje, dokąd Nginx ma przekazywać żądania. Może to być localhost:port, adres IP w sieci wewnętrznej lub nazwa kontenera Docker.
proxy_set_header Host — przekazuje oryginalną nazwę domeny do aplikacji. Bez tego aplikacja zobaczyłaby localhost:8080 zamiast example.com.
proxy_set_header X-Real-IP — przekazuje prawdziwy adres IP klienta. Bez tego aplikacja widziałaby IP serwera Nginx (zwykle 127.0.0.1).
proxy_set_header X-Forwarded-For — łańcuch adresów IP, przez które przeszło żądanie. Ważne, gdy masz wiele warstw proxy.
proxy_set_header X-Forwarded-Proto — informacja, czy oryginalne żądanie było HTTP czy HTTPS.
Wiele aplikacji na jednym serwerze
Prawdziwa moc reverse proxy ujawnia się, gdy hostujesz wiele serwisów. Każda domena trafia do innego kontenera lub procesu:
# Strona firmowa
server {
listen 80;
server_name www.firma.pl;
location / {
proxy_pass http://strona-firmowa:8080;
include /etc/nginx/proxy-params.inc;
}
}
# Aplikacja do faktur
server {
listen 80;
server_name faktury.firma.pl;
location / {
proxy_pass http://app-faktury:3000;
include /etc/nginx/proxy-params.inc;
}
}
# Blog
server {
listen 80;
server_name blog.firma.pl;
location / {
proxy_pass http://blog:8082;
include /etc/nginx/proxy-params.inc;
}
}
Wszystkie trzy serwisy są dostępne na porcie 80 (standardowy HTTP), a Nginx kieruje ruch na podstawie nagłówka Host. Powtarzające się dyrektywy proxy_set_header wydzielamy do wspólnego pliku include, żeby uniknąć duplikacji.
Dodanie HTTPS
W praktyce każda produkcyjna konfiguracja powinna używać HTTPS. Z Certbotem i Let's Encrypt to kwestia jednego polecenia, ale warto rozumieć wynikową konfigurację:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/ssl/certs/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/certs/example.com/privkey.pem;
# Silna konfiguracja TLS
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=63072000" always;
location / {
proxy_pass http://localhost:8080;
include /etc/nginx/proxy-params.inc;
}
}
Pierwszy blok server przekierowuje wszystkie żądania HTTP na HTTPS (kod 301). Drugi blok obsługuje ruch HTTPS z certyfikatem SSL. Aplikacja za proxy dalej działa na zwykłym HTTP — szyfrowanie odbywa się tylko między klientem a Nginx (tzw. SSL termination).
Load balancing
Gdy jedna instancja aplikacji nie wystarczy, Nginx może rozdzielać ruch między wiele backendów:
upstream app_servers {
server app1:8080 weight=3;
server app2:8080 weight=2;
server app3:8080 weight=1;
}
server {
listen 443 ssl http2;
server_name example.com;
location / {
proxy_pass http://app_servers;
include /etc/nginx/proxy-params.inc;
}
}
Dyrektywa upstream definiuje grupę serwerów. Parametr weight kontroluje proporcje rozdzielania ruchu — w tym przykładzie app1 otrzyma 3 razy więcej żądań niż app3. Nginx wspiera też inne algorytmy: least_conn (kieruje do serwera z najmniejszą liczbą aktywnych połączeń) i ip_hash (ten sam klient zawsze trafia do tego samego serwera).
Serwowanie plików statycznych
Dobrą praktyką jest serwowanie plików statycznych bezpośrednio przez Nginx, bez angażowania serwera aplikacji:
location /static/ {
alias /var/www/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location / {
proxy_pass http://localhost:8080;
include /etc/nginx/proxy-params.inc;
}
Żądania do /static/ są obsługiwane bezpośrednio z dysku, z nagłówkiem cache na 30 dni. Reszta ruchu trafia do aplikacji. To znacząco odciąża serwer aplikacji i przyspiesza ładowanie strony.
Rate limiting — ochrona przed nadużyciami
Nginx pozwala ograniczyć liczbę żądań z jednego adresu IP:
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
server {
location / {
limit_req zone=general burst=20 nodelay;
proxy_pass http://localhost:8080;
}
}
Ta konfiguracja pozwala na 10 żądań na sekundę z jednego IP, z buforem 20 dodatkowych żądań. Przekroczenie limitu skutkuje odpowiedzią 503. To prosta, ale skuteczna ochrona przed prostymi atakami i botami.
Podsumowanie
Nginx jako reverse proxy to standard w nowoczesnej infrastrukturze webowej. Zapewnia SSL termination, load balancing, cache, rate limiting i możliwość hostowania wielu serwisów na jednym serwerze. Konfiguracja jest deklaratywna i czytelna, a społeczność dostarcza rozwiązania na praktycznie każdy scenariusz. Jeśli Twoja aplikacja webowa jest wystawiona bezpośrednio do internetu bez reverse proxy — warto to zmienić.