Note: I am talking about a component previously in Zend Server, but is now a standalone module and [urlspan]open-sourced[/urlspan], and will be bundle with [urlspan]PHP 5.5[/urlspan], it has performance/usability edge over the more widely used APC.

Now here is my problem, I cannot seem to get Zend Optimizer+ work properly with my codeigniter-powered (PHP framework) site, my debug config is as following:

zend_optimizerplus.enable=1
zend_optimizerplus.memory_consumption=128
zend_optimizerplus.interned_strings_buffer=8
zend_optimizerplus.max_accelerated_files=4000
zend_optimizerplus.revalidate_freq=10
zend_optimizerplus.fast_shutdown=1
zend_optimizerplus.enable_cli=1
zend_optimizerplus.optimization_level=0
zend_optimizerplus.error_log=/var/log/zendop.log

Once ZO+ is enabled, I started to see php-fpm logs such as these (but nothing else, no segfault error, no entries in ZO+ error log):

[13-Mar-2013 00:58:45] WARNING: [pool www] child 6734 exited with code 1 after 176.326628 seconds from start
[13-Mar-2013 00:58:45] NOTICE: [pool www] child 6761 started

Basically php worker process were stopped and restarted. And all page appear to be outputing only partially. I turned on php error display with error_reporting(E_ALL & ~E_NOTICE); but still don't see any error from PHP itself.

All I can find in Nginx log were entries like:

2013/03/13 00:22:47 [error] 1761#0: *14 readv() failed (104: Connection reset by peer) while reading response header from upstream, client: [my ip address], server: [my server name], request: "GET /leave HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: [my host name].
2013/03/13 00:22:47 [error] 1761#0: *17 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: [my ip address], server: [my server name], request: "GET /leave HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: [my host name].

I can't seem to find much insight regarding what were happening, and I can confirm the site works without ZO+ fine, and ZO+ source appear to compile and install correctly (make test also passed correctly, though there aren't many tests to run).

Anyone had experience with Zend Optimizer+? I am surprised to see no mention of soon-to-be PHP-bundled module anywhere on this site.

php -v

PHP 5.3.10-1ubuntu3.5 with Suhosin-Patch (cli) (built: Jan 18 2013 23:40:19)
Copyright (c) 1997-2012 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2012 Zend Technologies

nginx -v

nginx version: nginx/1.1.19

(If this post belongs to stackoverflow, please help to migrate, though I am not seeing a programming-related error here.)

В web-сервер и reverse-proxy nginx встроены очень мощные возможности по кэшированию HTTP-ответов. Однако в ряде случаев документации и примеров не хватает, в результате не все получается так легко и просто, как хотелось бы. Например, мои конфиги nginx-а местами написаны кровью. Этой статьей я попробую немного улучшить ситуацию.

В этой статье: а) подводные камни при полностраничном кэшировании; б) кэширование с ротацией; в) создание динамического «окна» в закэшированной странице.

Я буду предполагать, что вы используете связку nginx+fastcgi_php. Если вы применяете nginx+apache+mod_php, просто замените имена директив с fastcgi_cache* на proxy_cache*

Если выбирать, кэшировать ли страницу на стороне PHP или на стороне nginx, я выбираю nginx. Во-первых, это позволяет отдавать 5-10 тыс. запросов в секунду без каких-либо сложностей и без умных разговоров о «высокой нагрузке». Во-вторых, nginx самостоятельно следит за размером кэша и чистит его как при устаревании, так и при вытеснении нечасто используемых данных.

Кэширование всей страницы целиком

Если на вашем сайте главная страница хоть и генерируется динамически, но меняется достаточно редко, можно сильно снизить нагрузку на сервер, закэшировав ее в nginx. При высокой посещаемости даже кэширование на короткий срок (5 минут и меньше) уже дает огромный прирост в производительности, ведь кэш работает очень быстро. Даже закэшировав страницу всего на 30 секунд, вы все равно добьетесь значительной разгрузки сервера, сохранив при этом динамичность обновления данных (во многих случаях обновления раз в 30 секунд вполне достаточно).

Например, закэшировать главную страницу можно так:

fastcgi_cache_path /var/cache/nginx levels= keys_zone=wholepage:50m;
...
server {
  ...
  location / {
    ...
    fastcgi_pass 127.0.0.1:9000;
    ...
    # Включаем кэширование и тщательно выбираем ключ кэша.
    fastcgi_cache wholepage;
    fastcgi_cache_valid 200 301 302 304 5m;
    fastcgi_cache_key "$request_method|$http_if_modified_since|$http_if_none_match|$host|$request_uri";
    # Гарантируем, что разные пользователи не получат одну и ту же сессионную Cookie.
    fastcgi_hide_header "Set-Cookie";
    # Заставляем nginx кэшировать страницу в любом случае, независимо от
    # заголовков кэширования, выставляемых в PHP.
    fastcgi_ignore_headers "Cache-Control" "Expires";
  }
}

Я не сильно преувеличу, если скажу, что каждая строчка в этом конфиге написана кровью. Здесь много подводных камней, давайте их все рассмотрим.

fastcgi_cache_path: простота отладки тоже важна

fastcgi_cache_path /var/cache/nginx levels= keys_zone=wholepage:50m;

В директиве fastcgi_cache_path я выставляю «пустое» значение для levels. Хотя это немного снижает производительность (файлы будут напрямую создаваться в /var/cache/nginx, без разбиения по директориям), но зато на порядок облегчает отладку и диагностику проблем с кэшем. Поверьте, вам еще не раз придется руками залезать в /var/cache/nginx и смотреть, что там хранится.

fastcgi_cache_valid: кэшируем код ответа 304 тоже

fastcgi_cache_valid 200 301 302 304 5m;

В директиве fastcgi_cache_valid мы заставляем кэшировать не только стандартные коды 200 ОК, 301 Moved Permanently и 302 Found, но также и 304 Not Modified. Почему? Давайте вспомним, что означает 304. Он выдается с пустым телом ответа в двух случаях:

  • Если браузер послал заголовок «If-Modified-Since: date», в котором date больше либо равна значению заголовка ответа «Last-Modified: date». Т.е. клиент спрашивает: «Есть ли новая версия с момента date? Если нет, верни мне 304 и сэкономь трафик. Если есть, отдай мне тело страницы».
  • Если браузер послал заголовок «If-None-Match: hash», где hash совапдает со значением заголовка ответа «ETag: hash». Т.е. клиент спрашивает: «Отличается ли текущая версия страницы от той, что я запросил в прошлый раз? Если нет, верни мне 304 и сэкономь трафик. Если да, отдай тело страницы».

В обоих случаях Last-Modified или ETag будут взяты, скорее всего, из кэша nginx, и проверка пройдет очень быстро. Нам незачем «дергать» PHP только для того, чтобы скрипт выдал эти заголовки, особенно в свете того, что клиентам, которым уйдет ответ 200, он будет отдан из кэша.

fastcgi_cache_key: внимательно работаем с зависимостями

fastcgi_cache_key «$request_method|$http_if_modified_since|$http_if_none_match|$host|$request_uri»;

Особого внимания заслуживает значение в директиве fastcgi_cache_key. Я привел минимальное рабочее значение этой директивы. Шаг вправо, шаг влево, и вы начнете в ряде случаев получать «неправильные» данные из кэша. Итак:

  • Зависимость от $request_method нам нужна, т.к. HEAD-запросы в Интернете довольно часты. Ответ на HEAD-запрос никогда не содержит тела. Если убрать зависимость от $request_method, то может так совпасть, что кто-то до вас запросил главную страницу HEAD-методом, а вам потом по GET отдастся пустой контент.
  • Зависимость от $http_if_modified_since нужна для того, чтобы кэш с ответом 304 Not Modified не был случайно отдан клиенту, делающему обычный GET-запрос. Иначе клиент может получить пустой ответ из кэша.
  • То же самое и с $http_if_none_match. Мы должны быть застрахованы от выдачи пустых страниц клиентам!
  • Наконец, зависимость от $host и $request_uri не требует комментариев.
fastcgi_hide_header: решаем проблемы с безопасностью

fastcgi_hide_header «Set-Cookie»;

Директива fastcgi_hide_header очень важна. Без нее вы серьезно рискуете безопасностью: пользователи могут получить чужие сессии через сессионную Cookie в кэше. (Правда, в последних версиях nginx что-то было сделано в сторону автоматического учета данного фактора.) Понимаете, как это происходит? На сайт зашел Вася Пупкин, ему выдалась сессия и сессионная Cookie. Пусть кэш на тот момент оказался пустым, и в него записалась Васина Cookie. Затем пришел другой пользователь, получил ответ из кэша, а в нем — и Cookie Васи. А значит, и его сессию тоже.

Можно, конечно, сказать: давайте не будем вызывать session_start () на главной странице, тогда и с Cookies проблем не будет. В теории это так, но на практике данный способ очень неустойчив. Сессии часто стартуют «отложено», и достаточно какой-либо части кода «случайно» вызвать функцию, требующую доступа к сессии, как мы получим дыру в безопасности. А безопасность — такая штука, что если в той или иной методике может возникнуть дыра по неосторожности, то эта методика считается «дырявой» по определению. К тому же есть и другие Cookies, кроме сессионной; их тоже не надо записывать в кэш.

fastcgi_ignore_headers: не даем сайту «лечь» от нагрузки при опечатке

fastcgi_ignore_headers «Cache-Control» «Expires»;

Сервер nginx обращает внимание на заголовки Cache-Control, Expires и Pragma, которые выдает PHP. Если в них сказано, что страницу не нужно кэшировать (либо что она уже устарела), то nginx не записывает ее в кэш-файл. Это поведение, хотя и кажется логичным, на практике порождает массу сложностей. Поэтому мы его блокируем: благодаря fastcgi_ignore_headers в кэш-файлы попадет содержимое любой страницы, независимо от ее заголовков.

