Запускаем PHP-скриптики через php-fpm без web-сервера. Или свой FastCGI-клиент (под капотом)
Статья получилась довольно длинная и тем кто не хочет читать предысторию, а хочет перейти сразу к сути прошу прямиком к главе «Решение».
Вступление
В данной статье хотелось бы рассказать о решении довольно нестандартной задачи, с которой пришлось столкнуться во время рабочего процесса. А именно, нам понадобилось запускать в цикле кучу php скриптов. О причинах и о спорности подобного архитектурного решения в данной статье распространяться не буду, т.к. собственно она и не про это вовсе, просто была задача, ее нужно было решить и решение показалось мне достаточно интересным чтобы им поделиться с Вами, тем более манов по данному вопросу в интернете я не нашел совсем (ну разумеется кроме официальных спецификаций). Спеки конечно это хорошо и в них конечно все есть, но думаю вы согласитесь, что если вы не особо знакомы с темой, да и еще и ограничены по времени то разбираться в них то еще удовольствие.
Для кого эта статья
Для всех кто работает с web-ом и о протоколе FastCgi знает лишь что это протокол в соответствии с котороым web-сервер запускает php скриптики, но хочет более детально его изучить и заглянуть под капот.
Обоснование (зачем эта статья)
В общем как я уже писал выше когда мы столкнулись с необходимостью запускать много php скриптов без участия web-сервера (грубо говоря из другого php скрипта), то первое что пришло на ум это .
shell_exec('php \path\to\script.php')
Но при запуске каждого скрипта, будет создаваться окружение, запускаться отдельный процесс, в общем как то затратно по ресурсам нам показалось. Данную реализацию отвергли. Второе что пришло на ум это конечно же php-fpm, он ведь такой крутой, всего один раз запускает окружение, следит за памятью, все там логирует, корректно запускает и останавливает скрипты, в общем все делает круто, и нам конечно же этот путь понравился больше.
Но вот незадача, в теории то мы знали как это работает, в общих чертах (как оказалось в очень общих), но вот реализовать этот протокол на практике без участия web-сервера оказалось довольно трудно. Чтение спецификаций и пару часов безуспешных попыток показали что для реализации потребуется время, которого у нас на тот момент не было. Манов по реализации данной затеи, в которых было бы просто и понятно описано данное взаимодействие не нашлось, спеки наскоком взять тоже не удалось, из готовых решений нашли питоновский скрипт и пыховскую либу на гитхабе, которую в итоге не захотели тащить к себе в проект (может это и не правлиьно но не особо мы любим всякие сторонние библиотеки да еще и не очень то и популярные, а значит и не проверенные). В общем по итогу от этой идеи мы отказались и реализовали все это через старых добрый rabbitmq.
Хоть задачу в итоге и решили, но разобраться в FastCgi детально я все таки решил, и в добавок решил написать об этом статью, в которой будет просто и подробно описано как заставить php-fpm запустить php скрипт без web-сервера, а точнее в качестве web-сервера будет другой скрипт, далее его буду называть Fcgi клиент. В общем надеюсь что данная статья поможет тем кто столкнулся с такой же задачей как и мы и прочитав ее сможет быстро все написать как ему надо.
Творческий поиск (ложный путь)
Итак проблема обозначена, надо приступать к решению. Естественно как любой «нормальный» программист для решения задачи, про которую ни где не написано что делать и что вводить в консоль, я не стал читать и переводить спецификацию, а сразу же придумал свое «гениальное» решение. Суть его в следующем, я знаю что nginx (мы используем nginx и чтобы не писать далее дурацкое — web-сервер, буду писать nginx, так как то посимпатичнее) что то передает в php-fpm, это что то php-fpm обрабатывает и на основе него запускает скрипт, что ж вроде все просто, возьму да залогирую то что передает nginx и передам то же самое.
Тут поможет великолепный netcat (UNIX-утилита для работы с сетевым трафиком, которая по моему может практически все). Итак ставим netcat на прослушивание локального порта, а nginx настраиваем на работу с php файлами через сокет (естественно сокет на том же порту который слушает netcat)
слушаем 9000 порт
nc -l 9000
Проверить что все ок, можно обратившись через браузер на адрес 127.0.0.1:9000 должна быть следующая картина
настраиваем nginx чтобы он php скрипты обрабатывал через сокет на 9000 порту (в настройках ‘/etc/nginx/sites-available/default’, конечно могут отличаться)
location ~ \.php$ < include snippets/fastcgi-php.conf; fastcgi_pass 127.0.0.1:9000; >
После этих манипуляций проверим что же получилось, обратившись к php скрипту через браузер
Видно что nginx отправил переменные окружения, а также непечатаемые символы, то есть данные были переданы в двоичной кодировке, а это значит что так просто их нельзя скопировать и послать в сокет php-fpm. Если сохранить их в файл например то они сохраняться в 16-ричной кодировке, выглядеть это будет примено так
Но это тоже мало что нам дает, наверное чисто теоретически их можно перевести в двоичную кодировку, каким то образом (даже не представляю каким) их отправить в сокет fpm, и даже есть вероятность что весь этот велосипед как то сработает, и даже запустит какой то скрипт, но уж как то все это страшненько и кривенько.
Стало ясно что данный путь совершенно неверный, сами видите насколько все это убого выглядит, и тем более все эти действия не позволят нам управлять соединением, и ни как не приблизят к пониманию взаимодействия между php-fpm и nginx.
Все пропало, изучения спецификации не миновать!
Решение (тут собственно начинается вся соль данной статьи)
Теоретическая подготовка
Давайте теперь рассмотрим как же все таки происходит соединение и обмен данными между nginx и php-fpm. Немного теории, все общение происходит как уже понятно через сокеты, далее будем рассматривать конкретно соединение через TCP сокет.
Единицей информации в протоколе FastCgi является cgi запись. Такие записи сервер отправляет приложению и точно такие же записи получает в ответ.
Немного теории (структуры)
Далее рассмотрим структуру записи. Для понимания из чего состоит запись нужно понимать что из себя представляют Си подобные структуры и понимать их обозначения. Для тех кто не знает далее это будет кратко (но достаточно для понимания) описано. Описать постараюсь как можно проще, в детали углубляться тут нет смысла, да и боюсь что в деталях запутаюсь, главное чтобы было общее понимание.
Структуры представляют собой просто напросто набор байтов, и нотацию к ним позволяющую их интерпретировать. То есть у вас есть просто последовательность нулей и единиц, и в этой последовательности зашифрованы какие то данные, но пока у вас к этой последовательности нет аннотации то эти данные для вас не представляют никакой ценности, т.к. интерпретировать их вы не можете.
//допустим вам пришли такие данные 1101111000000010010110000010011100010000
Что тут видно, у нас есть некоторые биты, что это за биты мы понятия не имеем. Ну давайте попробуем например их разделить на байты и представить в десятичной системе
//получилось у нас 5 байт 11011110 00000010 01011000 00100111 00010000 //переведем в десятичную систему 222 2 88 39 16
Отлично мы интерпретировали их и получили какие то результаты, допустим что эти данные отвечают за то сколько определенная квартира должна за электроэнергию. Получается что в доме 222 квартира номер 2 должна заплатить 88 рублей. А что еще за две цифры, что с ними делать просто отбросить? Конечно нет! дело в том что мы не имели нотации (формата) которая подсказала бы нам как интерпретировать данные, и интерпретировали их по своему, в связи с этим получили не только бесполезный, но и вредный результат. В итоге квартира 2 заплатила совершенно не то что должна была. (примеры конечно надуманные и служат лишь для того чтобы более понятно объяснить ситуацию)
Теперь посмотрим как же мы должны были интерпретировать правильно эти данные, имея нотацию (формат). Далее буду называть вещи своими именами, а именно нотация = формат (вот тут форматы).
//формат следующий "Cnn" //расшифровка формата //C - беззнаковый символ (char) (8 бит) //n - беззнаковый short (16 бит) //разобьем данные в соответствии с форматом 11011110 0000001001011000 0010011100010000 //переведем в десятичную систему 222 600 10000
Теперь все сходиться в доме №222 квартира 600 за электричество должна 1000 рублей Думаю теперь ясна важность формата, и теперь понятно как примерно выглядит условно Си подобная структура. (прошу обратить внимания, тут цель не детально объяснить что такое эти структуры, а дать общее понимание что это такое и как это работает)
Условное обозначение данной структуры будет такое
struct < unsigned char houseNumber; unsigned char flatNumperA1; unsigned char flatNumperA2; unsigned char summB1; unsigned char summB2; >; //одинаковые имена, с разными окончаниями означают что в них хранится одно значение // houseNumber - дом // flatNumperA1 && flatNumperA2 - квартира // summB1 && summB2 - сумма долга
Еще немного теории (FastCgi записи)
Как я уже сказал выше единицей информации в протоколе FastCgi являются записи. Записи сервер отправляет приложению и такие же записи получает в ответ. Запись состоит из заголовка и тела с данными.
- версия протокола (всегда 1) обозначается 1 байтом (‘C’)
- тип записи. Для открытия, закрытия соединения и др. все не буду рассматривать, далее рассмотрю только то что понадобится для конкретной задачи, если нужны другие — добро пожаловать сюда спецификация. Обозначается 1 байтом (‘C’).
- ID запроса, произвольное число, обозначается 2 байтами (‘n’)
- длинна тела записи (данных), обозначается 2 байтами (‘n’)
- длинна выравнивающих данных и зарезервированные данные, по одному байту (тут не нужно особо обращать внимания, дабы не отвлекаться от главного в нашем случае всегда будет 0)
Далее идет само тело записи:
- сами данные (тут то именно и передаются переменные), могут иметь довольно большой размер (до 65535 байт)
Вот пример самой простой FastCgi записи в двоичном виде с форматом
struct < //Заголовок unsigned char version; unsigned char type; unsigned char idA1; unsigned char idA2; unsigned char bodyLengthB1; unsigned char bodyLengthB2; unsigned char paddingLength; unsigned char reserved; //Тело записи unsigned char contentData; //до 65535 байт unsigned char paddingData; >;
Практика
Скрипт клиент и передающий сокет
Для передачи данных будем использовать стандартное php расширение socket. И первое что нужно будет сделать — это настроить php-fpm на прослушивание порта на локальном хосте, например 9000. Это делается в большинстве случаем в файле ‘/etc/php/7.3/fpm/pool.d/www.conf’, путь конечно зависит от настроек вашей системы. Там нужно прописать примерно следующее (всю портянку привожу чтобы можно было сориентироваться, главная секция здесь listen)
; The address on which to accept FastCGI requests. ; Valid syntaxes are: ; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on ; a specific port; ; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on ; a specific port; ; 'port' - to listen on a TCP socket to all addresses ; (IPv6 and IPv4-mapped) on a specific port; ; '/path/to/unix/socket' - to listen on a unix socket. ; Note: This value is mandatory. ;listen = /run/php/php7.3-fpm.sock listen = 127.0.0.1:9002
После настройки fpm, следующим этапом будет подключение к сокету
$service_port = 9000; $address = '127.0.0.1'; $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); $result = socket_connect($socket, $address, $service_port);
Начало запроса FCGI_BEGIN_REQUEST
Для открытия соединения мы должны отправить запись с типом FCGI_BEGIN_REQUEST = 1 Заголовок записи будет такой (для приведения числовых значений к бинарной строке с заданным форматом будет использована php функция pack())
socket_write($socket, pack('CCnnCx', 1, 1, 1, 8, 0)); //версия протокола - 1 //тип записи - 1 - FCGI_BEGIN_REQUEST //id - 1 //длинна тела запроса - 8 бит //выравнивание - 0
Тело записи для открытия соединения должно содержать роль записи и флаг управляющий соединением
//структура тела записи для открытия соединения //struct < // unsigned char roleB1; // unsigned char roleB0; // unsigned char flags; // unsigned char reserved[5]; //>; //php реализация socket_write($socket, pack('nCxxxxx', 1, 0)); //роль - 1 - открытие //флаг - 1 - если упростить то 1 значит удерживать соединение
Итак запись для открытия соединения успешно отправлена, php-fpm ее примет и далее будет ожидать от нас дальнейшей записи в которой нужно передать данные для разворачивания окружения и запуска скрипта.
Передача параметров окружения FCGI_PARAMS
В данной записи мы будем передавать все параметры которые нужны для разворачивания окружения, а так же имя скрипта который нам надо будет запустить.
Минимальные необходимые параметры окружения
$url = '/path/to/script.php' $env = [ 'REQUEST_METHOD' => 'GET', 'SCRIPT_FILENAME' => $url, ];
Первое что нам тут нужно сделать — это подготовить необходимые переменные, то есть пары имя => значение, которые мы передадим приложению.
Структура пар имя значение будет такая
//для пар в которых значение имени и данных в менее 128 байт typedef struct < unsigned char nameLength; unsigned char valueLength; unsigned char nameData unsigned char valueData; >; //имя и значение кодируется 1 байтом
Идет сначала 1 байт — длинна имени, потом 1 байт значение
//для пар в которых значение имени и данных более 128 байт typedef struct < unsigned char nameLengthA1; unsigned char nameLengthA2; unsigned char nameLengthA3; unsigned char nameLengthA4; unsigned char valueLengthB1; unsigned char valueLengthB2; unsigned char valueLengthB3; unsigned char valueLengthB4; unsigned char nameData unsigned char valueData; >; //имя и значение кодируется 4 байтами
В нашем случае и имя и значения короткие и подходят под первый вариант, по этому его и будем рассматривать.
Закодируем наши переменные в соответствии форматом
$keyValueFcgiString = ''; foreach ($env as $key => $value) < //длинна разных по длине значений кодируется по разному //если меньше 128 байт то одним байтом если больше то четырьмя $keyLen = strlen($key); $lenKeyChar = $keyLen
Тут значения меньше 128 бит кодируются функцией chr($keyLen), больше pack('N', $valLen), где 'N' обозначает 4 байта. И затем все это слепляется в одну строку в соответствии с форматом структуры. Тело записи готово.
В заголовке записи передаем все то же самое как и в предыдущей записи, кроме типа (он будет FCGI_PARAMS = 4) и длинны данных (она будет равна длине пар имя => значение, или длине строки $keyValueFcgiString которую ранее мы сформировали).
//отправка заголовка socket_write($socket, pack('CCnnCx', 1, 4, 1, strlen($keyValueFcgiString), 0)); //отправка body socket_write($socket, $keyValueFcgiString); //для перевода приложения в режим выполнения и отправки ответа посылаем еще одну запись //с нулевым body socket_write($socket, pack('CCnnCx', 1, 4, 1, 0, 0));
Получение ответа FCGI_PARAMS
Собственно после того как все предыдущее проделано, и приложению отправлено все что оно ожидает, оно начинает работу и нам остается только забрать результат этой работы из сокета.
Помним что в ответ мы получаем такие же записи и нам их тоже нужно интерпретировать.
Получаем заголовок, он всегда равен 8 байт (получать данные будем по байту)
$buf = ''; $arrData = []; $len = 8; while ($len) < socket_recv($socket, $buf, 1, MSG_WAITALL); //получаем данные по 1 байту и пишем их в массив $arrData[] = $buf; $len--; >//интерпретируем заголовок в соответствии с форматом 'CCnnCx' $protocol = unpack('C', $arrData[0]); $type = unpack('C', $arrData[1]); $id = unpack('n', $arrData[2] . $arrData[3]); $dataLen = unpack('n', $arrData[4] . $arrData[5])[1]; //длинна данных в ответе, их нам надо будет получить после заголовка (unpack возвращает массив, по этому там индекс) $foo = unpack('C', $arrData[6]); var_dump($dataLen); //сколько байт будет в теле ответа
Теперь в соответствии с полученной длинной тела ответа сделаем еще одно чтение из сокета
$buf2 = ''; $result = []; while ($dataLen) < socket_recv($socket, $buf2, 1, MSG_WAITALL); $result[] = $buf2; $dataLen--; >var_dump(implode('', $result)); //тут будет то что отдаст искомый скрипт socket_close($socket);
Ура все сработало! Наконец то!
Что мы имеем в ответе, если например в этом файле
$url = '/path/to/script.php' //переменная окружения которую задали ранее
то в ответе получим в итоге

