LNMP – Część 2: Nginx

W połowie czerwca rozpocząłem serię o LNMP – połączeniu Linuksa, nginksa, MySQL (MariaDB) oraz PHP. No właśnie… wtedy się rozpoczęło, minęło tyle czasu, a kontynuacji nie widać. Czas to wreszcie nadrobić. Mam również nadzieję, że wybaczycie mi tak długą zwłokę. Wiecie jak często wygląda lipiec, no a doszedł jeszcze Hot Zlot, w tym roku pod względami przełomowy, tak dla portalu jak i dla mnie :) Zgodnie z obietnicą, drugi odcinek będzie dotyczył serwera www, a więc nginksa. Nie umieszczę tu jednak wszystkich informacji, bo część pojawi się dopiero w odcinku dotyczącym PHP.

Na początek może pewne uwaga. Ostatnio pisałem o repozytoriach, a mianowicie o użyciu Dotdeba. Wspominałem wtedy o PHP 5.5, które wymaga dodania dwóch dodatkowych linijek i nie było stabilne. Od tamtego czasu wersja ta jest już stabilna i śmiało można te linijki dodać. Dzięki pojawieniu się wbudowanego OPcache zmiana jest naprawdę widoczna, ale o szczegółach powiemy dokładnie w dalszych odcinkach.

Wybór wersji i instalacja

Aby mieć do dyspozycji serwer www, musimy go zainstalować. Tutaj sprawa wcale nie jest taka prosta, bo do dyspozycji mamy aż 5 różnych wersji. Dokładne informacje znajdziecie w tym arkuszu, poniżej przedstawiam tylko najważniejsze informacje:

  • nginx

    Podstawowa wersja, zapewniająca obsługę przepisywania adresów (rewrite), fastcgi, auth_basic czy też kompresji. Wystarczająca do większości zastosowań.

  • nginx-light

    Wersja lekka, ukierunkowana na jak najszybsze działanie. Z jednej strony, nie znajdziemy to niektórych modułów z wersji standardowej, ale za to jest obsługa tak SSL jak i rozwijanego przez Google SPDY.

  • nginx-naxsi

    To jest specyficzna wersja, zawierająca w sobie moduł naxsi, który stanowi zaporę aplikacji internetowych (WAF). Dodatkowo znalazła się w nim obsługa odczytywania prawdziwego ip (realip) gdyby było coś jeszcze. Moim zdaniem ta wersja jest użyteczna jako osobny serwer stojący na samym początku i kierujący po sprawdzeniu ruch dalej. Jeżeli czas pozwoli, kwestię naxsi poruszę w dalszych odcinkach.

  • nginx-full

    Wersja poszerzona, zawierająca to co standardowa, a także dodatkowe moduły, np., obsługę geolokalizacji, czy też możliwość utworzenia dostępu poprzez WebDAV.

  • nginx-extras

    Najbardziej bogata w moduły edycja, zawiera to co edycja full, oraz kolejne dodatki, np. pozwalające na wysyłanie zupełnie innych nagłówków. Na dniach ta wersja otrzymała moduł Google Pagespeed pozwalający na automatyczną optymalizację witryn – usuwanie wolnych linii z kodu, łączenie plików CSS/JS, ładowanie obrazków na żądanie itp.

Co wybrać? Na to pytanie niestety musicie odpowiedzieć sobie sami. Jak napisałem, do podstawowych zadań wystarczy wersja podstawowa, lub nawet light, bo jako serwer www spełni swoje zadanie bardzo dobrze. Czego używam ja? Wersji extras, bo mam bzika na punkcie wydajności, a połączenie nginxa z SSL, SPDY i Pagespeedem naprawdę daje niesamowite rezultaty. Jeżeli ktoś nie wie jak instalować, to przypominam (również o tym, że używamy Debiana/Ubuntu zgodnie z założeniami pierwszej części cyklu):

aptitude install nazwa_pakietu

Konfiguracja ogólna