Что же это за сложности? Они опять связаны с сессиями и функцией session_start (), которая в PHP по умолчанию выставляет заголовки «Cache-Control: no-cache» и «Pragma: no-cache». Здесь существует три решения проблемы:

  • Не пользоваться session_start () на странице, где предполагается кэширование. Один из минусов этого способа мы уже рассмотрели выше: достаточно одного неосторожного движения, и ваш сайт, принимающий тысячи запросов в секунду на закэшированную главную страницу, моментально «ляжет», когда кэш отключится. Второй минус — нам придется управлять логикой кэширования в двух местах: в конфиге nginx и в PHP-коде. Т.е. эта логика окажется «размазанной» по совершенно разным частям системы.
  • Выставить ini_set ('session.cache_limiter', ''). Это заставит PHP запретить вывод каких-либо заголовков, ограничивающих кэширование при работе с сессиями. Проблема здесь та же: «размазанность» логики кэширования, ведь в идеале мы бы хотели, чтобы все кэширование управлялось из единого места.
  • Игнорировать заголовки запрета кэширования при записи в кэш-файлы при помощи fastcgi_ignore_headers. Кажется, это беспроигрышное решение, поэтому я его и советую.

 

Кэширование с ротацией

Статическая главная страница — это не так уж и интересно. Что делать, если на сайте много материалов, а Главная выступает в роли своеобразной «витрины» для них? На такой «витрине» удобно отображать «случайные» материалы, чтобы разные пользователи видели разное (и даже один пользователь получал новый контент, перезагрузив страницу в браузере).

Решение задачи — кэширование с ротацией:

  1. Мы заставляем скрипт честно выдавать элементы главной странице в случайном порядке, выполняя необходимые запросы в базу данных (пусть это и медленно).
  2. Затем мы сохраняем в кэше не одну, а, скажем, 10 вариантов страницы.
  3. Когда пользователь заходит на сайт, мы показываем ему один из этих вариантов. При этом, если кэш пуст, то запускается скрипт, а если нет, то результат возвращается из кэша.
  4. Устанавливаем время устаревания кэша малым (например, 1 минута), чтобы за день разные пользователи «отсмотрели» все материалы сайта.

В итоге первые 10 запросов к скрипту-генератору выполнятся «честно» и «нагрузят» сервер. Зато потом они «осядут» в кэше и в течение минуты будут выдаваться уже быстро. Прирост производительности тем больше, чем больше посетителей на сайте.

Вот кусочек конфига nginx, реализующий кэширование с ротацией:

fastcgi_cache_path /var/cache/nginx levels= keys_zone=wholepage:50m;
perl_set $rand 'sub { return int rand 10 }';
...
server {
  ...
  location / {
    ...
    fastcgi_pass 127.0.0.1:9000;
    ...
    # Включаем кэширование и тщательно выбираем ключ кэша.
    fastcgi_cache wholepage;
    fastcgi_cache_valid 200 301 302 304 1m;
    fastcgi_cache_key "$rand|$request_method|$http_if_modified_since|$http_if_none_match|$host|$request_uri";
    # Гарантируем, что разные пользователи не получат одну и ту же сессионную Cookie.
    fastcgi_hide_header "Set-Cookie";
    # Заставляем nginx кэшировать страницу в любом случае, независимо от
    # заголовков кэширования, выставляемых в PHP.
    fastcgi_ignore_headers "Cache-Control" "Expires";

    # Заставляем браузер каждый раз перезагружать страницу (для ротации).
    fastcgi_hide_header "Cache-Control";
    add_header Cache-Control "no-store, no-cache, must-revalidate, post-check=0, pre-check=0";
    fastcgi_hide_header "Pragma";
    add_header Pragma "no-cache";
    # Выдаем всегда свежий Last-Modified.
    expires -1; # Внимание!!! Эта строка expires необходима!
    add_header Last-Modified $sent_http_Expires;
  }
}

Вы можете заметить, что по сравнению с предыдущим примером мне пришлось добавить еще 6 директив в location. Они все очень важные! Но не будем забегать вперед, рассмотрим все по порядку.

perl_set: зависимость-рандомизатор

perl_set $rand 'sub { return int rand 10 }';

С директивой perl_set все просто. Мы создаем переменную, при использовании которой nginx будет вызывать функцию встроенного в него Perl-интерпретатора. По словам автора nginx, это достаточно быстрая операция, так что мы не будем «экономить на спичках». Переменная принимает случайное значение от 0 до 9 в каждом из HTTP-запросов.

fastcgi_cache_key: зависимость от рандомизатора

fastcgi_cache_key «$rand|$request_method|...»;

Теперь мы замешиваем переменную-рандомизатор в ключ кэша. В итоге получается 10 разных кэшей на один и тот же URL, что нам и требовалось. Благодаря тому, что скрипт, вызываемый при кэш-промахе, выдает элементы главной страницы в случайном порядке, мы получаем 10 разновидностей главной страницы, каждая из которой «живет» 1 минуту (см. fastcgi_cache_valid).

add_header: принудительно выключаем браузерный кэш

 

fastcgi_hide_header "Cache-Control";
add_header Cache-Control "no-store, no-cache, must-revalidate, post-check=0, pre-check=0";
fastcgi_hide_header "Pragma";
add_header Pragma "no-cache";

Выше мы говорили, что nginx чувствителен к кэш-заголовкам, выдаваемым PHP-скриптом. Если PHP-скрипт возвращает заголовки «Pragma: no-cache» или «Cache-Control: no-store» (а также еще некоторые, например, «Cache-Control: не-сохранять, не-выдавать, меня-тут-не-было, я-этого-не-говорил, чья-это-шляпа»), то nginx не будет сохранять результат в кэш-файлах. Специально чтобы подавить такое его поведение, мы используем fastcgi_ignore_headers (см. выше).

Чем отличается «Pragma: no-cache» от «Cache-Control: no-cache»? Только тем, что Pragma — наследие HTTP/1.0 и сейчас поддерживается для совместимости со старыми браузерами. В HTTP/1.1 используется Cache-Control.

Однако есть еще кэш в браузере. И в некоторых случаях браузер может даже не пытаться делать запрос на сервер, чтобы отобразить страницу; вместо этого он достанет ее из собственного кэша. Т.к. у нас ротация, нам такое поведение неудобно: ведь каждый раз, заходя на страницу, пользователь должен видеть новые данные. (На самом деле, если вы все же хотите закэшировать какой-нибудь один вариант, то можно поэкспериментировать с заголовком Cache-Control.)

Директива add_header как раз и передает в браузер заголовок запрета кэширования. Ну а чтобы этот заголовок случайно не размножился, мы вначале убираем из HTTP-ответа то, что записал туда PHP-скрипт (и то, что записалось в nginx-кэш): директива fastcgi_hide_header. Ведь вы, когда пишете конфиг nginx-а, не знаете, что там надумает выводить PHP (а если используется session_start (), то он точно надумает). Вдруг он выставит свой собственный заголовок Cache-Control? Тогда их будет два: PHP-шный и добавленный нами через add_header.

expires и Last-Modified: гарантируем перезагрузку страницы

 

expires -1; # Внимание!!! Эта строка expires необходима!
add_header Last-Modified $sent_http_Expires;

Еще один трюк: мы должны выставить Last-Modified равным текущему времени. К сожалению, в nginx нет переменной, хранящей текущее время, однако она магическим образом появляется, если указать директиву expires -1.

Хотя это сейчас (октябрь 2009 г.) не задокументировано, nginx создает переменные вида $sent_http_XXX для каждого заголовка ответа XXX, отданного клиенту. Одной из них мы и пользуемся.

Почему же так важно выставлять текущим временем этот заголовок? Все довольно просто.

  1. Давайте представим, что PHP выдал заголовок «Last-Modified: некоторая_дата».
  2. Данный заголовок будет записан в кэш-файл nginx (можете проверить: в нашем примере файлы хранятся в /var/cache/nginx), а потом отдан в браузер клиенту.
  3. Браузер запомнит страницу и дату ее модификации...
  4. … поэтому при следующем заходе пользователя на сайт в HTTP-запросе будет заголовок-вопрос «If-Modified-Since: некоторая_дата».
  5. Что же сделает nginx? Он достанет страницу из своего кэша, разберет ее заголовки и сравнит Last-Modified с If-Modified-Since. Если значения совпадут (или первое окажется меньше второго), то nginx вернет ответ «304 Not Modified» с пустым телом. И пользователь не увидит никакой ротации: он получит то, что уже видел раньше.

На самом деле, большой вопрос, как поведет себя браузер при наличии одновременно Last-Modified и Cache-Control no-cache. Будет ли он делать запрос If-Modified-Since? Кажется, что разные браузеры ведут тут себя по-разному. Экспериментируйте.

Есть и еще один повод выставлять Last-Modified вручную. Дело в том, что PHP-функция session_start () принудительно выдает заголовок Last-Modified, но указывает в нем… время изменения PHP-файла, который первый получил управление. Следовательно, если у вас на сайте все запросы идут на один и тот же скрипт (Front Controller), то ваша Last-Modified будет почти всегда равна времени изменения этого единственного скрипта, что совершенно не верно.

Динамическое «окно» в закэшированной странице

Ну и напоследок упомяну одну технику, которая может быть полезна в свете кэширования. Если вам хочется закэшировать главную (или любую другую) страницу сайта, однако мешает один маленький блок, который обязательно должен быть динамическим, воспользуйтесь модулем для работы с SSI.

В ту часть страницы, которая должна быть динамической, вставьте вот такой «HTML-комментарий»:

<!--# include virtual="/get_user_info/" -->

С точки зрения кэша nginx данный комментарий — обычный текст. Он будет сохранен в кэш-файле именно в виде комментария. Однако позже, при прочтения кэша, сработает модуль SSI nginx, который обратится к динамическому URL. Конечно, по адресу /get_user_info/ должен быть PHP-обработчик, который выдает содержимое данного блока. Более подробно данный способ описан в этой статье с Хабра.

Ну и, естественно, не забудьте включить SSI для этой страницы или даже для всего сервера:

ssi on;

Директива SSI include имеет еще одно, крайне важное свойство. Когда на странице встречаются несколько таких директив, то все они начинают обрабатываться одновременно, в параллельном режиме. Так что, если у вас на странице 4 блока, каждый из которых загружается 200мс, в сумме страница будет получена пользователем через 200мс, а не через 800.