Итоги
Много тут не буду писать итак статья длинная получилась. Надеюсь она кому то поможет. И приведу сам итоговый скрипт, он получился совсем небольшой. Конечно он в таком виде довольно мало может, и в нем нет обработки ошибок и всего этого, но ему это и не надо, он нужен как пример, чтобы показать основы.
Полная версия скрипта
'GET', 'SCRIPT_FILENAME' => $url, ]; $service_port = 9000; $address = '127.0.0.1'; $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); $result = socket_connect($socket, $address, $service_port); //ОТКРЫТИЕ СОЕДИНЕНИЯ //запрос на начало сессии с php-fpm //параметры в пордке следования версия, тип записи (что эта запись будет делать), id запроса, длинна тела записи, длинна данных для выравнивания socket_write($socket, pack('CCnnCx', 1, 1, 1, 8, 0)); //тело записи для открытия соединения //параметры роль, флаг управляющий закрытыием соединения socket_write($socket, pack('nCxxxxx', 1, 0)); $keyValueFcgiString = ''; foreach ($env as $key => $value) < //длинна разных по длинне значений кодируется по разному //если меньше 128 байт то одним байтом если больше то четырьмя $keyLen = strlen($key); $lenKeyChar = $keyLen < 128 ? chr($keyLen) : pack('N', $keyLen); $valLen = strlen($value); $valLenChar = $valLen < 128 ? chr($valLen) : pack('N', $valLen); $keyValueFcgiString .= $lenKeyChar . $valLenChar . $key . $value; >//следующая запись, тут уже будем передавать в php-fpm параметры в каком окружении и какой скрипт мы хотим запустить //из особенностей опишу параметры которые передаю //1-версия (по старому), 4-тип записи (новое, означает передачу пар имя-значение FCGI_PARAMS), id запроса (тот же), длинна тела записи (длинна моих пар ключ-значение), длинна данных для выравнивания socket_write($socket, pack('CCnnCx', 1, 4, 1, strlen($keyValueFcgiString), 0)); //отправляем пары ключ значение на сервер socket_write($socket, $keyValueFcgiString); //финишируем запрос socket_write($socket, pack('CCnnCx', 1, 4, 1, 0, 0)); $buf = ''; $arrData = []; $len = 8; while ($len) < socket_recv($socket, $buf, 1, MSG_WAITALL); //получаем данные по 1 байту и пишем их в массив $arrData[] = $buf; $len--; >//интерпритируем заголовок в соответствии с форматом 'CCnnCx' $protocol = unpack('C', $arrData[0]); $type = unpack('C', $arrData[1]); $id = unpack('n', $arrData[2] . $arrData[3]); $dataLen = unpack('n', $arrData[4] . $arrData[5])[1]; //длинна данных в ответе, их нам надо будет получить после заголовка (unpack возвращает массив, по этому там индекс) $foo = unpack('C', $arrData[6]); $buf2 = ''; $result = []; while ($dataLen) < socket_recv($socket, $buf2, 1, MSG_WAITALL); $result[] = $buf2; $dataLen--; >var_dump(implode('', $result)); //тут будет то что отдас искомый скрипт socket_close($socket);
Как настроить PHP-FPM для веб-сервера Nginx
PHP-FPM (FastCGI Process Manager) — это альтернативная реализация FastCGI для PHP с дополнительными функциями, которые полезны для сайтов с высоким трафиком. Это предпочтительный метод обработки PHP-страниц с помощью NGINX, который работает быстрее, чем традиционные методы CGI (SUPHP или mod_php для запуска PHP-скрипта). Основное преимущество PHP-FPM заключается в том, что он использует значительно меньше памяти и CPU по сравнению с любыми другими методами запуска PHP. Основная причина такого поведения — он демонизирует PHP, тем самым превращая его в фоновый процесс и предоставляя скрипт CLI для управления PHP-запросами.
Требования
- Вы можете открыть SSH-сессию в системе Ubuntu от имени пользователя root или sudo.
- В системе должны быть установлены NGINX и PHP.
Этапы настройки PHP-FPM с NGINX
- Установка PHP-FPM
- Конфигурация пула PHP-FPM
- Настройка NGINX для работы с PHP-FPM
- Тест конфигурации NGINX и PHP-FPM
1: Установка PHP-FPM
Nginx не знает, как запустить свой PHP-скрипт. Для эффективного управления этими скриптами ему нужен PHP-модуль (например PHP-FPM). PHP-FPM работает вне среды NGINX, создавая отдельный процесс. Поэтому, когда пользователь запрашивает страницу PHP, сервер nginx передает запрос службе PHP-FPM с помощью FastCGI. Установка php-fpm в Ubuntu зависит от PHP и его версии. Перед установкой FPM на ваш сервер следует проверить документацию установленной версии PHP. Предположим, что вы установили последнюю версию PHP 7.3, значит вы можете установить FPM с помощью следующей команды apt-get:
# apt-get install php7.3-fpm
Сервис FPM запустится автоматически после завершения установки. Чтобы проверить его статус, выполните следующую команду systemd:
# systemctl status php7.3-fpm
php7.3-fpm.service - The PHP 7.3 FastCGI Process Manager Loaded: loaded (/lib/systemd/system/php7.3-fpm.service; enabled; vendor preset: enabled) Active: active (running) since Sun 2019-02-17 06:29:31 UTC; 30s ago Docs: man:php-fpm7.3(8) Main PID: 32210 (php-fpm7.3) Status: "Processes active: 0, idle: 2, Requests: 0, slow: 0, Traffic: 0req/sec" Tasks: 3 (limit: 1152) CGroup: /system.slice/php7.3-fpm.service ├─32210 php-fpm: master process (/etc/php/7.3/fpm/php-fpm.conf) ├─32235 php-fpm: pool www └─32236 php-fpm: pool www
2: Настройка пула PHP-FPM
Сервис php-fpm создает пул по умолчанию, конфигурация которого (www.conf) находится в папке /etc/php/7.3/fpm/pool.d. Вы можете настроить пул по умолчанию в соответствии с вашими требованиями. Также рекомендуется создавать индивидуальные пулы для более эффективного управления ресурсами каждого процесса FPM. Кроме того, отделение пулов FPM позволит им работать независимо друг от друга, создавая собственный мастер-процесс. Это означает, что каждое php-приложение может иметь свои параметры кэша. Изменение конфигурации одного пула не требует запуска или остановки остальных пулов FPM.
Давайте создадим пул FPM для эффективного запуска PHP-приложения отдельным пользователем. Для начала создайте нового пользователя, который будет иметь исключительные права на этот пул:
# groupadd wordpress_user
# useradd -g wordpress_user wordpress_user
Теперь перейдите в каталог конфигурации FPM и создайте файл настроек с помощью любого текстового редактора, например vi:
# cd /etc/php/7.3/fpm/pool.d
# vi wordpress_pool.conf
[wordpress_site] user = wordpress_user group = wordpress_user listen = /var/run/php7.2-fpm-wordpress-site.sock listen.owner = www-data listen.group = www-data php_admin_value[disable_functions] = exec,passthru,shell_exec,system php_admin_flag[allow_url_fopen] = off ; Choose how the process manager will control the number of child processes. pm = dynamic pm.max_children = 75 pm.start_servers = 10 pm.min_spare_servers = 5 pm.max_spare_servers = 20 pm.process_idle_timeout = 10s
Рассмотрим подробнее приведенные выше параметры конфигурации FPM и их значения:
- [wordpress_site]: название, которое должно быть уникальным для каждого пула.
- user и group: пользователь и группа, под которыми будет работать пул.
- listen: название файла сокета для этого пула.
- listen.owner и listen.group: должны соответствовать пользователю и группе, на которых работает NGINX. В нашем случае это www-data.
- php_admin_value: позволяет устанавливать пользовательские значения конфигурации php.
- php_admin_flag: позволяет устанавливать логические флаги PHP.
- pm: параметры менеджера процессов. Значение Dynamic означает, что количество дочерних процессов устанавливается динамически на основе следующих директив:
- pm.max_children: максимальное количество дочерних процессов, которые могут быть одновременно активны.
- pm.start_servers: количество дочерних процессов, создаваемых при запуске.
- pm.min_spare_servers: минимальное количество дочерних процессов в состоянии ожидания (ожидающих обработки). Если количество простаивающих процессов меньше этого числа, то будет создано несколько дочерних.
- pm.max_spare_servers: максимальное количество дочерних процессов в состоянии ожидания (ожидающих обработки). Если количество простаивающих процессов больше этого числа, некоторые дочерние будут завершены.
- pm.process_idle_timeout: желаемое максимальное количество простаивающих процессов сервера. Используется только при установке значения pm в “dynamic”. Кроме вышеуказанных параметров, также можно передавать несколько системных переменных в сервис php-fpm, с помощью env[‘PHP_FOO’] = $bar. Например, добавление следующих опций в приведенный выше файл конфигурации установит имя хоста и расположение временной папки в среде PHP.
. . env[HOSTNAME] = $HOSTNAME env[TMP] = /tmp . .
Настройки менеджеров процессов в приведенном выше файле конфигурации пула установлены как “dynamic”. Выберите параметр, который лучше всего соответствует вашим требованиям. Существуют также и другие конфигурации для менеджера процессов:
- Static: будет поддерживаться фиксированное количество процессов PHP.
- ondemand : дочерние процессы при запуске не создаются. Они будут создаваться при получении новых запросов на сервере.
После создания файла конфигурации необходимо перезапустить сервис fpm, чтобы применить новые настройки:
# systemctl start php7.3-fpm
После этого будет создан пул FPM для обслуживания php-страниц. Обратите внимание, что вы можете создать отдельный сервис systemd для данного пула, указав вышеупомянутый файл конфигурации FPM. Это позволит вам запускать/останавливать этот пул, не влияя на другие пулы.
3: Настройка NGINX для PHP-FPM
Теперь создайте server block NGINX, который будет использовать указанный выше пул FPM. Для этого отредактируйте файл конфигурации NGINX и укажите путь к файлу пула с помощью опции fastcgi_pass внутри location block для php.
server listen 80; server_name example.journaldev.com; root /var/www/html/wordpress; access_log /var/log/nginx/example.journaldev.com-access.log; error_log /var/log/nginx/example.journaldev.com-error.log error; index index.html index.htm index.php; location / try_files $uri $uri/ /index.php$is_args$args; > location ~ \.php$ fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/var/run/php7.2-fpm-wordpress-site.sock; fastcgi_index index.php; include fastcgi.conf; > >
Убедитесь, что приведенные выше параметры конфигурации синтаксически верны, и перезапустите NGINX.
# nginx-t
# systemctl restart nginx4: Тестирование конфигурации PHP-FPM NGINX
Для проверки того, использует ли вышеуказанный файл конфигурации NGINX только что созданный пул FPM, создайте файл phpinfo в root каталоге веб-сервера. Мы используем /var/www/html/wordpress в качестве веб-сервера в приведенном выше файле конфигурации NGINX. Измените это значение в соответствии с вашей средой.
После создания страницы с информацией о PHP, откройте ее в любом браузере. Вы заметите, что значения переменных $_SERVER[‘USER’] и $_SERVER[‘HOME’] указывают на wordpress_user и /home/wordpress_user соответственно — их мы ранее установили в файле конфигурации FPM, это подтверждает, что NGINX обслуживает php-страницы с помощью выбранного пула FPM.

Подводим итоги
В этой статье мы рассмотрели установку php-fpm и настройку отдельных пулов для разных пользователей и приложений. Вы также узнали, как настроить NGINX server block для подключения к сервису PHP-FPM. PHP-FPM обеспечивает надежность, безопасность, масштабируемость и скорость, а также предлагает множество опций настройки производительности. Теперь вы можете разбить стандартный пул PHP-FPM на несколько пулов для обслуживания разных приложений. Это не только повысит безопасность вашего сервера, но и позволит вам оптимально распределять ресурсы.
Читайте также:
- Настройка Apache с помощью MPM Event и PHP-FPM
- Запуск нескольких версий PHP на одном сервере с помощью Apache и PHP-FPM в Ubuntu 20.04
Настройка веб-сервера Nginx + PHP-FPM + MySQL
Быстрая установка и настройка веб-сервера NginX для работы CS-Cart.
Если вы в первый раз настраиваете VPS сервер, то рекомендуем начать с изучения с более детальной инструкции: Настройка веб-сервера Apache .
Видео
Сервер и ресурсы
Для инструкции арендован самый простой VPS сервер.
Обычно VPS сервер предоставляется с чистой операционной системой, мы выбрали последнюю версию Ubuntu 14.04 LTS x86 на данный момент.
Технические характеристики сервера:
ОС Ubuntu 14.04 LTS x86 Диск 2.0 ГБ Процессор 2000 МГц Память 128 МБ Ресурсов данного сервера достаточно только для теста и эксперимента.
Предупреждение! Настройку сервера для живого интернет-магазина необходимо доверить профессионалам.
Данная инструкция может быть использована для тестовых интернет-магазинов.
1. Подключаемся к серверу по SSH
Используем терминал (PuTTY).
2. Обновим список пакетов
Запустите в терминале (PuTTY) команду:
sudo apt-get update -y
3. Устанавливаем NginX
sudo apt-get install nginx -y
4. Останавливаем Nginx
Остановим NginX на время настройки простой командой:
sudo service nginx stop
5. Узнаем количество процессоров в системе
Количество процессоров нам понадобится для конфигурации NginX:
cat /proc/cpuinfo | grep processor | wc -l
Запоминаем число процессоров.
6. Установим число процессов Nginx
Найдите на сервере и откройте для редактирования файл:
worker_processes 4;
Установите для неё значение равное числу процессоров. В нашем случае один процессор.
worker_processes 1;
7. Создадим папку для интернет-магазина
mkdir -p /var/www/html/example.com
В примерах команд и в конфигурационном файле мы будем использовать example.com . Замените его на имя своего домена, например, dbazhenov.ru.
8. Конфигурация Nginx
Нам необходимо настроить конфигурацию NginX. Сделаем так, чтобы Nginx понимал наш домен и отправлял его в нужную папку на сервере, а также установим правила для SEO.
Найдите на сервере и откройте файл:
Удалите весь код и вставьте новый. Ниже будет код, в комментариях кратко описано происходящее. Вам нужно заменить домен example.com на ваш домен
####################################################################### # Описание и конфигурация основного домена для интернет-магазина ####################################################################### server listen 80; # Домен интернет-магазина server_name example.com; ############################################################################ # Кодировка по умолчанию charset utf-8; ############################################################################ # Основной каталог интернет-магазина root /var/www/html/example.com; index index.php index.html index.htm; ############################################################################ # Сжатие gzip on; gzip_disable "msie6"; gzip_comp_level 6; gzip_min_length 1100; gzip_buffers 16 8k; gzip_proxied any; gzip_types text/plain application/xml application/javascript text/css text/js text/xml application/x-javascript text/javascript application/json application/xml+rss; ############################################################################ # Прочие настройки client_max_body_size 100m; client_body_buffer_size 128k; client_header_timeout 3m; client_body_timeout 3m; send_timeout 3m; client_header_buffer_size 1k; large_client_header_buffers 4 16k; ############################################################################ access_log /var/log/nginx/example.com_access.log combined; error_log /var/log/nginx/example.com_error.log; ############################################################################ error_page 598 = @backend; ############################################################################ location @backend try_files $uri $uri/ /$2$3 /$3 /index.php =404; # Путь к сокету PHP-FPM fastcgi_pass unix:/var/run/php5-fpm.sock; # fastcgi_index index.php; fastcgi_read_timeout 360; # Добавляем содержимое fastcgi_params.conf ################################################################################ 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 HTTPS $https if_not_empty; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; 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; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param REDIRECT_STATUS 200; ################################################################################ > ############################################################################ location / index index.php index.html index.htm; try_files $uri $uri/ /index.php?$args; > ############################################################################ location ~ ^/(\w+/)?(\w+/)?api/ rewrite ^/(\w+/)?(\w+/)?api/(.*)$ /api.php?_d=$3&ajax_custom=1&$args last; rewrite_log off; > ############################################################################ location ~ ^/(\w+/)?(\w+/)?var/database/ return 404; > location ~ ^/(\w+/)?(\w+/)?var/backups/ return 404; > location ~ ^/(\w+/)?(\w+/)?var/restore/ return 404; > location ~ ^/(\w+/)?(\w+/)?var/themes_repository/ allow all; location ~* \.(tpl|php.?)$ return 404; > > location ~ ^/(\w+/)?(\w+/)?var/ return 404; location ~* /(\w+/)?(\w+/)?(.+\.(js|css|png|jpe?g|gz|yml|xml))$ try_files $uri $uri/ /$2$3 /$3 /index.php?$args; allow all; access_log off; expires 1M; add_header Cache-Control public; add_header Access-Control-Allow-Origin *; > > ############################################################################ location ~ ^/(\w+/)?(\w+/)?app/payments/ return 404; location ~ \.php$ return 598; > > location ~ ^/(\w+/)?(\w+/)?app/addons/rus_exim_1c/ return 404; location ~ \.php$ return 598; > > location ~ ^/(\w+/)?(\w+/)?app/ return 404; > ############################################################################ location ~* /(\w+/)?(\w+/)?(.+\.(jpe?g|jpg|ico|gif|png|css|js|pdf|txt|tar|woff|svg|ttf|eot|csv|zip|xml|yml))$ access_log off; try_files $uri $uri/ /$2$3 /$3 /index.php?$args; expires max; add_header Access-Control-Allow-Origin *; add_header Cache-Control public; > ############################################################################ location ~ ^/(\w+/)?(\w+/)?design/ allow all; location ~* \.(tpl|php.?)$ return 404; > > ############################################################################ location ~ ^/(\w+/)?(\w+/)?images/ allow all; location ~* \.(php.?)$ return 404; > > ############################################################################ location ~ ^/(\w+/)?(\w+/)?js/ allow all; location ~* \.(php.?)$ return 404; > > ############################################################################ location ~ ^/(\w+/)?(\w+/)?init.php return 404; > location ~* \.(tpl.?)$ return 404; > location ~ /\.(ht|git) return 404; > location ~* \.php$ return 598 ; > ################################################################################ >
9. Перезапускаем nginx
sudo service nginx restart
Прямое подключение к PHP-FPM
Во время отладки иногда необходимо сначала выявить проблему и понять это проблема из-за PHP или Nginx делает что-то не правильно.
В этом случае может пригодиться cgi-fcgi.Установка cgi-fcgi на Ubuntu#
Достаточно запустить команду:
apt-get install libfcgi0ldblПодключение к PHP-FPM на прямую#
Предполагаем что вы запустили PHP-FPM используя TCP/IP с IP и PORT значениями 127.0.0.1 и 9000 соответственно.
Ниже приведены некоторые примеры кода, которые вы можете использовать.Тест ответа PHP-FPM Ping#
Вы можете запустить следующий код из командной строки что бы протестировать ответ FPM:
1 2 3 4SCRIPT_NAME=/ping \ SCRIPT_FILENAME=/ping \ REQUEST_METHOD=GET \ cgi-fcgi -bind -connect 127.0.0.1:9000Должен быть возвращён подобный ответ:
1 2 3 4Content-Type: text/plain Expires: Thu, 01 Jan 1970 00:00:00 GMT Cache-Control: no-cache, no-store, must-revalidate, max-age=0 pongТест ответа PHP-FPM Status#
Вы можете запустить следующий код из командной строки что бы протестировать ответ FPM
1 2 3 4SCRIPT_NAME=/status \ SCRIPT_FILENAME=/status \ REQUEST_METHOD=GET \ cgi-fcgi -bind -connect 127.0.0.1:9000Должен быть возвращён подобный ответ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17Expires: Thu, 01 Jan 1970 00:00:00 GMT Cache-Control: no-cache, no-store, must-revalidate, max-age=0 Content-Type: text/plain pool: www process manager: dynamic start time: 08/Jan/2014:15:04:57 +0530 start since: 93492 accepted conn: 1215 listen queue: 0 max listen queue: 1 listen queue len: 0 idle processes: 26 active processes: 4 total processes: 30 max active processes: 34 max children reached: 0 slow requests: 150Тест ответа PHP-FPM Full Status#
Если вы хотите протестировать ответ PHP-FPM Full Status, вы можете использовать команды:
1 2 3 4 5SCRIPT_NAME=/status \ SCRIPT_FILENAME=/status \ QUERY_STRING=full \ REQUEST_METHOD=GET \ cgi-fcgi -bind -connect 127.0.0.1:9000Запуск собственного скрипта#
Если нужно выполнить собственный скрипт, можно применить следующие команды, указав при этом верный “document root”:
1 2 3 4 5 6SCRIPT_NAME=/custom.php \ SCRIPT_FILENAME=/custom.php \ QUERY_STRING=VAR1 \ DOCUMENT_ROOT=/var/www/example.com/htdocs/ \ REQUEST_METHOD=GET \ cgi-fcgi -bind -connect 127.0.0.1:9000
