Blog članak

Savladavanje konfiguracije Nginx-a: reverzni proxy, predmemoriranje i sigurnost

Izgradite pouzdan Nginx rubni sloj s reverznim proxyjem, provjerom ispravnosti udaljenih poslužitelja, revalidacijom i invalidacijom predmemorije, TLS-om, sigurnosnim zaglavljima i korisnom opservabilnošću.

Stilizirani Nginx reverzni proxy usmjerava promet kroz čvorove predmemorije prema poslužitelju zaštićenom TLS-om

Nginx je često kontrolna točka u kojoj se aplikacija susreće s javnim internetom. On raskida TLS, odabire poslužitelj u pozadini (upstream), odlučuje može li se odgovor predmemorirati, dodaje sigurnosna zaglavlja i pruža dokaze koji su vam potrebni kada zahtjev ne uspije. Pouzdana konfiguracija stoga je mnogo više od radnog izraza proxy_pass: ona usmjeravanje, svježinu, granice povjerenja i ponašanje u slučaju kvara čini eksplicitnima.

Ovaj vodič pretpostavlja da već poznajete osnove uređivanja programskog bloka poslužitelja. Primjeri su namjerno maleni; prilagodite nazive domaćina (hostnames), certifikate, aplikacijske priključke (ports) i vremenska ograničenja (timeouts) svojoj usluzi, a zatim provjerite ispravnost naredbom nginx -t prije ponovnog učitavanja.

Započnite s ispravnim reverznim proxyjem

Minimalni proxy trebao bi proslijediti izvornog domaćina i pouzdan kontekst zahtjeva aplikaciji. Zaglavlje X-Forwarded-For mora biti izgrađeno od adrese koju Nginx vidi; nemojte naslijepo prihvaćati zaglavlje prosljeđivanja koje je isporučio klijent, osim ako zahtjev nije došao preko proxyja koji sami kontrolirate.

location / {
    proxy_pass http://app_backend;
    proxy_http_version 1.1;
    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;
    proxy_set_header Connection "";
    proxy_connect_timeout 5s;
    proxy_read_timeout 60s;
}

Postavka Connection "" omogućuje ponovno korištenje trajnih (keepalive) veza prema poslužitelju u pozadini umjesto prosljeđivanja zaglavlja Connection koje se odnosi na pojedinačni skok (hop-by-hop). Razlika između proxy_pass http://app_backend; i proxy_pass http://app_backend/; je važna: kada je naveden URI, Nginx zamjenjuje onaj dio normaliziranog URI-ja zahtjeva koji se podudara s lokacijom. Kosa crta na kraju (trailing slash) stoga može ukloniti prefiks putanje i pretvoriti očekivani /api/users u /users. Testirajte i aplikacijsku rutu i rezultirajuću rutu poslužitelja u pozadini prilikom promjene ovog retka.

Za više od jedne aplikacijske instance, definirajte poslužitelj u pozadini i odaberite način balansiranja opterećenja koji odgovara radnom opterećenju:

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

location / {
    proxy_pass http://app_backend;
    proxy_next_upstream error timeout http_502 http_503 http_504;
    proxy_next_upstream_tries 2;
}

Kružno raspoređivanje (round-robin) zadana je postavka; NGINX Open Source također podržava least_conn, ip_hash i hash ... consistent. Metoda ip_hash može pružiti jednostavnu postojanost sesije (session persistence), ali nije zamjena za dijeljenu pohranu sesija. Metode least_time i random imaju ograničenja verzija ili izdanja proizvoda, stoga provjerite trenutnu dokumentaciju za Nginx prije kopiranja primjera koji se plasira kao univerzalna značajka. Postavke max_fails i fail_timeout pasivne su provjere: one reagiraju na neuspješne prosljeđene zahtjeve umjesto da aktivno ispituju krajnju točku. Postavka keepalive 32 predstavlja predmemoriju neaktivnih veza prema pozadini po radnom procesu, a ne ograničenje ukupnog broja veza.

Pažljivo predmemorirajte: revalidacija nije invalidacija

Predmemorija je sigurna samo kada njezin ključ predstavlja svaku dimenziju koja mijenja odgovor. Nikada nemojte predmemorirati personalizirani odgovor pod ključem koji dijele svi korisnici ili zakupci (tenants). Izričito zaobiđite privatne zahtjeve i uključite dimenziju zakupca ili autorizacije u pomno pregledan ključ kada je predmemoriranje doista prikladno.

proxy_cache_path /var/cache/nginx/app
    levels=1:2
    keys_zone=app_cache:20m
    max_size=2g
    inactive=30m
    use_temp_path=off;

map $http_authorization $skip_private_cache {
    default 1;
    ""      0;
}

server {
    location /assets/ {
        proxy_cache app_cache;
        proxy_cache_key "$scheme$proxy_host$request_uri";
        proxy_cache_valid 200 10m;
        proxy_cache_min_uses 2;
        proxy_cache_revalidate on;
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_bypass $skip_private_cache;
        proxy_no_cache $skip_private_cache;
        add_header X-Cache-Status $upstream_cache_status always;
        proxy_pass http://app_backend;
    }
}

Postavka proxy_cache_revalidate on izvršava uvjetni zahtjev, omogućujući poslužitelju u pozadini vraćanje statusa 304 Not Modified kada pohranjena reprezentacija ostane aktualna. To je revalidacija: unosi ostaju u predmemoriji dok se provjerava njihova svježina. Invalidacija je drukčija: ona izričito uklanja ili čini unos neupotrebljivim. NGINX Open Source nema ugrađenu direktivu za čišćenje (purge). Kada je to moguće, prednost dajte URL-ovima resursa s verzijama ili verziji sadržaja u ključu predmemorije. Ako je nužno strogo čišćenje, koristite pomno ograničen modul treće strane kao što je ngx_cache_purge ili ugrađenu značajku NGINX Plus poslužitelja proxy_cache_purge; nikada nemojte izlagati javnu PURGE krajnju točku.

proxy_cache_use_stale ... updating podržava uzorak zastarjelog tijekom osvježavanja (stale-while-revalidate) tijekom obnove pozadine, dok opcije pogreške i vremenskog ograničenja mogu održati uslugu dostupnom tijekom kratkog incidenta pozadinskog sustava. Promatrajte to kao kompromis u pogledu otpornosti: definirajte koliko star odgovor smije biti i osigurajte da korisnici nikada ne prime privatne podatke iz dijeljene predmemorije. Varijabla $upstream_cache_status izvještava o stanjima kao što su MISS, HIT, EXPIRED, UPDATING i BYPASS, čineći ponašanje predmemorije mjerljivim umjesto anegdotskim.

TLS raskid i sigurnosna zaglavlja

Tipičan TLS rub preusmjerava čist tekstualni promet i primjenjuje zaglavlja na razini na kojoj se ona ne mogu slučajno izgubiti:

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

server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    location / {
        proxy_pass http://app_backend;
    }
}

Lanac certifikata trebao bi sadržavati poslužiteljski certifikat praćen njegovim međucertifikatima (intermediates); privatni ključ mora biti čitljiv samo namjenskom korisničkom računu usluge. TLS 1.2 i 1.3 trenutna su praktična osnovna linija u Nginx dokumentaciji. Za prilagođeni profil šifriranja (cipher) i protokola, koristite TLSRef konfigurator: njegov srednji (Intermediate) profil pogoduje širokoj kompatibilnosti, dok je moderan (Modern) prikladan samo kada svi klijenti podržavaju TLS 1.3. HSTS je teško poništiti za klijente koji su ga predmemorirali; dodajte preload tek nakon provjere je li svaka poddomena spremna za HTTPS.

Direktiva always primjenjuje ova zaglavlja i na odgovore o pogreškama. Nasljeđivanje zaglavlja putem add_header u Nginxu lako se može previdjeti: ugniježđena lokacija koja definira vlastiti add_header može zamijeniti naslijeđena zaglavlja, stoga provjerite učinkovitu konfiguraciju naredbom nginx -T. Prednost dajte pomno osmišljenoj politici sigurnosti sadržaja (Content-Security-Policy), uključujući frame-ancestors, umjesto oslanjanja samo na X-Frame-Options. Nemojte dodavati zastarjeli X-XSS-Protection; trenutne smjernice organizacije OWASP preporučuju da se ne koristi.

