# теория · шаг 1 из 5
Как nginx разговаривает с PHP
Коротко
PHP не держит свой веб-сервер. Между nginx и кодом стоит php-fpm - служба, которая выполняет PHP-файлы; nginx разговаривает с ней не по HTTP, а набором именованных параметров.
- Обработчиком становится
fastcgi_pass.rootвнутри такого блока ничего не отдаёт. - Главный параметр -
SCRIPT_FILENAME: какой файл выполнять. Забыл - клиент получаетFile not found.при живом файле на диске. fastcgi_paramнаследуется всё или ничего, ровно какproxy_set_header. Симптом потери - ответ 200 с пустым телом.- Статику nginx отдаёт сам, интерпретатор при этом не просыпается.
Если знаешь, что делает SCRIPT_FILENAME и почему include fastcgi_params пишут в том же блоке - листай до «Теперь сам».
Сначала ответь сам
root /var/www/shop;
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass php:9000;
}
Файл /var/www/shop/index.php на месте, php-fpm жив, конфиг проходит
nginx -t. Что получит клиент на запрос /index.php?
curl -s http://shop.local/index.php
File not found.
Код 404, файл существует (проверено: блок без SCRIPT_FILENAME отдаёт ровно
это). В конфиге не хватает одной строки - той, что называет
php-fpm имя файла. root он не читает и про URL не знает.
На что это похоже
Ты приходишь в архив с бумажкой. Сотрудник за окном не видит ни двери, в которую ты вошёл, ни вывески на здании - он видит только бумажку. Если на ней написан шифр дела, он принесёт дело. Если шифра нет, он скажет «не найдено», и спорить бесполезно: у него нет способа догадаться, что ты имел в виду.
Разговор с php-fpm устроен так же. Он не знает, по какому адресу пришёл запрос и где корень сайта. Ему передают список полей, и одно из них - путь к файлу.
Механизм: не HTTP, а набор параметров
Правило
root /var/www/shop;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
fastcgi_pass здесь указывает на unix:/run/... - юникс-сокет, но бывает и
php:9000, где php - имя хоста (в Docker - контейнера), 9000 - порт.
$document_root - это root текущего блока, $fastcgi_script_name - имя
скрипта из URI; две переменные подряд nginx читает как есть, разделитель между
ними не нужен. Вместе выходит полный путь к файлу.
Адрес бывает двух видов: юникс-сокет быстрее и снаружи недоступен, но требует
прав (пользователь nginx должен читать файл сокета, права задаются в конфиге
пула строками listen.owner, listen.group, listen.mode); TCP нужен, когда
php-fpm в другом контейнере. Симптом неверных прав узнаваем:
connect() to unix:/... failed (13: Permission denied) в error_log и 502 у
клиента.
Строка try_files $uri $uri/ /index.php?$args; даёт красивые ссылки: файла нет,
каталога нет - зови входной скрипт и отдай ему исходные параметры. $args в
конце не украшение: без него ?page=2 теряется и постраничная навигация
ломается «сама по себе».
Разбор: как выглядит потеря SCRIPT_FILENAME
Отличить «параметр не передан» от «файла правда нет» помогает access-лог самого
php-fpm. Три запроса подряд на стенде (формат упрощён, в docker compose logs
каждую строку предваряет имя сервиса вроде php-1 |):
172.25.0.4 - 11/Sep/2026:11:33:54 +0000 "GET /index.php" 200 script=/var/www/html/index.php
172.25.0.4 - 11/Sep/2026:11:33:54 +0000 "GET " 404 script=-
172.25.0.4 - 11/Sep/2026:11:33:54 +0000 "- " 200 script=-
Первая строка - нормальная работа, script указывает на файл. Во второй
имя скрипта пустое (script=-): параметра не было, php-fpm не понял, что
выполнять, и ответил File not found.. Третья - случай хуже: пропал весь набор
параметров, php-fpm ответил 200 и пустое тело (проверено: код=200 длина=0).
Клиент видит белую страницу без единой ошибки.
Кстати про фразу «Primary script unknown», которая гуляет по руководствам: на
забытый SCRIPT_FILENAME свежий php-fpm отвечает File not found., а эту
старую строку больше не пишет (проверено на php-fpm 8.3). Ищи пустое имя
скрипта, а не её.
Разбор: почему include пишут в том же блоке
Третья строка лога выше получилась из такого конфига:
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
location ~ \.php$ {
fastcgi_param X_MY_FLAG one; # ← и весь набор сверху пропал
fastcgi_pass php:9000;
}
Правило то же, что у proxy_set_header из прошлой главы: своя директива в
блоке отменяет весь унаследованный набор. Отсюда порядок, который стоит держать
без исключений: include fastcgi_params; пишут в том же блоке, где стоят
свои параметры, а не этажом выше.
Что ломается без этого
Забыть блок ~ \.php$ целиком - и nginx отдаст файл как обычную статику:
curl -s http://shop.local/index.php
<?php echo "INDEX script=", $_SERVER['SCRIPT_FILENAME'] ?? '-', " uri=", $_SERVER['REQUEST_URI'] ?? '-', "\n";
Исходник ушёл клиенту. Вместе с ним уходят пароли к базе, ключи к платёжкам и всё, что лежит в конфигурационных файлах рядом. Такое случается при переносе сайта на новый сервер, где конфиг собрали заново и одну локацию не дописали.
Обратная ошибка - ждать, что try_files $uri =404; внутри php-локации отдаст
файл клиенту. Он только проверяет наличие: отдавать всё равно будет
контентный обработчик, то есть fastcgi_pass. Зачем тогда строка - разбор в
следующем уроке, там она закрывает конкретную дыру.
Зачем это в работе
Главный выигрыш связки виден на статике. Картинки, стили и скрипты под
регулярку \.php$ не подходят, поэтому их отдаёт nginx напрямую:
curl -so /dev/null -w '%{http_code} %{content_type}\n' http://shop.local/uploads/photo.jpg
200 image/jpeg
Интерпретатор при этом не просыпается вовсе. На сайте, где на страницу приходится полсотни файлов, это разница между «php-fpm занят рисованием страниц» и «php-fpm занят отдачей логотипа».
Порядок блоков в файле при этом не решает ничего, а модификаторы решают:
регулярка спрашивается раньше префиксов (четвёртый раздел), поэтому location /
не перехватывает .php, даже если написан выше.
Теперь сам
Сайт переехал на новый сервер. Конфиг скопировали, но root в блоке server
указал на старый путь /var/www/old-shop, которого на новой машине нет. Что
увидит клиент на /index.php и что в логах?
Клиент получит File not found. и код 404. В логе nginx будет 404, ошибки
open() не будет вовсе: файл ищет не nginx, а php-fpm - ему передали путь
/var/www/old-shop/index.php, и он его не нашёл. В логе php-fpm имя скрипта
будет непустым - это и отличает «неверный root» от «забытого
SCRIPT_FILENAME».
Главное
PHP работает отдельным процессом, и nginx общается с ним набором параметров, а
не HTTP-запросом. Обработчиком становится fastcgi_pass, а главный параметр -
SCRIPT_FILENAME $document_root$fastcgi_script_name: без него клиент получает
File not found. при живом файле, и в логе php-fpm имя скрипта пустое.
Остальные параметры приезжают через include fastcgi_params;, который держат в
одном блоке со своими: fastcgi_param наследуется всё или ничего, а симптом
потери - ответ 200 с пустым телом. Забытый блок ~ \.php$ отдаёт исходники
клиенту.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий