Siódmy artykuł z cyklu Konfigurujemy VPS - podstawy konfiguracji nginksa.
Czemu konfiguruję nginksa a nie Apache, tłumaczyłem w części piątej. O Apache w przyszłości parę słów napiszę ale w wielu wypadkach nginx sprawi się lepiej i oszczędniej.
Nie opisuję w tym artykule wszystkich możliwości nginxa, wybrałem kilka najważniejszych konstrukcji. Staram się też omówić elementy, które najczęściej bywają konfudujące.
Bardzo prosty minimalny przykład
Zacznijmy od minimalistycznego przykładu.
Plik /etc/nginx/nginx.conf
może wyglądać tak:
# Przykładowy plik konfiguracyjny nginx-a user www-data; worker_processes 1; error_log /var/log/nginx/error.log info; pid /var/run/nginx.pid; events { worker_connections 512; } http { include /etc/nginx/mime.types; default_type application/octet-stream; access_log /var/log/nginx/access.log combined; sendfile on; keepalive_timeout 65; tcp_nodelay on; gzip on; gzip_proxied any; gzip_types text/plain text/html text/css application/x-javascript; uninitialized_variable_warn on; server { listen 80; server_name supersajt.pl www.supersajt.pl localhost; location / { root /var/www; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /var/www/nginx-default; } } }
Z powyższą konfiguracją nginx będzie działał na koncie www-data
,
nasłuchiwał na porcie 80, zapisywał błędy w pliku
/ver/log/nginx/error.log
i serwował statyczne pliki z drzewa poniżej
/var/www
. Do tego w razie błędów wewnętrznych wyświetli plik
/var/www/nginx-default/50x.html
.
Wrzuciłem do powyższego przykładu parę mniej trywialnych elementów (gzip, keepalive, ...), wrócę do nich w dalszej części artykułu.
Testowanie konfiguracji, testowe uruchamianie
Syntaktyczną poprawność pliku konfiguracyjnego sprawdzamy poleceniem
$ nginx -t
(co sprawdzi poprawność /etc/nginx/nginx.conf
) albo
$ nginx -t -c /sciezka/do/nginx.conf
To polecenie niczego nie uruchamia, po prostu sprawdza składnię.
Uwaga: w razie pojawienia się błędów warto zajrzeć do (wskazanego w konfiguracji) error-logu. Nginx zapisuje tam niekiedy więcej informacji, niż pokazuje na konsoli.
Standardową metodą uruchamiania nginx-a jest typowe
$ sudo /etc/init.d/nginx start
Możliwe są też chwilowe testy z palca, nawet bez używania konta
root
. By je robić trzeba:
- zakomentować dyrektywę
user
, - zmienić
port
na jakiś powyżej 1024, - zmienić ścieżki do logów i pliku pid (
error_log
,pid
,access_log
) na jakieś dostępne dla użytkownika z którego odpalamy test (np.error_log /home/pawel/tmp/error.log info;
) - dopisać na głównym poziomie (np. na samym początku)
daemon off;
(ta ostatnia dyrektywa sprawi, że nginx nie będzie przechodził w tło, można go będzie zatrzymać Ctrl-C)
Następnie:
$ nginx -c test.conf
i można testować.
Serwery wirtualne
Nginx bezproblemowo obsługuje serwery wirtualne, zarówno oparte o IP, jak o nazwę, czy wreszcie działające na innym porcie. Przykład:
# ...Parametry globalne ... http { # ...ogólne parametry HTTP... server { listen 80; server_name supersajt.pl www.supersajt.pl localhost; access_log /var/log/nginx/supersajt-pl.log; root /var/www/supersajt; # ... dalsze reguły dla tego serwera .... } server { listen 80; server_name blog.supersajt.pl; access_log /var/log/nginx/blog-supersajt-pl.log; root /var/www/supersajtblog; # ... dalsze reguły dla tego serwera .... } server { listen 443; server_name secure.supersajt.pl; access_log /var/log/nginx/secure-supersajt-pl.log; root /var/www/secure; # ... dalsze reguły dla tego serwera .... } }
(przy podziale na podstawie IP piszemy np. listen 123.107.123.141:80
)
Nginx nasłuchuje na wszystkich portach, dla których jakiś serwer
ma dyrektywę listen
, a dla ustalonego portu wybiera właściwy
serwer na podstawie IP i nazwy.
Jeden plik konfiguracyjny - czy wiele
Nginx pozwala podzielić plik konfiguracyjny na części - obsługuje
dyrektywę include
. Przy czym traktuje ją w pełni elastycznie, tj. w
dowolnym miejscu można włączyć cokolwiek. Obsługa odbywa się w trybie
preprocesora (tj. napisanie include /nazwa/pliku
jest w pełni
równoważne wklejeniu treści tego pliku w to samo miejsce)..
Pakiety nginx-a dla Debiana i Ubuntu naśladują konwencję stosowaną
w Apache, tj. domyślnie instalowany plik /etc/nginx/nginx.conf
wygląda mniej więcej tak:
# ...Parametry globalne, blok events... http { # ...ogólne parametry HTTP, logi, dyrektywy # optymalizacyjne - wszystko to, co wyżej # ale bez dyrektyw server ... include /etc/nginx/sites-enabled/*; }
Chcąc się dostosować do tej konwencji piszemy - na przykład - plik
/etc/nginx/sites-available/supersajtblog
, następnie symlinkujemy go:
$ sudo ln -s /etc/nginx/sites-available/supersajtblog \ /etc/nginx/sites-enabled/
i restartujemy nginxa. Przy tym plik supersajtblog
to po prostu
blok server
, np:
server { listen 80; server_name blog.supersajt.pl # ... }
By wyłączyć dany serwis usuwamy symlink i restartujemy. Ogólnie: jest
jak przy Apache, tylko nie ma pomocniczych skryptów a2ensite
i
a2dissite
, musimy sami robić/usuwać linki.
Ta konwencja jest wymyślona (tak w przypadku Apache, jak Nginxa) głównie dla maszyn serwujących równocześnie wiele domen. Przy prostych instalacjach wygodniejsze mogą być inne sposoby pocięcia konfiguracji, a nawet pozostanie przy pojedynczym pliku konfiguracyjnym.
Sam stosuję coś takiego:
# ... http { # .... server { listen 80; server_name supersajt.pl www.supersajt.pl; access_log /var/log/nginx/supersajt-pl.log; root /var/www/supersajt; include /etc/nginx/supersajt-rules.conf; } server { listen 80; server_name blog.supersajt.pl; access_log /var/log/nginx/blog-supersajt-pl.log; root /var/www/supersajtblog; include /etc/nginx/supersajtblog-rules.conf; } }
tj. pozostawiam w głównym pliku konfiguracyjnym nazwy, porty
logi i główny katalog, a do osobnych
plików wynoszę dyrektywy location
, reguły przepisywania itd itp.
Zalety takiego podejścia? Są dwie.
Pierwsza: ten sam plik /etc/nginx/supersajtblog-rules.conf
mogę
włączyć, bez żadnych zmian, także do konfiguracji testowej (na innym
porcie czy na innej maszynie). Ułatwia to testy (dopieszczam regułki
na konfiguracji testowej w domu, po czym wgrywam na produkcję nie
musząc modyfikować).
Druga: nieraz ten sam plik można włączyć dwa razy. Na przykład oba moje blogi (notatnik i blog po angielsku) są skonfigurowane tak:
server { listen 80; server_name notatnik.mekk.waw.pl; access_log /var/log/nginx/notatnik.log; root /var/www/notatnik; include /etc/nginx/blog.conf; } server { listen 80; server_name blog.mekk.waw.pl; access_log /var/log/nginx/eng_blog.log; root /var/www/eng_blog; include /etc/nginx/blog.conf; }
Podsumowując: dyrektywy include
można traktować jak makra - gdy
jakiś fragment konfiguracji powtarza się w tej samej formie wielokrotnie,
jest dobrym kandydatem na wydzielenie do osobnego pliku.
Dyrektywy location
Kluczową częścią konfiguracji nginx-a są dyrektywy location
. Najprostsza
forma bardzo przypomina znaną z np. Apache:
location /images/ { alias /apps/common/img/; expires 1d; }
(dla żądania http://supersajt.pl/images/plik.png
zostanie zwrócony plik
/apps/common/img/plik.png
, zostaną też ustawione nagłówki cacheowania
pozwalające klientom nie pobierać go na nowo przez 1 dzień)
Różnica między
root
aalias
jest podobna jak w przypadku Apache:alias
wykorzystuje resztę URLa pozostałą po dopasowaniu, aroot
cały URL. Gdybym napisał:location /images/ { root /apps/common/img/; expires 1d; }
to dla żądania
http://supersajt.pl/images/plik.png
zostałby zwrócony plik/apps/common/img/images/plik.png
.Są też pewne różnice zastosowań:
alias
nie może być wykorzystywane wewnątrz lokacji dopasowywanych wyrażeniem regularnym.
Bardzo silny jest zapis oparty o wyrażenia regularne, na przykład:
location ~ ^/myapp/.*\.css$ { expires 30d; } # albo location ~ \.php$ { # Tu dyrektywy obsługi PHP, o których później }
Zapis ze znakiem równości odnosi się tylko i wyłącznie do dokładnie dopasowanych żądań:
location = /google3243a1b08dcaac7.html { empty_gif; }
Wreszcie, zapis ^~
ma tą samą semantykę co zapis bez żadnych
znaczków, ale wyższy priorytet (patrz opis pod koniec tego rozdziału):
location ^~ /myapp/ { # .... }
(identyczne jak location /myapp/
ale wygra z żądaniami
dopasowywanymi wyrażeniami regularnymi).
Mnemotechnika: ^~
znaczy nie-regexpy.
Uwaga: nginx zawsze wybiera jedną, najbardziej pasującą regułę. Przykładowo, jeśli napiszemy:
root /var/www; location /static/ { alias /apps/common/; } location ~ \.png$ { expires 10d; }
to dla żądania
/static/help.png
zostanie pobrany plik/var/www/static/help.png
a nie/apps/common/help.png
, bo nginx wybierze lokację z wyrażeniem regularnym jako bardziej szczegółową.W niemal identycznym zapisie:
root /var/www; location ^~ /static/ { alias /apps/common/; } location ~ \.png$ { expires 10d; }
pobrany zostanie plik
/apps/common/help.png
ale nie będzie miał nagłówków cacheowania (tym razem wybrana zostanie pierwsza dyrektywa).
Dyrektywy location
można zagnieżdżać.
Na przykład:
location /myapp/ { alias /apps/myapp/; location ~ \.(css|js)$ { expires 10d; } location ~ \.admin$ { allow 64.97.11.103; deny all; } }
(definiujemy hierarchię reguł).
Jeden ze sposobów na rozplątanie problemu opisanego wyżej:
location ^~ /static/ { alias /apps/common/; location ~ \.png$ { expires 10d; } }
Dokładny algorytm wyboru location
przez nginx-a jest następujący:
-
jeśli znajdzie dyrektywę
location =
dokładnie pasującą do żądania, to ją wykorzystuje, nic innego nie sprawdza, -
wybiera najbardziej pasującą spośród dyrektyw tekstowych (tych bez żadnego znaczka i tych z znaczkiem
^~
), przy czym najbardziej pasująca jest ta najdłuższa (jeśli są do wyborulocation /img
orazlocation /imgs
a dopasowujemy/imgs/clock.gif
, wybrana zostanie lokacja/imgs
), zapamiętuje wybór ale jeszcze nie kończy, -
jeśli wybrana dyrektywa używała znaczka
^~
(nie-regexp), używa jej i kończy, -
sprawdza (w kolejności w jakiej występują w pliku konfiguracyjnym) wszystkie dyrektywy zawierające wyrażenie regularne, jeśli którakolwiek pasuje to używa jej (pierwszej pasującej) i na tym kończy,
-
jeśli żadna z dyrektyw z wyrażeniami nie pasowała (albo ich w ogóle nie było), używa najbardziej pasującej z dyrektyw tekstowych.
Brzmi to dość formalnie. W praktyce można myśleć iż nginx sprawdza w kolejności: dyrektywy z
=
, dyrektywy z^~
, dyrektywy z wyrażeniami regularnymi, dyrektywy zwykłe.Jedyna subtelność występuje, gdy mamy coś w stylu:
location ^~ /img/ { # wariant A } location ~ \.png$ { # wariant B } location /img/icons/ { # wariant C }
w takim przypadku dla żądania
/img/icons/clock.png
wybrana zostanie dyrektywa B, bo A przegrało z C konkurs na najwierniejsze dopasowanie, a C jako zwykła dyrektywa tekstowa przegrywa z B. Jest to dosyć sztuczny przypadek, warto go zapamięŧać i ... unikać mieszania trzech stylów zapisu na jednym poziomie.
Po wybraniu właściwego location
jest interpretowana jego treść
(i np. przetwarzane dyrektywy zagnieżdżone).
Rewrite
Nginx ma też silny język przepisywania URLi (dyrektyw rewrite), z czytelną i wygodną składnią. Pełnego podręcznika nie będę tu przepisywał, dam kilka przykładów.
Podobnie jak w Apache, ten sam syntax, zależnie od dodanej flagi,
służy do przekierowań redirect wysyłanych do przeglądarki (flagi
permanent
i redirect
) i do wewnętrznego przepisywania URLi.
Proste trwałe przekierowanie:
rewrite ^/documents/file.zip /download/file.zip permanent;
Inne przekierowanie, tym razem tymczasowe i z użyciem wyrażenia regularnego:
rewrite ^/~(joe|mickey|tom)/(.*) /users/$1/$2 redirect;
Pełnego syntaksu wyrażeń regularnych nie będę tu opisywał (nginx ma ten sam język co python czy perl), najważniejsze elementy to:
- kropka pasuje do dowolnej litery
- gwiazdka oznacza wielokrotne powtarzanie (
.*
to dowolny ciąg znaków,\d*
to ciąg cyfr), przy tym gwiazdka to powtarzanie przynajmniej zero razy (pasuje do napisu pustego), plusik powtarza przynajmniej raz (\d+
to choć jedna cyfra),- nawiasy kwadratowe pozwalają wybierać zbiory znaków, np.
[a-z]
to mała litera a[a-z0-9_]
to mała litera, cyfra lub podkreślenie (i np.[a-z0-9_]+
to słowo złożone z takich znaków),- nawiasy zaznaczają fragmenty do użycia (
$1
to zawartość dopasowana do pierwszego nawiasu,$2
do drugiego itd)
Przykład wewnętrznego przypisania:
rewrite ^/scripts/(jui|site)-\d+/(.*) /scripts/$1/$2;
Tu: tak /scripts/jui-17/base.js
jak /scripts/jui-97/base.js
zostaną przepisane na /scripts/jui/base.js
(technika użyteczna
przy używaniu długotrwałych dyrektyw cacheowania).
Moduł rewrite nginx-a umie dużo więcej, min. można stosować polecenia
if
zależne od różnych charakterystyk żądania, można ustawiać zmienne
pomocnicze, można zerwać przetwarzanie i zwrócić zadany kod błędu.
Patrz dokumentacja. Dość ekscentryczny przykład
jaki swego czasu napisałem, by zrobić redirect z
http://mekk.waw.pl/pgnviewer/ltpgnviewer.html?/wb/pgn/287987.txt
na http://mekk.waw.pl/mk/watchbot/game/287987 wygląda tak:
if ($args ~ \/wb\/pgn\/([0-9]+)\.txt ) { set $game $1; rewrite ^/pgnviewer/ltpgnviewer\.html /mk/watchbot/game/$game? permanent; break; }
(tekst po rewrite to jedna linia, złamałem dla czytelności)
Dodajmy jeszcze, że dyrektywy rewrite
można zagnieżdżać wewnątrz
dyrektyw location
(czyli przepisywać tylko urle już dopasowane
do jakiejś lokacji).
Diagnozowanie problemów
W razie problemów z konfiguracją warto włączyć szczegółowe logowanie. W szczególności:
-
przestawić w dyrektywie
error_log
poziom logowania zinfo
nadebug
lubnotice
-
dodać dyrektywę
rewrite_log on
powodującą drobiazgowy zapis do logu informacji jak przebiegało dopasowywanie dyrektyw.
Czyli np.:
# ... error_log /var/log/nginx/error.log debug; # ... server { # ... rewrite_log on; # ... }
Podsumowanie
W tym odcinku opisałem elementy konfiguracji Nginxa
związane z serwowaniem statycznych plików, a przede wszystkim
starałem się wytłumaczyć jak nginx przetwarza żądanie
wyszukując odpowiednią dla niego dyrektywę location
.
Opis był dość drobiazgowy (w niektórych fragmentach może przesadnie), bo bardzo wiele osób ma kłopoty związane z niezrozumieniem zasad mapowania żądania na lokację.
W następnym odcinku zajmę się tym co ciekawego można
napisać w ramach location
- czyli jak obsłużyć
różne rodzaje dynamicznych serwisów, prawa dostępu itp
itd. Napiszę też trochę o różnych elementach dopieszczania
wydajności.