# теория · шаг 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 проверь путь SCRIPT_FILENAME, который nginx передаёт php-fpm, и весь набор FastCGI-параметров в выбранном location. Наличие файла на диске недостаточно, если интерпретатор получил другой путь. Маршрут без обработчика PHP может отдать исходник, поэтому проверь ответ клиента.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий