Ars Longa, Vita Brevis

Как настроить связку nginx+PHP в Windows и не иметь головной боли.

Принято считать, что замечательный Web-сервер nginx работает только под Unix-подобными операционными системами. Верно, но лишь отчасти. nginx превосходно собирается и в Cygwin. Возникает вопрос: а зачем это надо, собирать nginx под Windows? Ответ: я знаю достаточно много web-разработчиков, работающих в силу тех или иных причин под Windows. И в работе встречаются ситуации, когда на рабочей машине нужно создать конфигурацию, максимально похожую на конфигурацию сервера (production или development в данном случае не важно).

Начну с примера: есть сайт, который позволяет зарегистрированным пользователям скачивать что-либо (музыку, программы, фильмы — не суть важно). Одним из самых распространённых решений в этом случае является использование нескольких серверов. Во многих конфигурациях, которые я видел, "web-мордой" занимается Apache, а на обслуживании закачек стоит nginx+PHP+FastCGI (PHP занимается вопросами авторизации, ведения статистики и т.п.). Сразу оговорюсь, что есть альтернативное решение, когда nginx используется в качестве reverse proxy перед Apache — nginx проксирует запросы к PHP-скриптам Apache, а PHP-скрипт, если ему надо отдать что-то статическое, делает X-Accel-Redirect. Подробнее описано в статьях "Использование X-Accel-Redirect с Nginx для реализации контролируемых скачиваний" и "Использование Nginx Как Reverse-Proxy Сервера На Загруженных Сайтах".

Отвлекусь от темы и скажу пару слов про альтернативную конфигурацию: она работает на "ура", если сайт отдаёт в основном статический контент. Если же большая часть запросов идёт к PHP, то логичнее поставить nginx за Apache (в противном случае будет тратиться время на проксирование запроса). Еще один минус у такой конфигурации — использование менеджеров закачек (всякие там ReGet, FlashGet) "ложит" Apache. Можно, конечно, ввести ограничения на одновременное количество заказчек, а можно настроить PHP работать в паре с nginx.

Возвращаясь к теме разговора: а теперь предположим, что это всё нужно реализовать локально (для тестирования). Вот в таких ситуациях nginx для Windows очень хорошо помогает :-)

В Cygwin сборка nginx проходит практически без проблем (хотя более старые версии приходилось немного патчить руками), на выходе получаем nginx.exe :-)
Я не буду вдаваться в детали конфигурирования nginx, потому что есть замечательная статья "Настройка Nginx для поддержки PHP при помощи FastCGI", просто отмечу некоторые детали, упрощающие жизнь.

Итак, у нас есть PHP, Cygwin и nginx. К сожалению, nginx не поддерживает PHP как модуль (подобно Apache), поэтому nginx и PHP могут работать только по протоколу FastCGI (возможно, и по обычному CGI, я не пробовал — в любом случае, производительность при работе через CGI будет значительно ниже).

Для определённости будем полагать, что nginx у нас сконфигурирован следующим образом (я привожу только часть, относящуюся к FastCGI):

location ~ \.php$ {
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  disk:/path/to$fastcgi_script_name;
    include        conf/fastcgi_params;
}

Ключевой момент: в fastcgi_param должен быть путь, который поймёт интерпретатор PHP. Если интерпретатор собран в Windows, то путь тоже должен быть в формате Windows (например, c:/web/mysite.com/scripts). Хвостового слэша быть не должно, потому что $fastcgi_script_name именно с него и начинается. То есть если пользователь запросил http://mysite.com/test.php, то $fastcgi_script_name будет равен /test.php.

Файл conf/fastcgi_params:

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;
fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx;
fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

Для удобства, установим nginx как сервис Windows. Для этого можно использовать, например, cygrunsrv:

[-]
View Code Bash
cygrunsrv --install nginx --path /path/to/nginx.exe --disp nginx --neverexits

Ключевым здесь является параметр --neverexits — без него Windows будет жаловаться, что процесс был остановлен (из-за того, что nginx fork()'ается). Хотя это неправильное использование --neverexits.

Переходим к настройке PHP. По счастью, в Windows-версии PHP уже есть модуль с поддержкой FastCGI (он называется php-cgi.exe), так что пересобирать PHP не придётся (сборка PHP под Cygwin — неблагодарное занятие из-за парочки глюков в libtool; если у меня будет желание, как нибудь напишу). В статье приводится скрипт для запуска PHP/FastCGI, но мы поступим проще.

Сначало об одной не очень документированной особенности PHP/FastCGI: он имеет тенденцию "незаметно умирать" после некоторого числа запросов. Это связано со значением переменной окружения PHP_FCGI_MAX_REQUESTS, определяющей, какое максимальное количество запросов может "пережить" PHP. Можно, кончено, поставить "космическое" значение, но 2 миллиарда запросов — так ли это много? :-) На отрицательные значения переменной PHP громко ругается. Экспериментально было выяснено, что 0 — это то, что нужно (в документации я это не нашел).

Запускаем PHP:

[-]
View Code Bash
set PHP_FCGI_MAX_REQUESTS=0
php-cgi -b 127.0.0.1:9000 -c path/to/php.ini

А теперь как установить PHP/FastCGI сервисом Windows:

[-]
View Code Bash
cygrunsrv --install php-cgi --path /cygdrive/f/web/php/php-cgi.exe --disp "PHP/FastCGI" --env "PHP_FCGI_MAX_REQUESTS=0" --args "-b 127.0.0.1:9000 -c path/to/php.ini"

Строка "-b 127.0.0.1:9000 -c path/to/php.ini" будет передана интерпретатору в качестве командной строки, поэтому путь path/to/php.ini должен быть путем Windows, если интепретатор собран под Windows.

Собственно, всё :-)

Добавить в закладки<!--[if lte IE 6]>
<!