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:
(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.
Jedne 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 -
pliki /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 a alias jest podobna jak w przypadku
Apache: alias wykorzystuje resztę URLa pozostałą po dopasowaniu,
a root 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;
}
}
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 wyboru location /img
oraz location /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:
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.
Bardzo fajny tekst, bardzo pomogłeś mi, dzięki i niech Ci Bóg w dzieciach wynagrodzi. ;)
Khmm. W tej formie już wynagrodził (x3)!
Obślizgnął mi się palec i niechcący skasowałem komentarz osoby pytającej o PHP. Będzie w następnym odcinku.
Dzieki za ciekawy blog