LNMP – Część 6: Kilka sztuczek dla nginxa

Ostatnio pisałem o tym, jak naszą konfigurację LNMP doposażyć w bardzo przyjemny skrypt phpMyAdmin w celu wygodniejszego zarządzania niemalże całym serwerem bazodanowym. Na koniec zaznaczyłem, że z tej serii pojawi się jeszcze przynajmniej jeden wpis. Dzisiaj chciałbym się nim podzielić. Postanowiłem w jednym artykule spisać nieco przydanych rozwiązań dla serwera nginx. Pokazują one jego uniwersalność, oraz to, że wcale nie musi koniecznie robić jako dodatek, dostawka dla Apache. Dlaczego nie wykorzystać go jako główny serwer, skoro radzi sobie tak dobrze? Mam nadzieję, że poniższe porady okażą się dla kogoś przydatne.

nginx

Domyślny serwer wirtualny

Na początek zajmiemy się najłatwiejszym elementem, a więc domyślnym serwerem. Możliwe jest, że na IP naszej maszyny będzie wskazywać więcej adresów, nie wszystkie wykorzystywane. Szkoda, aby taki nawet przypadkowy ruch się marnował, zawsze lepiej przerzucić to na jakąś konkretną witrynę. Od tego jest właśnie opcja domyślnego serwera. Tworzymy ją tak samo jak inne vhosty, z tą różnicą, że nie podajemy nazwy, a także oznaczamy całość właśnie jako default_server. Dodatkową różnicą jest wyłączenie server_name_in_redirect, a więc przekazywania nazwy hostu przy przekierowywaniu. Poniżej kod:

server  
{
    server_name _;
    listen 80 default_server;
    server_name_in_redirect off;

    return 301 http://nasza-strona.pl$request_uri;
}

Chronimy SSL przed atakiem BEAST

Jeżeli na jakiejś z witryny używamy szyfrowania, to warto poprawić jego konfigurację w celu zabezpieczenia przed atakiem BEAST. Wymaga to zmiany algorytmów stosowanych przy szyfrowaniu, a zarazem określenia akceptowanych protokołów. Do tego można wymusić, aby używane były właśnie ustawienia serwera, jeżeli klient tego nie spełnia, sam sobie robi krzywdę, więc nie będziemy go dopuszczać do bezpiecznej zawartości. Zmiana jest bardzo prosta, wykonujemy ją na poziomie głównego pliku konfiguracyjnego nginx.conf lub w pliku konfiguracyjnym do niego dołączanym.

ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers RC4:HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

SPDY – będzie szybciej

Skoro już wspomniałem o szyfrowaniu, nie sposób nie wspomnieć o SPDY, bo od jakiegoś czasu jest on wspierany przez nginxa udostępnianego w repozytoriach Dotdeb (dotyczy tylko Debiana w wersji Wheezy). Nie wchodząc tutaj w szczegóły: jest to zaproponowane przez Google, swoiste rozszerzenie protokołu HYYP, które pozwala na zmniejszenie czasu ładowania stron. Dane w nim są szyfrowane (stąd wymóg używania SSL), a zarazem w pełni kompresowane, włącznie z nagłówkami. Obecnie SPDY jest wspierany przez większość przeglądarek, nie licząc Internet Explorera, który obsługuje ten protokół dopiero od najnowszej wersji 11 dostępnej w Windows 8.1. Co prawda jak zaznaczają sami twórcy, moduł SPDY dla nginxa jest póki co w fazie eksperymentalnej, sam jednak nie natknąłem się na żadne problemy i polecam jego użycie. Włączenie jest bardzo proste, wymaga minimalnej zmiany linijki w której wybieramy port dla danego serwera wirtualnego, wystarczy dopisać od niej „ssl spdy”:

listen 443 ssl spdy;

ssl_certificate server.crt;
ssl_certificate_key server.key;

Page Speed – będzie jeszcze szybciej

Idąc dalej, możemy wykorzystać kolejny w wynalazków proponowanych przez Google, jakim jestmoduł Page Speed. Pozwala on na automatyczne optymalizowanie stron internetowych udostępnianych przez nasz serwer www wedle zaleceń samego Google odnoszących się do ich tworzenia. Moduł jest zupełnie darmowy, jakiś czas temu znalazł się również w nginxie z Dotdeba, więc można go spokojnie wykorzystać. Warunek: musimy korzystać z najbogatszej wersji nginx-extras, bo tylko ona jest w niego wyposażona. Samo uruchomienie nie stanowi większego problemu, tworzymy odpowiedniego katalog:

mkdir –p /var/ngx_pagespeed_cache
chmod 777 /var/ngx_pagespeed_cache

A następnie dodajemy w głównym pliku pliku konfiguracyjnym nginx.conf:

pagespeed on;
pagespeed FileCachePath /var/ngx_pagespeed_cache;
pagespeed CreateSharedMemoryMetadataCache "/var/ngx_pagespeed_cache" 102400;

Następnie wystarczy dodać odpowiednie zapisy w plikach wirtualnych serwerów. Najlepiej w tym celu zapoznać się z dokumentacją techniczną Page Speed, w niej znajdziemy dokładnie informacje włącznie z tym, jak np. połączyć jego działanie z memcached w celu jeszcze większej wydajności. Podstawowe przykładowe reguły wyglądają następująco:

# Filtry Page Speed
pagespeed RewriteLevel CoreFilters;
pagespeed EnableFilters collapse_whitespace,remove_comments,trim_urls,insert_image_dimensions;
pagespeed Domain http://moja-domena.pl;

#location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+" { add_header "" ""; }
#location ~ "^/ngx_pagespeed_static/" { }
#location ~ "^/ngx_pagespeed_beacon$" { }
#location /ngx_pagespeed_statistics { allow  DOZWOLONE_IP; deny all; }
#location /ngx_pagespeed_message { allow  DOZWOLONE_IP; deny all; }
#location /pagespeed_console { allow  DOZWOLONE_IP; deny all; }

Trzy pierwsze linijki oznaczają uruchomienie podstawowych reguł, a także wybranych filtrów, których działanie powinny tłumaczyć już same ich nazwy np. usuwanie zbędnych spacji, komentarzy HTML, dodawanie wymiarów do znaczników obrazków w celu odpowiedniego ładowania witryny. Dalsze są przeznaczone do celów statystycznych, bo pod odpowiednim adresem możemy obserwować informacje o tym, jak działa Page Speed. Jak widać, zalecane jest ustawienie dostępu tylko dla wybranych adresów IP.

CloudFlare i Incapsula

Wiele witryn używa obecnie usług typu CDN (Content Delivery Network) lub w celu zwiększenia ochrony przed atakami spamowymi i DDoS „chowa się” za odpowiednimi dostawcami. Przykładami takich usług jest CloudFlare, z którego używania swojego czasu dzieliłem się wrażeniami, a także mniej znana, ale równie albo i bardziej warta uwagi Incapsula. W wypadku stosowania tego typu rozwiązań musimy dokonać korekty w konfiguracjach wirtualnych serwerów (najlepiej wydzielić to do osobnego pliku) aby ustawiany był poprawny adres IP. Pamiętajmy, że użytkownicy łącząc się przykładowo przez CloudFlare są rozpoznawani jako osoby z IP serwerów właśnie tej firmy. Kilka automatycznych blokad np. z poziomu skryptów PHP i możemy zablokować dostęp samemu sobie, poza tym nie mamy żadnych prawdziwych informacji. Aby to poprawić, potrzebne jest użycie zapisu set_real_ip_from:

# Cloudflare
set_real_ip_from 204.93.240.0/24;
set_real_ip_from 204.93.177.0/24;
set_real_ip_from 199.27.128.0/21;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
real_ip_header CF-Connecting-IP;

# Incapsula
set_real_ip_from 199.83.128.0/21;
set_real_ip_from 198.143.32.0/19;
set_real_ip_from 149.126.72.0/21;
set_real_ip_from 103.28.248.0/22;
set_real_ip_from 185.11.124.0/22;
real_ip_header X-Forwarded-For;

Statystyki pracy nginx