Исходный текст данной статьи можно прочитать тут: http://dklab.ru/chicken/nablas/56.html

В данной статье я приведу пример конфигурации nginx для кеширования FastCGI-запросов. При желании его можно использовать его для защиты от хабраэффекта, частично от DDoS'а и, как вариант, для облегчения жизни сервера с высокой нагрузкой.

В секции «http» объявляем кеш-зону fastcgi_cache, с хранением кеша в папке /tmp/nginx с 2 уровнями вложенности, с максимальным размером 256Мб и кешем ключей 16Мб (неиспользуемые более 1 дня объекты будут автоматически удаляться):

fastcgi_cache_path /tmp/nginx/ levels=1:2 keys_zone=fastcgi_cache:16m max_size=256m inactive=1d;

Далее, в секции «server» правим соответствующий location:

location ~ .php$ {
            # Стандартная конфигурация для php
            fastcgi_pass   unix:/tmp/php-fcgi.sock;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  /usr/local/www/somedir/$fastcgi_script_name;
            include        fastcgi_params;
            fastcgi_param  DOCUMENT_ROOT /usr/local/www/somedir/;

            fastcgi_pass_header Cookie; # Необходимо для передачи cookie в соответствующие переменные, например cookie с именем phpsessid будет находится в переменной $cookie_phpsessid

            fastcgi_ignore_headers Cache-Control Expires Set-Cookie; # Игнорируем заголовки, относящиеся к кешированию, полученные от FastCGI-сервера

            fastcgi_cache_key "$server_addr:$server_port$request_uri|$cookie_phpsessid"; # Формируем уникальный ключ; в данном случае различаем пользователей с помощью $cookie_phpsessid

            fastcgi_cache fastcgi_cache; # Говорим о том, что использовать надо вышеобъявленную кеш-зону fastcgi_cache

            fastcgi_temp_path  /tmp/nginx/temp 1 2; # Указываем папку для хранения временных файлов

            fastcgi_cache_use_stale updating error timeout invalid_header http_500; # Используем вариант из кеша (даже если он устарел) в случае ошибки

            fastcgi_cache_valid 10s; # Время жизни кеша для ответов 200, 301 & 302
            #fastcgi_cache_valid any 10s; # Таким образом можно закешировать любые ответы
            }

Конфиг опробован на nginx версии >= 0.7.60 (на => 0.8.1 должен работать, но не тестировался).

Документация: Директивы модуля ngx_http_fastcgi_module, Changelog.

upd: Если не игнорировать заголовки Cache-Control и Expires, nginx ведёт себя соответственно их содержанию (что, в принципе, логично).

upd/28.01.2011: добавлен параметр Set-Cookie к fastcgi_ignore_headers (начиная с nginx/0.8.44)

upd/07.02.2011: необходимо реализовать отделение поисковых краулеров, т.к. при игнорировании cookies могут выкинуть из выдачи

Импорт необходимых PGP ключей:

 wget -O - http://nginx.org/keys/nginx_signing.key|apt-key add  -

Для добавления репозиториев nginx необходимо вписать в конец файла /etc/apt/sources.list

deb http://nginx.org/packages/debian/ wheezy nginx
deb-src http://nginx.org/packages/debian/ wheezy nginx

>или выполнить в командной строке

echo "#Rep NGINX">> /etc/apt/sources.list
echo deb http://nginx.org/packages/debian/ wheezy nginx>> /etc/apt/sources.list
echo deb-src http://nginx.org/packages/debian/ wheezy nginx>> /etc/apt/sources.list

Обновляем репозитарии

 apt-get update

Приступаем к установке

apt-get install nginx php5 php5-curl php5-cgi php5-fpm fcgiwrap php5-gd memcached php5-memcached siege php5-cli php5-common  mysql-server php5-mysql php5-suhosin
  • nginx — сам веб сервер.
  • php5-cli php5-common php5-sqlite  php5-cgi php5-fpm php5-gd — некоторые нужные для работы движков модули php.
  • mysql-server php5-mysql — база данных mysql и связь ее с php.
  • fcgiwrap — обработка perl скриптов.
  • php5-apc memcached php5-memcached — ускорители работы веб сервера.
  • siege — утилита для тестирования скорости работы сайта, понадобится при подборе количества обработчиков.
  • php5-suhosin — патч для PHP  предназначенный для повышения защиты сервера от действий злоумышленника.

Проверим версию установленного NGINX

nginx -v
 nginx version: nginx/1.6.2

Создаем каталог для сайтов и логов, также устанавливаем права

mkdir -p /usr/local/hosting/www && chmod -R a-rwx,u+rwX,g+rX /usr/local/hosting/www && chown www-data:www-data -R /usr/local/hosting/www
#Дирректория для кеша  mkdir /tmp/fcgi-cache/ && chown www-data:www-data -R /tmp/fcgi-cache/

 

2. Настройка Nginx

Несмотря на то, что конфигурация Nginx состоит из нескольких файлов, сам nginx начинает читать единственный файл: /etc/nginx/nginx.conf, все остальные подключаются директивой include.

Редактируем /etc/nginx/nginx.conf