Jaką byśmy wersję nie wybrali, aptitude ustali nam zależności, pozostanie zatwierdzenie i oczekiwanie na zakończenie instalacji. Po tym serwer wystartuje na domyślnym porcie 80 i wbudowanym wirtualnym hoście. Plik konfiguracyjny nginksa znajdziemy w /etc/nginx/nginx.conf, zaś konfiguracje vhostów w /etc/nginx/sites-available. Napisałem tu może zbyt ogólnikowo – pierwszy plik to główna konfiguracja mająca wpływ na całość. Vhosty to obsługa poszczególnych hostów, np. domen. Chociaż nikt nam nie broni, aby wszystko wpakować do jednego, to nie jest to z pewnością rozwiązanie ani eleganckie, ani praktyczne gdy trzeba wyłączyć obsługę tylko jednego adresu. Jak natomiast skonfigurować ogólne ustawienia serwera? To jest kolejne trudne pytanie, bo wszystko zależy od tego, co chcemy zrobić. W poradniku tym zajmiemy się ustawieniami dla niezbyt mocnego VPSa.

worker_processes 1;

To ustawienie odpowiada za ilość procesów nginksa. Dokumentacja mówi, że jeżeli wykonuje on coś bardziej skomplikowanego (np. SSL) to powinniśmy ustawić na ilość dostępnych procesorów/rdzeni.

worker_connections 256;

Maksymalna ilość połączeń do jednego procesu. Mnożąc to przez ilość procesów, otrzymujemy maksymalną teoretyczną ilość klientów możliwych do obsłużenia w jednej chwili. W dalszej części w ramach sekcji http polecam dodać zapis:

server_tokens off;

Wyłącza on dokładne przedstawianie się serwera, a dokładniej wysyłanie wersji. Starczy, że w nagłówkach i tak idzie informacja o nginksie, nie ułatwiajmy potencjalnemu włamywaczowi ataku dając mu do rąk numer wersji. O takich problemach podczas Hot Zlotu wspominał także Paszczak w swojej kapitalnej sesji o podatnościach i atakach. Z domyślnej konfiguracji zalecam również zmianę maksymalnej wielkości wysyłanego żądania od użytkownika:

client_max_body_size 2m;

Ustawienie limitu na 2 megabajty to I tak już naprawdę sporo. Rzadko kiedy wrzuca się większe obrazki, a to one są głównym elementem dodawanym na serwisy czy fora internetowe. Oczywiście o ile potrzebujemy większej wartości, zwiększamy. Dodam tutaj, że np. na potrzeby phpMyAdmina i eksportu z jego poziomu baz danych będzie potrzebna większa wartość, ale nie powinniśmy tego ustawiać w tym miejscu, a dopiero w konkretnym vhoście do jego obsługi.

Na koniec możemy odkomentować fragmenty odpowiedzialne za kompresję po stronie serwera www. Jest ona o wiele bardziej efektywna, niż po stronie PHP i warto to rozwiązać właśnie w taki sposób. Nie zaszkodzi również dołożyć typ MIME od javascriptu, bo domyślnie go brakuje:

gzip on;
gzip_disable "msie6";

gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

Dodatkowa, globalna konfiguracja

Następnie dodajmy nowy plik, który będziemy załączać do każdego vhosta, a zawierający dosyć uniwersalne zapisy. Np. jako /etc/nginx/global.conf, z następującą treścią:

# Ustawianie naglowkow expires
location ~* \.(?:ico|css|js|gif|jpe?g|png)$ 
{
    max;
    access_log off;
    log_not_found off;
}


# Blokada dostepu + wylaczanie logowania
location = /robots.txt  { access_log off; log_not_found off; }
location ~ /\.          { access_log off; log_not_found off; deny all; }
location ~ ~$           { access_log off; log_not_found off; deny all; }
    

# Blokada dostepu do specyficznych danych
location ~ cache/(.*)$
{
    deny all;
}

Pierwsza część odpowiada za wysyłanie nagłówków expires, dzięki którym przeglądarki będą buforowały wskazane treści u siebie. Wrzucamy tam obrazki, javascript, ikony oraz style CSS. Wartość max oznacza, że serwer będzie informował przeglądarki o wygaśnięciu ważności pliku dopiero w 2037 roku. Oczywiście jeżeli często dokonujemy jakichś zmian np. w CSS/JS, to dla tych plików powinniśmy ustawić inną wartość np. jeden dzień. Poza tym, dla tego typu plików wyłączamy log dostępu oraz błędów. Nie są one dla nas istotne, a potrafią zawalić logi serwera.

Druga sekcja to ponownie wyłączenie logów, ale tym razem dla pliku robots.txt (roboty indeksujące próbują go automatycznie pobrać), oraz plików ukrytych. W tym drugim wypadku dodatkowo wyłączamy do nich dostęp, bo nie chcemy aby ktoś go uzyskał.

Ostatnia część jest tylko poglądowa i każdy może ją wykorzystać inaczej. Blokuje ona dostęp do żądań zawierających katalog cache, bo ten jest w wielu skryptach często wykorzystywane do przechowywania danych tymczasowych. Równie dobrze możemy tak zablokować dostęp do np. katalogu z plikami szablonów, to już według upodobań.

Przykładowy vhost dla WordPressa

Na koniec bardzo prosty szablon vhosta, do umieszczeni aw /etc/sites-available/mojadomena, uwzględniający utworzony przez nas plik global.conf, a także z przygotowanym miejscem na konfigurację dla PHP. Zdecydowałem się dodać zapis dla Wodrpessa/Joomli/Drupala, bo są to bardzo często wykorzystywane CMSy.

server {
    server_name www.mojadomena;
    listen 80;

    return 301 http://mojadomena$request_uri;
}

server 
{
    server_name mojadomena;
    listen 80;

    access_log /var/log/nginx/mojadomena_access_log;
    error_log /var/log/nginx/mojadomena_error_log;
    root /home/mojadomena/www;

    # Globalny plik ustawien
    include global.conf;

    # PHP Wrapper
    # Tutaj zalaczymy PHP

    location / 
    {
        try_files $uri $uri/ /index.php?q=$uri&$args;
    }
}

Jak widać, w pliku mamy tak naprawdę dwa vhosty nasłuchujące na porcie 80. Pierwszy z nich to nasza domena z jakże brzydkim przedrostkiem www. Wyrzucamy go przekierowując go (301) na adres bez www i… to całe działanie tego vhosta. Można by użyć if i modułu rewrite, ale to nie jest szybkie rozwiązanie, trzeba zapomnieć o przyzwyczajeniach z plików htaccess z Apache.

Dalej mamy ustawienia logów, oraz głównego katalogu. Ze względu na użycie PHP-FPM i oddzielnych użytkowników zastosowałem właśnie taki zapis – każdy użytkownik ma swój katalog www i tam będziemy wrzucać pliki dostępne z poziomu HTTP. Załączania pliku global i miejsca przygotowanego na PHP nie trzeba chyba tłumaczyć.

Ciekawsza jest ostatnia część. To ona odpowiada za przekierowanie na plik index.php wszystkich żądań jak w domyślnych zapisach htaccess w np. WordPressie. Zapis try_files to coś, co powinno być stosowane zamiast if’a. Powoduje on, że sprawdzane są kolejne wpisane adresy – bezpośrednio to co wpisaliśmy, druga wersja zakończona slashem, a na koniec, o ile dwie poprzednie opcje nie dają rezultatu (brak katalogu i pliku o takiej nazwie), przerzucamy wszystko na plik index.php. Jako że konfiguracja PHP będzie wyżej, nie musimy się tutaj tym przejmować.

Na koniec należałoby włączyć nowego vhosta, możemy to robić podobnie do tego jak w Apache:

# Włączenie
ngxensite mojadomena;  

# Wyłączenie
ngxdissite mojadomena;

Teraz wystarczy zrestartować serwer, ale najpierw zróbmy test, czy cała konfiguracja jest ok. Jeżeli coś jest nie tak, nginx poinformuje nas o tym bez wysypywania się.

# Testowanie
nginx -t

# Restart
service nginx restart

Podsumowanie

To już koniec. Nie zdziwię się, jeżeli będzie sporo pytań, zarzutów o niedopowiedzenia czy nieścisłości. Jak napisałem, nie da się zrobić uniwersalnego przepisu na całość, bo wszystko zależy od tego, co chcemy osiągnąć. Jeżeli macie jakieś pytania, sugestie, lub pomysły zapraszam do komentowania. W dalszej części PHP, ale na sam koniec cyklu postaram się przedstawić jeszcze dodatkowe informacje opcjonalne dla nginksa np. jak zmienić konfigurację, aby poprawnie działać za osłoną Cloudflare, jak aktywować SSL i wspomniane w artykule „przyśpieszacze”.

Zainteresowanym polecam natomiast anglojęzyczną wiki nginksa:http://wiki.nginx.org