Introduzione
Nginx è uno dei server web più popolari al mondo. Può gestire con successo carichi elevati con molte connessioni client simultanee e può funzionare come server web, server di posta o server proxy inverso.
In questa guida, discuteremo alcuni dettagli dietro le quinte che determinano come Nginx elabora le richieste dei client. Comprendere queste idee può aiutare a eliminare le congetture nella progettazione di blocchi server e di posizione e può rendere l’elaborazione delle richieste meno imprevedibile.
Configurazioni dei Blocchi Nginx
Nginx divide logicamente le configurazioni destinate a servire contenuti diversi in blocchi, che vivono in una struttura gerarchica. Ogni volta che viene effettuata una richiesta da parte di un client, Nginx inizia un processo per determinare quali blocchi di configurazione dovrebbero essere utilizzati per gestire la richiesta. Questo processo decisionale è ciò di cui parleremo in questa guida.
I principali blocchi di cui parleremo sono il blocco server e il blocco location.
A server block is a subset of Nginx’s configuration that defines a virtual server used to handle requests of a defined type. Administrators often configure multiple server blocks and decide which block should handle which connection based on the requested domain name, port, and IP address.
A location block lives within a server block and is used to define how Nginx should handle requests for different resources and URIs for the parent server. The URI space can be subdivided in whatever way the administrator likes using these blocks. It is an extremely flexible model.
Come Nginx decide quale blocco server gestirà una richiesta
Dato che Nginx permette all’amministratore di definire più blocchi server che funzionano come istanze separate di server web virtuali, ha bisogno di una procedura per determinare quale di questi blocchi server verrà utilizzato per soddisfare una richiesta.
Lo fa attraverso un sistema definito di controlli che vengono utilizzati per trovare la corrispondenza migliore possibile. I principali direttive del blocco server di cui si preoccupa Nginx durante questo processo sono la direttiva listen
e la direttiva server_name
.
Analisi della direttiva listen
per trovare corrispondenze possibili
Prima, Nginx osserva l’indirizzo IP e la porta della richiesta. Confronta questo con la direttiva listen
di ciascun server per costruire un elenco dei blocchi server che possono potenzialmente risolvere la richiesta.
La direttiva listen
di solito definisce a quale indirizzo IP e porta risponderà il blocco del server. Per impostazione predefinita, qualsiasi blocco del server che non include una direttiva listen
riceve i parametri di ascolto di 0.0.0.0:80
(o 0.0.0.0:8080
se Nginx viene eseguito da un utente normale, non root). Questo consente a questi blocchi di rispondere alle richieste su qualsiasi interfaccia alla porta 80, ma questo valore predefinito non ha molto peso nel processo di selezione del server.
La direttiva listen
può essere impostata su:
- Una combinazione di indirizzo IP/porta.
- A lone IP address which will then listen on the default port 80.
- A lone port which will listen to every interface on that port.
- Il percorso a un socket Unix.
La terza opzione avrà generalmente implicazioni solo quando si passano le richieste tra server diversi.
Quando si cerca di determinare a quale blocco del server inviare una richiesta, Nginx cercherà prima di decidere in base alla specificità della direttiva listen
utilizzando le seguenti regole:
- Nginx traduce tutte le direttive
listen
“incomplete” sostituendo i valori mancanti con i loro valori predefiniti in modo che ogni blocco possa essere valutato dal suo indirizzo IP e porta. Alcuni esempi di queste traduzioni sono:- Un blocco senza direttiva
listen
utilizza il valore0.0.0.0:80
. - Un blocco impostato su un indirizzo IP
111.111.111.111
senza porta diventa111.111.111.111:80
- Un blocco impostato su porta
8888
senza indirizzo IP diventa0.0.0.0:8888
- Un blocco senza direttiva
- Nginx cerca quindi di raccogliere un elenco dei blocchi server che corrispondono alla richiesta in modo più specifico in base all’indirizzo IP e alla porta. Ciò significa che ogni blocco che utilizza funzionalmente
0.0.0.0
come suo indirizzo IP (per corrispondere a qualsiasi interfaccia) non verrà selezionato se ci sono blocchi corrispondenti che elencano un indirizzo IP specifico. In ogni caso, la porta deve corrispondere esattamente. - Se c’è solo una corrispondenza più specifica, quel blocco server verrà utilizzato per servire la richiesta. Se ci sono più blocchi server con lo stesso livello di specificità corrispondente, Nginx inizia quindi a valutare la direttiva
server_name
di ciascun blocco server.
È importante capire che Nginx valuterà solo la direttiva server_name
quando deve distinguere tra blocchi server che corrispondono allo stesso livello di specificità nella direttiva listen
. Ad esempio, se example.com
è ospitato sulla porta 80
di 192.168.1.10
, una richiesta per example.com
verrà sempre servita dal primo blocco in questo esempio, nonostante la direttiva server_name
nel secondo blocco.
server {
listen 192.168.1.10;
. . .
}
server {
listen 80;
server_name example.com;
. . .
}
Nel caso in cui più di un blocco server corrisponda con uguale specificità, il passaggio successivo è controllare la direttiva server_name
.
Analisi della Direttiva server_name
per Scegliere una Corrispondenza
Successivamente, per valutare ulteriormente le richieste che hanno direttive listen
altrettanto specifiche, Nginx controlla l’intestazione Host
della richiesta. Questo valore contiene il dominio o l’indirizzo IP che il client stava effettivamente cercando di raggiungere.
Nginx cerca di trovare la corrispondenza migliore per il valore trovato guardando la direttiva server_name
all’interno di ciascuno dei blocchi del server che sono ancora candidati alla selezione. Nginx valuta questi utilizzando la seguente formula:
- Nginx cercherà prima di trovare un blocco del server con un
server_name
che corrisponda esattamente al valore nell’intestazioneHost
della richiesta esattamente. Se questo viene trovato, il blocco associato verrà utilizzato per servire la richiesta. Se vengono trovate più corrispondenze esatte, viene utilizzata la prima. - Se non viene trovata alcuna corrispondenza esatta, Nginx cercherà quindi di trovare un blocco del server con un
server_name
che corrisponda utilizzando un wildcard iniziale (indicato da un*
all’inizio del nome nella configurazione). Se ne viene trovato uno, quel blocco verrà utilizzato per servire la richiesta. Se vengono trovate più corrispondenze, viene utilizzata la più lunga. - Se non viene trovata alcuna corrispondenza utilizzando un wildcard iniziale, Nginx cerca quindi un blocco del server con un
server_name
che corrisponda utilizzando un wildcard finale (indicato da un nome del server che termina con un*
nella configurazione). Se ne viene trovato uno, quel blocco viene utilizzato per servire la richiesta. Se vengono trovate più corrispondenze, viene utilizzata la più lunga. - Se nessuna corrispondenza viene trovata utilizzando un jolly di tracciamento, Nginx valuta quindi i blocchi del server che definiscono il
server_name
utilizzando espressioni regolari (indicate da un~
prima del nome). Il primoserver_name
con un’espressione regolare che corrisponde all’intestazione “Host” verrà utilizzato per servire la richiesta. - Se non viene trovata alcuna corrispondenza con espressioni regolari, Nginx seleziona quindi il blocco del server predefinito per quell’indirizzo IP e porta.
Ogni combinazione di indirizzo IP/porta ha un blocco del server predefinito che verrà utilizzato quando non può essere determinata una linea d’azione con i metodi sopra indicati. Per una combinazione di indirizzo IP/porta, questo sarà o il primo blocco nella configurazione o il blocco che contiene l’opzione default_server
come parte della direttiva listen
(che sovrascriverebbe l’algoritmo del primo trovato). Può esserci solo una dichiarazione default_server
per ogni combinazione di indirizzo IP/porta.
Esempi
Se è definito un server_name
che corrisponde esattamente al valore dell’intestazione Host
, quel blocco del server viene selezionato per elaborare la richiesta.
In questo esempio, se l’intestazione Host
della richiesta fosse impostata su host1.example.com
, sarebbe selezionato il secondo server:
server {
listen 80;
server_name *.example.com;
. . .
}
server {
listen 80;
server_name host1.example.com;
. . .
}
Se non viene trovata alcuna corrispondenza esatta, Nginx controlla se esiste un server_name
con un asterisco iniziale compatibile. La corrispondenza più lunga che inizia con un asterisco sarà selezionata per soddisfare la richiesta.
In questo esempio, se la richiesta avesse un’intestazione Host
di www.example.org
, sarebbe stato selezionato il secondo blocco del server:
server {
listen 80;
server_name www.example.*;
. . .
}
server {
listen 80;
server_name *.example.org;
. . .
}
server {
listen 80;
server_name *.org;
. . .
}
Se non viene trovata alcuna corrispondenza con un asterisco iniziale, Nginx verificherà quindi se esiste una corrispondenza utilizzando un asterisco alla fine dell’espressione. A questo punto, verrà selezionata la corrispondenza più lunga che termina con un asterisco per servire la richiesta.
Ad esempio, se la richiesta ha un’intestazione Host
impostata su www.example.com
, sarà selezionato il terzo blocco del server:
server {
listen 80;
server_name host1.example.com;
. . .
}
server {
listen 80;
server_name example.com;
. . .
}
server {
listen 80;
server_name www.example.*;
. . .
}
Se non possono essere trovate corrispondenze con caratteri jolly, Nginx passerà quindi a tentare di abbinare le direttive server_name
che utilizzano espressioni regolari. Verrà selezionata la prima espressione regolare corrispondente per rispondere alla richiesta.
Ad esempio, se l’intestazione Host
della richiesta è impostata su www.example.com
, verrà selezionato il secondo blocco del server per soddisfare la richiesta:
server {
listen 80;
server_name example.com;
. . .
}
server {
listen 80;
server_name ~^(www|host1).*\.example\.com$;
. . .
}
server {
listen 80;
server_name ~^(subdomain|set|www|host1).*\.example\.com$;
. . .
}
Se nessuno dei passaggi precedenti riesce a soddisfare la richiesta, allora la richiesta verrà passata al server predefinito per l’indirizzo IP e la porta corrispondenti.
Blocchi di Localizzazione Corrispondenti
Simile al processo che Nginx utilizza per selezionare il blocco del server che elaborerà una richiesta, Nginx ha anche un algoritmo stabilito per decidere quale blocco di posizione all’interno del server utilizzare per gestire le richieste.
Sintassi del Blocco di Posizione
Prima di coprire come Nginx decida quale blocco di posizione utilizzare per gestire le richieste, vediamo alcuni degli elementi di sintassi che potresti trovare nelle definizioni dei blocchi di posizione. I blocchi di posizione risiedono all’interno dei blocchi di server (o altri blocchi di posizione) e vengono utilizzati per decidere come elaborare l’URI della richiesta (la parte della richiesta che arriva dopo il nome di dominio o l’indirizzo IP/porta).
I blocchi di posizione generalmente hanno la seguente forma:
location optional_modifier location_match {
. . .
}
Il location_match
nel precedente definisce cosa Nginx dovrebbe controllare l’URI della richiesta. L’esistenza o la non esistenza del modificatore nell’esempio precedente influisce sul modo in cui Nginx tenta di abbinare il blocco di posizione. I modificatori seguenti faranno sì che il blocco di posizione associato venga interpretato come segue:
- (nessuno): Se non sono presenti modificatori, la posizione viene interpretata come corrispondenza di prefisso. Ciò significa che la posizione data verrà confrontata con l’inizio dell’URI della richiesta per determinare una corrispondenza.
=
: Se viene utilizzato un segno di uguaglianza, questo blocco verrà considerato una corrispondenza se l’URI della richiesta corrisponde esattamente alla posizione data.~
: Se è presente un modificatore tilde, questa posizione verrà interpretata come una corrispondenza esatta espressione regolare sensibile alle maiuscole.~*
: Se viene utilizzato un modificatore tilde e asterisco, il blocco di posizione verrà interpretato come una corrispondenza esatta espressione regolare non sensibile alle maiuscole.^~
: Se è presente un modificatore di accento circonflesso e tilde e se questo blocco è selezionato come la migliore corrispondenza non espressione regolare, la corrispondenza con espressioni regolari non avrà luogo.
Esempi che Dimostrano la Sintassi del Blocco di Posizione
Come esempio di corrispondenza per prefisso, il seguente blocco di posizione può essere selezionato per rispondere alle richieste URl che assomigliano a /sito
, /sito/pagina1/index.html
, o /sito/index.html
:
location /site {
. . .
}
Per una dimostrazione della corrispondenza esatta della richiesta URI, questo blocco verrà sempre utilizzato per rispondere a una richiesta URI che assomiglia a /pagina1
. Non verrà utilizzato per rispondere a una richiesta URI /pagina1/index.html
. Tieni presente che se questo blocco viene selezionato e la richiesta viene soddisfatta utilizzando una pagina indice, avverrà un reindirizzamento interno verso un’altra posizione che sarà l’effettivo gestore della richiesta:
location = /page1 {
. . .
}
Come esempio di una posizione che dovrebbe essere interpretata come un’espressione regolare sensibile alle maiuscole e minuscole, questo blocco potrebbe essere utilizzato per gestire le richieste per /tortoise.jpg
, ma non per /FLOWER.PNG
:
location ~ \.(jpe?g|png|gif|ico)$ {
. . .
}
A block that would allow for case-insensitive matching similar to the above is shown below. Here, both /tortoise.jpg
and /FLOWER.PNG
could be handled by this block:
location ~* \.(jpe?g|png|gif|ico)$ {
. . .
}
Infine, questo blocco impedirebbe che si verifichi un matching di espressioni regolari se viene determinato che è la migliore corrispondenza non basata su espressioni regolari. Potrebbe gestire le richieste per /costumes/ninja.html
:
location ^~ /costumes {
. . .
}
Come vedi, i modificatori indicano come dovrebbe essere interpretato il blocco di posizione. Tuttavia, questo non ci dice l’algoritmo che Nginx utilizza per decidere a quale blocco di posizione inviare la richiesta. Ne parleremo dopo.
Come Nginx Sceglie Quale Posizione Utilizzare per Gestire le Richieste
Nginx sceglie la posizione che verrà utilizzata per servire una richiesta in modo simile a come seleziona un blocco di server. Esegue un processo che determina il miglior blocco di posizione per qualsiasi richiesta data. Comprendere questo processo è un requisito fondamentale per poter configurare Nginx in modo affidabile e accurato.
Tenendo presente i tipi di dichiarazioni di posizione descritti sopra, Nginx valuta i possibili contesti di posizione confrontando l’URI della richiesta con ciascuna delle posizioni. Fa questo utilizzando l’algoritmo seguente:
- Nginx inizia controllando tutti i match basati sui prefissi (tutti i tipi di location che non coinvolgono espressioni regolari). Controlla ogni location rispetto all’intero URI della richiesta.
- Innanzitutto, Nginx cerca una corrispondenza esatta. Se viene trovato un blocco di location che utilizza il modificatore
=
per corrispondere esattamente all’URI della richiesta, questo blocco di location viene immediatamente selezionato per servire la richiesta. - Se non vengono trovate corrispondenze esatte (con il modificatore
=
), Nginx passa quindi alla valutazione dei prefissi non esatti. Trova il prefisso corrispondente più lungo per l’URI della richiesta, che poi valuta come segue:- Se il prefisso corrispondente più lungo ha il modificatore
^~
, allora Nginx terminerà immediatamente la ricerca e selezionerà questa location per servire la richiesta. - Se il prefisso corrispondente più lungo non utilizza il modificatore
^~
, la corrispondenza viene memorizzata temporaneamente da Nginx in modo che il focus della ricerca possa essere spostato.
- Se il prefisso corrispondente più lungo ha il modificatore
- Dopo che la posizione con il prefisso corrispondente più lungo è stata determinata e memorizzata, Nginx procede valutando le posizioni delle espressioni regolari (sia sensibili che insensibili alle maiuscole). Se ci sono posizioni di espressioni regolari all’interno della posizione con il prefisso corrispondente più lungo, Nginx sposterà queste in cima alla sua lista di posizioni di espressioni regolari da controllare. Nginx quindi cerca di corrispondere alle posizioni delle espressioni regolari sequenzialmente. La prima posizione delle espressioni regolari che corrisponde all’URI della richiesta viene immediatamente selezionata per servire la richiesta.
- Se non vengono trovate posizioni di espressioni regolari che corrispondono all’URI della richiesta, viene selezionata la posizione con il prefisso memorizzato in precedenza per servire la richiesta.
È importante capire che, per impostazione predefinita, Nginx servirà corrispondenze con espressioni regolari in preferenza alle corrispondenze con prefissi. Tuttavia, valuta prima le posizioni con i prefissi, consentendo all’amministratore di sovrascrivere questa tendenza specificando posizioni utilizzando i modificatori =
e ^~
.
È anche importante notare che, mentre le posizioni con prefissi selezionano generalmente in base alla corrispondenza più lunga e specifica, la valutazione delle espressioni regolari si interrompe quando viene trovata la prima posizione corrispondente. Ciò significa che la posizione nella configurazione ha vaste implicazioni per le posizioni delle espressioni regolari.
Infine, è importante capire che le corrispondenze delle espressioni regolari all’interno della corrispondenza del prefisso più lungo “salteranno la fila” quando Nginx valuta le posizioni delle regex. Queste saranno valutate, nell’ordine, prima che vengano considerate qualsiasi altra corrispondenza delle espressioni regolari. Maxim Dounin, uno sviluppatore Nginx incredibilmente utile, spiega in questo post questa parte dell’algoritmo di selezione.
Quando l’Valutazione del Blocco di Localizzazione Salta verso Altre Posizioni?
Parlando in generale, quando un blocco di localizzazione è selezionato per gestire una richiesta, la richiesta viene gestita interamente all’interno di tale contesto da quel punto in poi. Solo la posizione selezionata e le direttive ereditate determinano come viene elaborata la richiesta, senza interferenze da parte dei blocchi di localizzazione fratelli.
Sebbene questa sia una regola generale che ti consentirà di progettare i tuoi blocchi di localizzazione in modo prevedibile, è importante rendersi conto che ci sono momenti in cui una nuova ricerca di localizzazione viene attivata da determinate direttive all’interno della posizione selezionata. Le eccezioni alla regola “solo un blocco di localizzazione” possono avere implicazioni su come viene effettivamente servita la richiesta e potrebbero non essere allineate alle aspettative che avevi quando hai progettato i tuoi blocchi di localizzazione.
Alcune direttive che possono portare a questo tipo di reindirizzamento interno sono:
- indice
- try_files
- rewrite
- error_page
Andiamo adesso ad esaminarli brevemente.
La direttiva index
porta sempre a un reindirizzamento interno se viene utilizzata per gestire la richiesta. Le corrispondenze di posizione esatta sono spesso utilizzate per velocizzare il processo di selezione, terminando immediatamente l’esecuzione dell’algoritmo. Tuttavia, se si effettua una corrispondenza di posizione esatta che è una directory, c’è una buona possibilità che la richiesta venga reindirizzata a una diversa posizione per il trattamento effettivo.
Nell’esempio seguente, la prima posizione è corrisposta da un URI di richiesta /esatto
, ma per gestire la richiesta, la direttiva index
ereditata dal blocco avvia un reindirizzamento interno al secondo blocco:
index index.html;
location = /exact {
. . .
}
location / {
. . .
}
Nel caso sopra, se è davvero necessario che l’esecuzione rimanga nel primo blocco, sarà necessario trovare un metodo diverso per soddisfare la richiesta alla directory. Ad esempio, è possibile impostare un index
non valido per quel blocco e attivare autoindex
:
location = /exact {
index nothing_will_match;
autoindex on;
}
location / {
. . .
}
Questo è un modo per impedire a un index
di cambiare contesti, ma probabilmente non è utile per la maggior parte delle configurazioni. Principalmente, una corrispondenza esatta su directory può essere utile per cose come la riscrittura della richiesta (che comporta anche una nuova ricerca della posizione).
Un’altra istanza in cui la posizione di elaborazione può essere rivalutata è con la direttiva try_files
. Questa direttiva dice a Nginx di verificare l’esistenza di un insieme nominato di file o directory. L’ultimo parametro può essere un URI a cui Nginx farà un reindirizzamento interno.
Considera la seguente configurazione:
root /var/www/main;
location / {
try_files $uri $uri.html $uri/ /fallback/index.html;
}
location /fallback {
root /var/www/another;
}
Nell’esempio precedente, se viene effettuata una richiesta per /blahblah
, la prima posizione inizialmente riceverà la richiesta. Proverà a cercare un file chiamato blahblah
nella directory /var/www/main
. Se non riesce a trovarne uno, cercherà quindi un file chiamato blahblah.html
. Cercherà quindi se esiste una directory chiamata blahblah/
all’interno della directory /var/www/main
. In caso di fallimento di tutti questi tentativi, verrà reindirizzato a /fallback/index.html
. Questo attiverà un’altra ricerca di posizione che verrà intercettata dal secondo blocco di posizione. Questo servirà il file /var/www/another/fallback/index.html
.
Un’altra direttiva che può portare al passaggio di un blocco di posizione è la direttiva rewrite
. Quando si utilizza il parametro last
con la direttiva rewrite
, o quando non si utilizza alcun parametro, Nginx cercherà una nuova corrispondenza di posizione in base ai risultati della riscrittura.
Per esempio, se modifichiamo l’ultimo esempio per includere una riscrittura, possiamo vedere che la richiesta viene a volte passata direttamente al secondo blocco di posizione senza fare affidamento sulla direttiva try_files
:
root /var/www/main;
location / {
rewrite ^/rewriteme/(.*)$ /$1 last;
try_files $uri $uri.html $uri/ /fallback/index.html;
}
location /fallback {
root /var/www/another;
}
Nell’esempio precedente, una richiesta per /rewriteme/hello
sarà gestita inizialmente dal primo blocco di posizione. Verrà riscritto in /hello
e verrà cercata una posizione. In questo caso, corrisponderà nuovamente alla prima posizione e verrà elaborato dalla direttiva try_files
come al solito, forse rimandando a /fallback/index.html
se non viene trovato nulla (utilizzando il reindirizzamento interno con try_files
di cui abbiamo discusso sopra).
Tuttavia, se viene effettuata una richiesta per /rewriteme/fallback/hello
, il primo blocco verrà nuovamente abbinato. La riscrittura verrà applicata nuovamente, questa volta risultando in /fallback/hello
. La richiesta verrà quindi gestita dal secondo blocco di posizione.
A related situation happens with the return
directive when sending the 301
or 302
status codes. The difference in this case is that it results in an entirely new request in the form of an externally visible redirect. This same situation can occur with the rewrite
directive when using the redirect
or permanent
flags. However, these location searches shouldn’t be unexpected, since externally visible redirects always result in a new request.
La direttiva error_page
può portare a un reindirizzamento interno simile a quello creato da try_files
. Questa direttiva viene utilizzata per definire cosa deve accadere quando vengono incontrati determinati codici di stato. Questo probabilmente non verrà mai eseguito se è impostato try_files
, poiché tale direttiva gestisce l’intero ciclo di vita di una richiesta.
Considera questo esempio:
root /var/www/main;
location / {
error_page 404 /another/whoops.html;
}
location /another {
root /var/www;
}
Ogni richiesta (tranne quelle che iniziano con /another
) verrà gestita dal primo blocco, che servirà file da /var/www/main
. Tuttavia, se un file non viene trovato (uno stato 404), verrà effettuato un reindirizzamento interno a /another/whoops.html
, portando a una nuova ricerca di posizione che alla fine si posizionerà sul secondo blocco. Questo file verrà servito da /var/www/another/whoops.html
.
Come puoi vedere, comprendere le circostanze in cui Nginx attiva una nuova ricerca di posizione può aiutare a prevedere il comportamento che vedrai quando effettui richieste.
Conclusione
Capire i modi in cui Nginx elabora le richieste dei client può rendere il tuo lavoro di amministratore molto più semplice. Sarai in grado di sapere quale blocco server selezionerà Nginx in base a ciascuna richiesta del client. Sarai anche in grado di capire come verrà selezionato il blocco di posizione in base all’URI della richiesta. In generale, conoscere il modo in cui Nginx seleziona blocchi diversi ti darà la capacità di tracciare i contesti che Nginx applicherà per servire ciascuna richiesta.