Postavite stroge granice prema poslužitelju u pozadini

Postavite ograničenja na temelju aplikacije umjesto da preopterećenje skrivamo iza dugačkih redova čekanja. Korisne kontrole uključuju proxy_send_timeout, proxy_read_timeout, proxy_buffering, proxy_buffer_size, proxy_buffers i client_max_body_size. Malo ograničenje stope zahtjeva može zaštititi prijavu ili skupe API krajnje točke:

http {
    limit_req_zone $binary_remote_addr zone=api_rate:10m rate=10r/s;
    limit_conn_zone $binary_remote_addr zone=client_conn:10m;

    server {
        location /api/ {
            limit_req zone=api_rate burst=20 nodelay;
            limit_conn client_conn 20;
            client_max_body_size 2m;
            proxy_pass http://app_backend;
        }
    }
}

Atribut burst apsorbira kratke vrhunce opterećenja; nodelay odmah odbija višak zahtjeva umjesto da ih stavlja u red čekanja. Kombinirajte ove kontrole s aplikacijskom autentifikacijom i vremenskim ograničenjima pozadinskog sustava. One nisu potpuna obrana od DDoS napada, a pretjerano strogo ograničenje može uskratiti pristup legitimnim korisnicima iza dijeljene NAT adrese.

Učinite greške vidljivima (opservabilnost)

Zadani zapisnik pristupa (access log) rijetko je dovoljan za objašnjenje sporog ili povremenog zahtjeva. Dodajte vremena izvođenja pozadinskog sustava i stanje predmemorije u prilagođeni format, a zatim centralizirajte dobivene zapisnike:

http {
    log_format proxy_timing '$remote_addr $request $status '
        'request_time=$request_time upstream_status=$upstream_status '
        'connect=$upstream_connect_time header=$upstream_header_time '
        'response=$upstream_response_time cache=$upstream_cache_status';
    access_log /var/log/nginx/access.log proxy_timing;
    error_log /var/log/nginx/error.log warn;
}

Varijabla $request_time prikazuje trajanje vidljivo klijentu; varijable vremena pozadinskog sustava pomažu u odvajanju kašnjenja veze, aplikacije i odgovora. Preslikavanje (map) može uvjetno zapisivati samo pogreške ili spore zahtjeve kada je opseg prometa velik. NGINX Open Source može koristiti prilagođeni log_format ili syslog za strukturirano prikupljanje; izlaz u JSON formatu za error_log značajka je izdanja NGINX Plus, a ne pretpostavka koju treba donositi za OSS.

Provjera i uobičajeni načini kvara

Pokrenite nginx -t i pregledajte nginx -T prije ponovnog učitavanja. Zatim testirajte javno ponašanje i ponašanje usmjereno prema pozadini:

curl -I https://example.com/health
curl -I https://example.com/assets/app.css
curl -kI https://example.com/

Greška 404 nakon prosljeđivanja često znači prepisivanje putanje unutar proxy_pass. Nepravilno usmjeravanje aplikacije može proizaći iz nedostajućeg zaglavlja Host, dok povremeni odgovori 502 mogu ukazivati na mrtve poslužitelje u pozadini, nekompatibilno ponašanje trajnih veza ili istek vremena (timeouts). Nedostatak sigurnosnih zaglavlja obično znači ugniježđeni opseg add_header ili nedostatak direktive always. Neočekivani pogoci u predmemoriji mogu ukazivati na koliziju ključeva; provjerite X-Cache-Status i isključite privatne odgovore prije povećanja trajanja predmemorije.

Za povezani kontekst primjene, pogledajte članke o posluživanju statične Astro stranice pomoću Nginxa, optimizaciji performansi web opterećenja na Linuxu, te praktičnom vodiču za sigurnosna zaglavlja. Dobra Nginx konfiguracija je ona koju možete objasniti, izmjeriti i vratiti na staro — a ne ona koja tek prolazi provjeru sintakse.

Izvori i dodatna literatura

Povezana područja

Ova su područja rada usklađena s temom članka i daju čišći prijelaz od edukativnog sadržaja do konkretne implementacije.

Nastavite čitati

Prvo po zajedničkim kategorijama, a zatim po najjačem preklapanju u tagovima.