NGINX: Nazwy domen i dyrektywa server_name
12 Sep 2019
best-practices http listen nginx server_name
Dokładne nazwy, nazwy symboli wieloznacznych rozpoczynające się od gwiazdki i nazwy symboli wieloznacznych kończące się gwiazdką są przechowywane w trzech tablicach skrótów powiązanych z dyrektywami nasłuchiwania.
Najpierw przeszukiwana jest tabela skrótów z dokładnymi nazwami. Jeśli więc najczęściej żądanymi nazwami serwerów są np. example.com i www.example.com, bardziej efektywne jest ich jawne zdefiniowanie.
Jeśli nie zostanie znaleziona dokładna nazwa, przeszukiwana jest tablica skrótów z nazwami symboli wieloznacznych rozpoczynającymi się gwiazdką. Jeśli nie ma tam żądanej nazwy, przeszukiwana jest tablica skrótów z nazwami symboli wieloznacznych kończącymi się gwiazdką. Wyszukiwanie takiej tablicy skrótów jest wolniejsze niż wyszukiwanie tabeli skrótów nazw dokładnych, ponieważ nazwy są wyszukiwane według części domeny.
Jeżeli żadne z dopasowań nazwy serwera nie zostanie znalezione, to na samym końcu przeszukiwana jest tablica skrótów nazw zbudowanych za pomocą wyrażeń regularnych. Wyrażenia regularne są testowane sekwencyjnie, dlatego są najwolniejszą metodą i nie są skalowalne. Z tych powodów lepiej jest używać dokładnych nazw tam, gdzie to możliwe w celu dokładnej identyfikacji i filtrowania puli domen.
Podczas wyszukiwania serwera wirtualnego według nazwy, jeśli nazwa pasuje do więcej niż jednego z określonych wariantów, np. zarówno nazwa wieloznaczna, jak i wyrażenie regularne pasują do żądania, wybierany będzie wariant z zachowaniem następującej kolejności (co wyjaśniłem już wyżej):
- dokładne dopasowanie
- najdłuższa nazwa wieloznaczna rozpoczynająca się od gwiazdki, np. *.example.com
- najdłuższa nazwa symbolu wieloznacznego kończąca się gwiazdką, np. api.*
- pierwsze pasujące wyrażenie regularne (w kolejności pojawienia się w pliku konfiguracyjnym)
Spójrzmy jeszcze co na temat nazw wieloznacznych oraz wyrażeń regularnych mówi oficjalna dokumentacja:
A wildcard name may contain an asterisk only on the name’s start or end, and only on a dot border. The names www.*.example.org and w*.example.org are invalid. [...] A special wildcard name in the form .example.org can be used to match both the exact name example.org and the wildcard name *.example.org.
The name *.example.org matches not only www.example.org but www.sub.example.org as well.
To use a regular expression, the server name must start with the tilde character. [...] otherwise it will be treated as an exact name, or if the expression contains an asterisk, as a wildcard name (and most likely as an invalid one). Do not forget to set ^ and $ anchors. They are not required syntactically, but logically. Also note that domain name dots should be escaped with a backslash. A regular expression containing the characters { and } should be quoted.
Na podstawie tego należy wiedzieć, że:
- stosowanie nazw dokładnych domen jest najbardziej zalecane i przetwarzane w pierwszej kolejności
- znak gwiazdki w nazwie wieloznacznej przechwytuje wszystko od lewej strony do pierwszego znaku separatora (czyli może chwycić kilka etykiet nazwy domenowej a nie tylko jedną), np. *.example.org obsłuży:
- api.example.org
- foo.bar.example.org
- x.y.z.foo.bar.example.org
- konstrukcje www.*.example.org i w*.example.org są niepoprawne, rozwiązaniem tego może być użycie wyrażeń regularnych
- nazwy wieloznaczne, np. *.example.org lub .example.org, mogą zakrywać bloki wykorzystujące wyrażenia regularne
- nazwa .example.org jest specjalnym symbolem wieloznacznym przechowywanym w tabeli skrótów nazw wieloznacznych, a nie w tabeli skrótów nazw dokładnych, mimo tego, że obsługuje za jednym razem example.org i *.example.org
- jeśli wykorzystujemy nazwę *.example.org, będzie trzeba rozbić ją na dokładne dopasowania, aby móc przechwycić domeny za pomocą ~^(?<foo>.+).bar.example.org (lub w specyficznych sytuacjach wykorzystać konstrukcję
if
)
- wyrażenia regularne przetwarzane są sekwencyjnie, czyli w kolejności wystąpienia w pliku konfiguracyjnym
- wyrażenie regularne musi zaczynać się od znaku
~
inaczej zostanie zinterpretowana jako dokładna nazwa - jeżeli wyrażenie regularne zawiera znak gwiazdki, zostanie zinterpretowane jako nazwa wieloznaczna
- kropki w nazwie (w wyrażeniu regularnym) muszą być poprzedzone odwrotnym ukośnikiem
- znaki
^
i$
są wymagane dla zachowania logiki wyrażenia regularnego
- wyrażenie regularne musi zaczynać się od znaku
Ogólnie rzecz biorąc, wykorzystywanie wyrażeń regularnych czy to dla server_name
, czy dla bloków lokalizacji jest w większości przypadków średnim pomysłem (trzeba je dokładnie przetestować) i powinno być ograniczone do naprawdę prostych przypadków — jednak w specyficznych sytuacjach może być pomocne. Co ciekawe, podczas budowania wyrażeń regularnych, możliwe jest przechwycenie takiego wyrażenia (lub jego elementu) i użycie go jako zmiennej:
server {
server_name ~^(www\.)?(?<domain>.+)$;
location / {
root /sites/$domain;
}
}
server {
server_name ~^(?<subdomain>.+)\.example\.org;
location / {
rewrite ^/(.*)$ /$subdomain/$1 break;
}
}
Oto przykład konfiguracji niezalecanej:
server {
listen 192.168.252.10:80;
# Z oficjalnej dokumentacji: "Searching wildcard names hash table is slower than searching
# exact names hash table because names are searched by domain parts. Note that the special
# wildcard form '.example.org' is stored in a wildcard names hash table and not in an exact
# names hash table.":
server_name .example.org;
...
}
Natomiast niżej znajduje się przykład konfiguracji zalecanej (zwłaszcza w stosunku do powyższej):
server {
listen 192.168.252.10:80;
# .example.org = example.org i *.example.org
server_name example.org www.example.org *.example.org;
...
}
Widzimy, że kiedy najczęściej żądanymi nazwami serwera są example.org i www.example.org, bardziej efektywne jest ich jawne zdefiniowanie (drugi przykład).
Na koniec poruszę jeszcze jeden ciekawy problem. Jeśli zdefiniowana przez nas domena nie istnieje, klient HTTP nie będzie mógł połączyć się z serwerem HTTP, a tym samym nie otrzyma żadnej odpowiedzi, ponieważ protokoły niższej warstwy nie będą mogły zestawić połączenia, aby zapewnić kanał komunikacji dla protokołu HTTP. Często się jednak zdarza, że klient ustawił domenę, która nie jest obsługiwana przez serwer, a żądanie do serwera dochodzi — wtedy dobrym pomysłem jest zwrócić kod błędu, który zapętli się wewnątrz serwera i zerwie połączenie po określonym czasie bez faktycznego zwrócenia odpowiedzi do klienta.
Serwer NGINX pozwala zrobić to za pomocą return 444;
, który nie jest częścią standardu (w rzeczywistości nie jest to nawet stan odpowiedzi) i został wprowadzony, aby wskazać, by serwer po prostu nie wysłał odpowiedzi i zamknął połączenie. Po ustawieniu tej dyrektywy, wysyłając zapytania narzędziem curl
otrzymamy w odpowiedzi Empty reply from server. Inne serwery w takim przypadku zwracają często kod 404 Domain not found. Po więcej informacji zerknij do artykułu NGINX: Przetwarzanie żądań a niezdefiniowane nazwy serwerów.