# Пользователь с правами которого работает nginx
user www-data;
# Рекомендуется устанавливать по числу ядер
worker_processes 4;
pid /var/run/nginx.pid;
#worker_rlimit_nofile 8192;
events {
# Максимальное число подключений к серверу на один worker-процесс
worker_connections 1024;
# Эффективный метод обработки соединений, используемый в Linux 2.6+
use epoll;
}
http {
##
# Базовые настройки
    #Организовываем кеш для FastCGI сервера, я использую раздел в ram
    fastcgi_cache_path /tmp/fcgi-cache/ levels=1:2   keys_zone=one:10m;
    #Используем sendfile, но осторожно, если надо отдавать большие файлы,
    #то sendfile случается вредит
    sendfile on;
    #Расширяем буфера отдачи
    #output_buffers   32 512k;
    #Ограничиваем размер сегмента отправляемой за одну
    #блокируемую отдачу
    sendfile_max_chunk  128k;
    #Буфер отдачи которы используется для обрабатываемых данных
    postpone_output  1460;
    #Размер хеша для доменных имен.
    server_names_hash_bucket_size 64;
    #Размер данных принемаемых post запросом
    client_max_body_size 15m;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        # При ошибках не говорим врагу версию nginx
        server_tokens off;
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
##
# Настройка логов
   log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;
##

# Настройки сжатия
     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;
    ssi on;
##
# Настройка виртуальных доменов
        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}

Настройка виртуального домена 

Создаем директории для доменов и шаблонов

mkdir /etc/nginx/sites-enabled
mkdir /etc/nginx/sites-available
mkdir /etc/nginx/templates
mkdir /var/www/htdocs

Настройка шаблонов. 

Общий шаблон
mcedit  /etc/nginx/templates/default

# Типовые настройки общие для всех доменов (если не захочется экзотики)
##
index index.html index.php;
# Реализуем "красивые" ссылки для Drupal (и для ряда других CMS)
location / {
try_files $uri $uri/ /index.php?q=$uri&$args;
}
# Закрываем доступ к файлами .htaccess и .htpassword
location ~ /.ht {
deny all;
}
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}

Шаблон подключения обработки php
mcedit  /etc/nginx/templates/php

# Передаём обработку PHP-скриптов PHP-FPM
location ~ .php$ {
try_files $uri =404;
#PHP-FPM слушает на Unix сокете
#fastcgi_pass   unix:/tmp/wwwpool.sock;
fastcgi_pass   unix:/var/run/php5-fpm.sock;
#Использовать cache зона one
fastcgi_cache  one;
#Помещать страницу в кеш, после 3-х использований. Меньшее число вызвало у меня труднообъяснимые глюки
# на формах регистрации
fastcgi_cache_min_uses 3;
#Кешировать перечисленные ответы
fastcgi_cache_valid 200 301 302 304 5m;
#Формат ключа кеша - по этому ключу nginx находит правильную страничку
fastcgi_cache_key "$request_method|$host|$request_uri";
#Если не использовать эту опцию - то в форумах все будут сидеть под именем первого вошедшего на форум
# fastcgi_hide_header "Set-Cookie";
#Этот запрос заставит nginx кешировать все что проходит через него
# fastcgi_ignore_headers "Cache-Control" "Expires";
fastcgi_index  index.php;
#  fastcgi_intercept_errors on; # только на период тестирования
# Включаем параметры из /etc/nginx/fastcgi_param
include fastcgi_params;
# Путь к скрипту, который будет передан в php-fpm
fastcgi_param       SCRIPT_FILENAME  $document_root$fastcgi_script_name;
fastcgi_ignore_client_abort     off;
}

Шаблон подключения обработки Perl
mcedit  /etc/nginx/templates/perlcgi

#Все скрипты заканчивающиеся на pl и cgi
location ~ .(pl|cgi)$
{
#Не сжимаем скрипты
gzip off;
try_files $uri =404;
#Передаем скрипты на обработку fcgiwrap
fastcgi_pass unix:/var/run/fcgiwrap.socket;
# Используем стандартные параметры
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_ignore_client_abort off;
}
#Замена апачевской ScriptAlias
location /cgi-bin/ {
gzip off;
try_files $uri =404;
root /var/www/;
fastcgi_pass unix:/var/run/fcgiwrap.socket;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_ignore_client_abort off;
}

Шаблон подключения phpmyadmin
mcedit  /etc/nginx/templates/phpmyadmin

location /phpmyadmin {
root /var/www/;
index index.php index.html index.htm;
location ~ ^/phpmyadmin/(.+.php)$ {
try_files $uri =404;
root /var/www/;
fastcgi_pass unix:/tmp/wwwpool.sock;
#fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include /etc/nginx/fastcgi_params;
}
location ~* ^/phpmyadmin/(.+.(jpg|jpeg|gif|css|png|js|ico|html|xml|txt))$ {
root /var/www/;
}
}

Отключаем уязвимость forum.antichat.ru/thread222063-php-fpm.html
прописываем в /etc/php5/fpm/php.ini

cgi.fix_pathinfo=0

Создаем шаблон по умолчанию для своего домена:

mcedit /etc/nginx/sites-available/default

server {
# Папка с контентом сайта (удобно, когда совпадает с именем домена)
root /var/www/htdocs/;
# Настройка логов, каждому виртуальному домену - свой лог
access_log /var/log/nginx/default-access.log;
error_log /var/log/nginx/default-error.log;

# Подключаем все шаблоны для проверки, на реальных хостах будем использовать только нужные.
include /etc/nginx/templates/default;
include /etc/nginx/templates/php;
include /etc/nginx/templates/phpmyadmin;
include /etc/nginx/templates/perlcgi;
}

Создаем симлинк

ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/000-default

Удаляем стандартный default, если есть

 rm /etc/nginx/conf.d/default.conf

Проверяем правильность конфига

service nginx configtest

Testing nginx configuration: the configuration file /etc/nginx/nginx.conf syntax is ok
configuration file /etc/nginx/nginx.conf test is successful
nginx.
Готово! перезапускаем Nginx

service nginx reload

Если все верно, то nginx начинает работать с новой конфигурацией, если нет — работа продолжается со старой, рабочей конфигурацией.

Проверка работоспособности php.
Создадим файл test.php в корневом каталоге сайта (в нашем случае это /var/www/htdocs/) следующего содержания:

 <?php phpinfo(); ?>
chmod 755 /usr/local/hosting/www/test.php && chown www-data:www-data /usr/local/hosting//www/htdocs/test.php

После чего переходим в браузере IP/test.php, если все верно — получаем параметры php на сервере, после чего удаляем этот фаил для безопасности.

Проверка работоспособности Perl.
Создадим файл test.cgi в корневом каталоге сайта (в нашем случае это /var/www/htdocs/) следующего содержания:

#!/usr/bin/perl -w
print "Content-type: text/htmlnn";
print "<html><head><title>Hello World!! </title></head>n";
print "<body><h1>Hello world</h1></body></html>n";
chmod 755 /var/www/htdocs/test.cgi && chown www-data:www-data /var/www/htdocs/test.cgi

После чего переходим в браузере IP/test.cgi, если все верно — получаем «Hello world».

Если все работает, то на этом этапе мы имеем рабочий веб сервер с поддержкой php и perl скриптов, дальше опишу некоторые нюансы донастройки отдельных его частей.

.htaccess !

В Nginx нет никакого аналога апачевского .htaccess, поэтому если работа сайта требует его наличия, то придётся переписывать его содержание в соответствии с синтаксисом nginx в основную конфигурацию домена. В нашем конфиге .htaccess был заменён следующим блоком:

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

Для преобразования .htaccess в синтаксис nginx можно воспользоваться [urlspan]онлайн-конвертором[/urlspan]

3. Настройка PHP-FPM

В Debian конфигурация PHP-FPM состоит из 2х частей: глобальной (/etc/php5/fpm/php-fpm.conf) и настройки пулов (/etc/php5/fpm/pool.d/*.conf). В данном примере глобальные настройки мы трогать не будем, а вот на настройке пулов остановимся чуть подробнее.

Пулы

Для начала разберёмся зачем нужны пулы. В случае разных требований сайтов к PHP-окружению (различные параметры php.ini, разное число обработчиков и т.д.) может потребоваться создание дополнительных пулов. Данная операция в PHP-FPM весьма тривиально:

Настройка каждого пула в Debian представлена своим файлом в каталоге /etc/php5/fpm/pool.d/. По умолчанию системе есть единственный пул «www» (файл: /etc/php5/fpm/pool.d/www.conf) именно его настройкой мы и займёмся.

Workers (обработчики)

Самая спорная часть в настройке пула, это количество обработчиков php-скриптов. На первый взгляд, кажется, что чем больше обработчиков, тем эффективней обрабатываются php-скрипты. Но это не так! Во-первых: большое число обработчиков расходует больше памяти (а для нашего сервера память весьма критичный ресурс), во-вторых: если обработчиков очень много и, так случилось, что все они реально заняты работой, то у сервера может просто не хватить ресурсов на другие задачи (даже есть вероятность, что подключение по SSH станет практически не возможным).

В идеале число обработчиков должно быть таким, что даже при стрессовой нагрузке LoadAvarage системы оставался в разумных пределах. Т.е. пусть лучше при высокой нагрузке пользователи периодически получают сообщения о недоступности сервиса (ошибка 502: Gateway timeout), чем полная недоступность сервера даже для администратора.

И так немного подредактируем стандартный пул mcedit /etc/php5/fpm/pool.d/www.conf

#Где будем слушать подключения, я предпочитаю сокеты, но если сервера разнесены, то прийдется использовать порт.
;listen = 127.0.0.1:9000
listen = /tmp/wwwpool.sock

# Выбираем динамический режим создания процессов, т.е.
# число запущенных процессов PHP-FPM будет зависеть от текущей нагрузки
pm = dynamic

# Максимальное количество дочерних процессов.
pm.max_children = 7

# Количество дочерних процессов, стартующих сразу при загрузке сервера. Т.к. время запуска каждого
# нового процесса отлично от нулевого, то выбираем значение больше 1, не смотря на экономию ресурсов
pm.start_servers = 3

# Минимальное чисто простаивающих процессов. Должен согласовываться по логике с предыдущими
# при экономии ресурсов будет удобно pm.start_servers = pm.min_spare_servers.
pm.min_spare_servers = 3

# Максимальное чисто простаивающих процессов. Естественно, что не более чем pm.max_children
# и не менее pm.min_spare_servers. Остальные будут выгружены.
pm.max_spare_servers = 4

# Если скрипт будет выполняться больше указанного времени, то отладочная информация по нему будет записана в файл "медленных" запросов
request_slowlog_timeout = 3s
# Определяет путь к файлу "медленных" запросов (обязательный параметр, в случае определения request_slowlog_timeout)
slowlog = /var/log/php-slow.log

Оптимальное число обработчиков зависит от ресурсов сервера, сложности php-скриптов, нагрузки, создаваемой на mysql-сервер и т.д. В любом случае оптимальное число обработчиков нужно подбирать на основе тестирования работы сайта. Методика тестирования неплохо описана тут, повторяться не буду.

Добавление пула

При увеличении числа обслуживаемых сайтов может понадобиться создание дополнительных пулов, для настройки различных параметров каждому сайту — своё. Данная операция в php-fpm, на наш взгляд, весьма тривиальна:
Нужно скопировать файл /etc/php5/fpm/pool.d/www.conf под новым именем (для примера назовём его newpool.conf)
Дать новому пулу имя: находим вверху нового файла строку [www] (имя первого пула) и меняем на [newpool]
Меняем адрес подключения к php-fpm (директива «listen»). Т.к. каждый адрес должен быть уникален, то нужно изменить:

listen = 127.0.0.1:9000

на

listen = 127.0.0.1:9001

Или, в случае использования unix-сокетов,

listen = /tmp/newpool.sock

Номера портов и путей к unix-сокетам во всех пулах должны быть разными!

Для применения параметров после изменения php.ini (для PHP-FPM полный путь к файлу выглядит так: /etc/php5/fpm/php.ini) или собственных настроек PHP-FPM требуется перезапуск сервиса

service php5-fpm restart

4. Установка phpmyadmin

Загрузим стабильную версию phpmyadmin с сайта [urlspan]www.phpmyadmin.net/home_page/downloads.php[/urlspan]

wget http://optimate.dl.sourceforge.net/project/phpmyadmin/phpMyAdmin/4.2.7/phpMyAdmin-4.2.7-all-languages.tar.gz

Распакуем сразу в нужную дирректорию и дадим нужные права.

tar -xzf phpMyAdmin-4.2.7-all-languages.tar.gz -C /var/www/
mv /var/www/phpMyAdmin-master-версия /var/www/phpmyadmin
chown www-data: /var/www/phpmyadmin -R

Скопируем файл с примером конфигурации и приведем его к следующему виду (сгенерировать blowfish_secret можно [urlspan]здесь[/urlspan]):

cp /var/www/phpmyadmin/config.sample.inc.php /var/www/phpmyadmin/config.inc.php
mcedit /var/www/phpmyadmin/config.inc.php
$cfg['blowfish_secret'] = ‘e%o$fd3}tC9[GxY$zY_+ddfTdss[i*JG]#GHt]alv’
$cfg['Servers'][$i]['auth_type'] = ‘http’;
$cfg['Servers'][$i]['controluser'] = ‘pma’;
$cfg['Servers'][$i]['controlpass'] = ‘fHUsMkId57NM’; // Сгенерируйте свой пароль
$cfg['Servers'][$i]['pmadb'] = ‘phpmyadmin’;
$cfg['Servers'][$i]['bookmarktable'] = ‘pma_bookmark’;
$cfg['Servers'][$i]['relation'] = ‘pma_relation’;
$cfg['Servers'][$i]['table_info'] = ‘pma_table_info’;
$cfg['Servers'][$i]['table_coords'] = ‘pma_table_coords’;
$cfg['Servers'][$i]['pdf_pages'] = ‘pma_pdf_pages’;
$cfg['Servers'][$i]['column_info'] = ‘pma_column_info’;
$cfg['Servers'][$i]['history'] = ‘pma_history’;
$cfg['Servers'][$i]['tracking'] = ‘pma_tracking’;
$cfg['Servers'][$i]['designer_coords'] = ‘pma_designer_coords’;
$cfg['Servers'][$i]['userconfig'] = ‘pma_userconfig’;
$cfg['SuhosinDisableWarning'] = ‘true’;

Затем создаем базу данных и пользователя необходимые для работы phpMyAdmin:

mysqladmin -p create phpmyadmin
 mysql -p
 CREATE USER 'pma'@'localhost' IDENTIFIED BY 'fHUsMkId57NM';
 GRANT ALL ON phpmyadmin.* TO 'pma'@'localhost';
 exit;

и необходимые для работы таблицы при помощи скрипта create_tables.sql:

mysql -p phpmyadmin < /var/www/phpmyadmin/examples/create_tables.sql

Теперь при обращении к любому хосту, в конфигурацию которого включен шаблон /etc/nginx/templates/phpmyadmin мы имеем возможность запустить phpMyAdmin, перейдя по адресу _http://имя_хоста/phpmyadmin