Wyżej podawałem przykład statystyk dla Page Speed. Okazuje się jednak, że sam serwer www jakim jest nginx udostępnia taką witrynę. Wystarczy w kontekście odpowiedniej lokalizacji włączyć ją zapisem stub_status, zrestartować serwer, a następnie przejść na wymienioną stronę. Na niej dowiemy się o ilości aktywnych w danym momencie połączeń, ilości odczytywanych żądań, dokonywanych odpowiedzi, a także połączeń oczekujących w trybie keep-alive. Oczywiście dostęp do takich danych powinien być chroniony, albo przez autoryzację http, albo przez dostęp tylko z określonych adresów IP:

location /nginx_status 
{
    stub_status on;
    access_log   off;
    allow DOWZWOLONE_IP;
    deny all;
}

X-Accel – serwowanie treści chronionej

Czasami bywa, że chcemy niektóre treści, np. pliki, udostępnić tylko użytkownikom z odpowiednimi uprawnieniami. Nie zawsze chodzi o autoryzację http, bo ta wymaga podawania im osobnych zestawów login-hasło. Co zrobić w sytuacji, gdy to np. w bazie danych znajdują się odpowiednie informacje o dostępie, a kontrola odbywa się poprzez skrypt PHP? Możemy oczywiście spróbować serwować te pliki przez PHP, jeżeli są niewielkie, to nie ma problemu. Co jednak w sytuacji, gdy są to spore archiwa? Ich pobieranie będzie konsumowało ogromne ilości pamięci PHP, procesy będą wisieć, a pobieranie raczej i tak nie zakończy się poprawnie z powodu przekroczenia czasu wykonywania skryptu. Tutaj z ratunkiem przychodzi nagłówek X-Accel – może on zostać wysłany np. przez PHP do serwera nginx aby ten wiedział, że ma udostępnić odpowiednie zasoby. Najpierw trzeba w wirtualnym serwerze przygotować odpowiedni zapis informujący o chronionych zasobach:

location /super-tajne 
{
    alias /home/tajniacy/pliki/;
    internal;
}

Po próbie bezpośredniego wejścia pod katalog super-tajne użytkownik otrzyma informację o braku dostępu. Teraz wystarczy w odpowiednim miejscu naszego skryptu PHP przesłać odpowiednie nagłówki, które otworzą nam drzwi sezamu z plikami. Zamieszczam umowny, przykłady zapis – sprawdzanie czy możemy pozwolić, a następnie podanie w nagłówku X-Accel ścieżki a w innym nazwy pliku:

if ($allowed)
{
    header("X-Accel-Redirect: {$path}");
    header('Content-type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . $file . '"');
    exit;
}

Strony błędów

Na koniec niewielka, acz może przydatna rzecz. Dzięki niewielkiemu zapisowi dołączanemu do wszystkich stron (np. w postaci osobnego pliku konfiguracyjnemu) możemy szybko stworzyć swoisty schemat obsługi stron błędów. Załóżmy, że każda witryna ma katalog error, a w nim zgodnie z kodami odpowiednie pliki html z przyjaznymi stronami informującymi np. o tym, że strona nie istnieje w wypadku błędu 404. Możemy to osiągnąć w bardzo prosty sposób, a gdyby nawet witryna plików takich nie zawierała, to wyświetlony zostanie domyślny komunikat. Poniższy przykład przewiduje wyświetlanie dokładnie tej samej strony dla błędów typu 404 (brak strony) oraz 403 (brak uprawnień):

error_page 403 404 = @error404;

location @error404 {
    try_files /error/404.html =404; 
}

Podsumowanie

Oczywiście po przeprowadzaniu zmian restartujemy proces nginxa (wcześniej koniecznie wykonując test konfiguracji!). To by było chyba tyle na dzisiaj. Jeżeli sami macie jakieś propozycje „sztuczek” dla nginxa, podzielcie się z nimi – jeżeli się zgodzicie, mogę je tutaj wtedy dopisać, aby stworzyć większy ich zbiór w jednym miejscu. Czy z całej serii będzie coś jeszcze? Możliwe że tak, w komentarzach pod poprzednim odcinkiem pojawiły się propozycje opisywania Varnisha, czy też konfiguracji serwera pocztowego. Nie wiem, czy zdecyduję się akurat na nie, ale kilka pomysłów, które mogłyby kogoś zainteresować chodzi mi aktualnie po głowie. Miłego grzebania w konfiguracji i dokumentacji :)