Безопасная настройка Nginx и PHP-FPM: чек-лист для сервера
Три белые коробки в ряд, в крышку средней проходит зелёный шарик, крышки боковых закрыты наглухо

Безопасная настройка Nginx и PHP-FPM: чек-лист для сервера

Безопасная связка Nginx и PHP-FPM: скрыть версии, закрыть выполнение PHP в лишних каталогах, изолировать пулы, включить TLS и лимиты.

Безопасная настройка Nginx и PHP-FPM

Чтобы связка Nginx и PHP-FPM была безопасной, нужно сделать четыре вещи: передавать в PHP только реально существующие скрипты, запускать каждый сайт в отдельном пуле под своим пользователем, отключить опасные функции и раскрытие информации в php.ini, а также закрыть периметр через TLS и лимиты запросов.

Большинство взломов PHP-сайтов происходит не из-за уязвимостей самих Nginx или PHP-FPM, а из-за ошибок в конфигурации: лишних правах, исполняемых файлах в каталоге загрузок и общем пользователе на всех сайтах.

В статье собран практический чек-лист: конфигурации, значения директив и порядок внедрения, который можно применить на VPS или выделенном сервере без остановки сайта.

Почему связка Nginx и PHP-FPM нуждается в укреплении

Nginx сам не умеет выполнять PHP. Он принимает запрос, определяет, что это динамический скрипт, и по протоколу FastCGI передаёт его менеджеру процессов PHP-FPM. Именно на этом стыке чаще всего возникают проблемы: веб-сервер передаёт в PHP больше, чем должен, а PHP-процесс получает больше прав, чем ему нужно.

Типичные сценарии атак выглядят так. Злоумышленник загружает через форму файл с расширением изображения, но с PHP-кодом внутри, и добивается его выполнения. Либо через уязвимость в одном плагине получает доступ ко всем сайтам на сервере, потому что все они работают от одного пользователя. Либо читает файлы `.env`, `.git` и резервные копии, которые случайно оказались в корне сайта.

Хорошая новость: все эти сценарии закрываются обычными настройками, которые не требуют дополнительного ПО. Ниже разберём защиту по слоям: от внешнего периметра до прав отдельного PHP-процесса.

путь запроса / слои защиты
01 / периметр

TLS и лимиты

HTTPS, актуальные протоколы, limit_req и limit_conn отсекают перебор и грубые нагрузки.

02 / nginx

Фильтрация запросов

Скрытые версии, запрет служебных файлов, ограничение методов, try_files для PHP.

03 / сокет

Доступ к PHP-FPM

Unix-сокет с правами 0660, отдельный сокет и пул для каждого сайта.

04 / php

Права процесса

Свой пользователь, open_basedir, disable_functions, запрет вывода ошибок.

Укрепление Nginx: базовые директивы

Начните с настроек, которые не влияют на работу сайта, но сразу сокращают поверхность атаки.

Скройте версию сервера. В блоке `http` добавьте `server_tokens off;`. Сканеры больше не увидят точную версию Nginx в заголовке ответа и на страницах ошибок.

Закройте служебные файлы. Запретите доступ ко всем скрытым файлам и каталогам, кроме `.well-known`, который нужен для выпуска сертификатов: `location ~ /\.(?!well-known) { deny all; }`. Так вы защитите `.git`, `.env`, `.htaccess` и `.htpasswd`.

Ограничьте методы. Большинству сайтов достаточно GET, HEAD и POST. Остальные можно отсекать через `if ($request_method !~ ^(GET|HEAD|POST)$) { return 405; }`.

Добавьте защитные заголовки. `X-Content-Type-Options: nosniff`, `X-Frame-Options: SAMEORIGIN` и `Referrer-Policy: strict-origin-when-cross-origin` задаются директивой `add_header ... always;`. Для сложных сайтов подберите отдельную политику `Content-Security-Policy`, начиная с режима отчётов, чтобы не сломать скрипты.

Ограничьте размер тела запроса. Директива `client_max_body_size` должна соответствовать реальным потребностям: если на сайте нет загрузки больших файлов, хватит 2–10 МБ.

nginx / безопасный location для php
server_tokens off;

location ~ \.php$ {
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_pass unix:/run/php/site.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

location ~* ^/(uploads|storage|cache)/.*\.php$ {
    deny all;
}

location ~ /\.(?!well-known) {
    deny all;
}
try_filesне пропускает в PHP несуществующие файлы
deny allзапрещает выполнение PHP в каталогах загрузок
unix-сокетвместо открытого порта 9000

Что делает этот блок конфигурации

Главное здесь — директива `try_files $uri =404;` внутри `location` для PHP. Она проверяет, существует ли запрошенный файл, и если нет, сразу возвращает 404, не обращаясь к PHP-FPM. Благодаря этому закрывается целый класс атак, когда в путь подставляют лишние сегменты вроде `/image.jpg/evil.php`.

Вторая важная строка — `fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;`. Она явно задаёт, какой именно скрипт выполнит PHP. В современных конфигурациях это уже есть в `fastcgi.conf`, но при ручной настройке про неё легко забыть.

Отдельный блок для каталогов загрузок страхует от ошибок приложения. Даже если злоумышленник загрузит файл с PHP-кодом, Nginx не передаст его на выполнение. Порядок правил имеет значение: регулярные выражения проверяются сверху вниз, поэтому запрещающий блок должен стоять до общего `location ~ \.php$`, иначе его правило не сработает. Выполните `nginx -t` после каждого изменения.

Частая ошибка: разрешено выполнение PHP в каталоге загрузок

Самый распространённый вектор взлома — файл, загруженный как «картинка» или «документ», который затем исполняется как PHP.

Как делать не надо

  • Оставлять в конфигурации общий location ~ \.php$ без try_files и без запрета для uploads
  • Включать cgi.fix_pathinfo=1 в php.ini при использовании PHP-FPM без проверки существования файла
  • Выдавать каталогу загрузок права 777 «чтобы всё работало»
  • Полагаться только на проверку расширения файла в самом приложении

Как правильно

  1. 01

    Добавить try_files $uri =404 в блок обработки PHP и установить cgi.fix_pathinfo=0

  2. 02

    Запретить выполнение PHP в uploads, storage и cache через отдельный location с deny all

  3. 03

    Использовать права 755 для каталогов и 644 для файлов, владелец — пользователь сайта

  4. 04

    Хранить пользовательские загрузки вне корня сайта или отдавать их отдельным статическим location

Изоляция сайтов через отдельные пулы PHP-FPM

По умолчанию PHP-FPM создаёт один пул `www`, который работает от пользователя `www-data`. Если на сервере несколько сайтов, все они выполняются с одними и теми же правами. Уязвимость в одном плагине даёт доступ к файлам, конфигурации и базе данных остальных проектов.

Правильный подход — отдельный пул на каждый сайт. У пула свой системный пользователь, свой сокет и свои ограничения PHP. Взлом одного проекта остаётся локальным: процесс просто не имеет прав на чужие каталоги.

Дополнительный плюс изоляции — предсказуемая нагрузка. Вы задаёте `pm.max_children` отдельно для каждого сайта, и один перегруженный проект не съедает всю память сервера.

Как настроить изолированный пул PHP-FPM

  1. 01

    Создайте системного пользователя без оболочки и домашнего каталога логина, например site, и передайте ему каталог проекта командой chown -R site:site /var/www/site.

  2. 02

    Скопируйте файл пула в /etc/php/8.3/fpm/pool.d/site.conf (путь зависит от версии PHP), замените имя пула на [site] и укажите user = site и group = site.

  3. 03

    Задайте собственный сокет: listen = /run/php/site.sock, а также listen.owner = www-data, listen.group = www-data и listen.mode = 0660, чтобы доступ был только у Nginx.

  4. 04

    Ограничьте процессы: pm = dynamic, pm.max_children подберите по объёму памяти (обычно доступная RAM, делённая на средний размер процесса), pm.max_requests = 500 для перезапуска «разбухших» воркеров.

  5. 05

    Добавьте ограничения на уровне пула: php_admin_value[open_basedir] = /var/www/site:/tmp и php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen.

  6. 06

    Удалите или отключите стандартный пул www, проверьте конфигурацию командой php-fpm8.3 -t, примените через systemctl reload php8.3-fpm и обновите fastcgi_pass в Nginx на новый сокет.

Настройки php.ini, которые стоит проверить

Ограничения в пуле дополняют глобальные настройки PHP. Их значения лучше задавать в отдельном файле в каталоге `conf.d`, а не править основной `php.ini`: так проще обновляться и отслеживать изменения.

Обратите внимание на функции, отключаемые через `disable_functions`. Некоторые CMS и библиотеки используют `exec` или `proc_open` для легитимных задач, например обработки изображений или очередей. Перед отключением проверьте, что приложение без них работает, лучше сначала на тестовой копии.

Ниже приведена таблица основных директив, которые закрывают самые частые проблемы.

ДирективаРекомендуемое значениеЗачем нужна
expose_phpOffНе добавляет версию PHP в заголовки ответа
display_errorsOffНе показывает посетителям пути и ошибки кода
log_errorsOnОшибки пишутся в журнал, а не на экран
cgi.fix_pathinfo0Блокирует выполнение «подменённых» путей
allow_url_includeOffЗапрещает подключение удалённых файлов через include
session.cookie_httponly1Скрывает cookie сессии от JavaScript
session.cookie_secure1 (при HTTPS)Передаёт cookie сессии только по HTTPS
open_basedirкаталог сайта и /tmpОграничивает чтение и запись за пределами проекта

TLS, лимиты запросов и защита от перебора

Периметр — первый слой защиты. Включите HTTPS на всех сайтах, оставьте только TLS 1.2 и TLS 1.3 (`ssl_protocols TLSv1.2 TLSv1.3;`) и настройте автоматическое продление сертификата. Добавьте перенаправление с HTTP на HTTPS и заголовок `Strict-Transport-Security` после того, как убедитесь, что весь сайт корректно работает по HTTPS.

Далее ограничьте частоту запросов. В блоке `http` объявите зону `limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;`, а в `location` страницы входа (например, `/wp-login.php` или `/admin`) подключите её директивой `limit_req zone=login burst=10 nodelay;`. Это не остановит распределённую атаку, но надёжно режет простой перебор паролей с одного адреса. Для общего потока добавьте `limit_conn` и разумные таймауты: `client_body_timeout`, `client_header_timeout` и `send_timeout`.

Не забывайте про файрвол. Наружу должны быть открыты только порты 80, 443 и SSH (лучше по ключам и с ограничением адресов). Порты PHP-FPM, базы данных и кэша не должны быть доступны из интернета.

Что даёт такая конфигурация

  1. 01

    Взлом одного сайта не приводит к компрометации остальных проектов на сервере

  2. 02

    Загруженные пользователями файлы нельзя выполнить как PHP-код

  3. 03

    Сканеры не получают версии ПО и служебные файлы вроде .env и .git

  4. 04

    Перебор паролей и всплески запросов не нагружают PHP-FPM

  5. 05

    Лимиты процессов защищают память сервера от исчерпания

  6. 06

    Ошибки пишутся в журнал, а посетители не видят внутренние пути и детали кода

Логи, мониторинг и обновления

Настроенная защита работает, только если вы видите, что происходит на сервере. Включите отдельные журналы доступа и ошибок для каждого сайта, настройте ротацию через logrotate и периодически просматривайте записи с кодами 403, 404 и 499, а также всплески запросов к `xmlrpc.php`, `wp-login.php` и `/admin`. Для автоматической блокировки адресов, которые много раз подряд ошибаются, подойдёт Fail2ban.

Следите и за внешним видом сайта в поиске. Яндекс Вебмастер и Google Search Console уведомляют, если роботы обнаружили вредоносный код или подозрительные страницы. Эти сообщения стоит подключить к почте: заражение, найденное вовремя, обходится гораздо дешевле.

И наконец, обновления. Подписывайтесь на рассылки по безопасности вашего дистрибутива и включайте автоматические обновления безопасности (например, unattended-upgrades в Debian и Ubuntu). Обновлять PHP нужно до версии, которая ещё получает исправления безопасности: устаревшие ветки остаются без патчей.

чек-лист / аудит за 15 минут
server_tokens off и expose_php = Off
try_files $uri =404 в блоке PHP
PHP не выполняется в uploads и cache
Отдельный пул и пользователь на каждый сайт
Unix-сокет с правами 0660, порт 9000 закрыт
open_basedir и disable_functions заданы
HTTPS, TLS 1.2+, limit_req на входе
Логи, Fail2ban и автообновления безопасности

Итоги: безопасность Nginx и PHP-FPM — это набор простых правил

Надёжная защита строится на изоляции: отдельный пул на сайт, минимальные права и только то, что действительно нужно приложению.
  1. 01

    Передавайте в PHP только существующие файлы через try_files и запрещайте выполнение кода в каталогах загрузок

  2. 02

    Запускайте каждый сайт в своём пуле PHP-FPM под отдельным пользователем и с собственным сокетом

  3. 03

    Отключите раскрытие информации и лишние функции в php.ini, задайте open_basedir

  4. 04

    Включите HTTPS, лимиты запросов и закройте лишние порты файрволом

  5. 05

    Ведите логи, обновляйте ПО и проверяйте конфигурацию командами nginx -t и php-fpm -t перед каждым reload

Начните с самых простых шагов: try_files, запрет PHP в uploads и отдельные пулы. Они занимают меньше часа и закрывают основную часть реальных атак. Остальное внедряйте постепенно, проверяя каждое изменение на тестовой копии. Если нужна помощь с аудитом или миграцией на изолированные пулы, обратитесь к специалистам.

Часто задаваемые вопросы

Проведём аудит безопасности вашего Nginx и PHP-FPM

Проверим конфигурацию, права и пулы, а затем дадим список правок по приоритету

Бесплатный первичный аудитПравки без простояОтчёт с приоритетами

Проверено на проектах с нагрузкой до 1 млн визитов

Политика конфиденциальности

При оставлении заявки на ресурсе «https://gurucontext.ru» пользователи предоставляют следующие сведения:

  • Имя
  • Контактный телефон или Telegram
  • Адрес сайта пользователя (не обязательно)

Также администрация сайта получает данные об IP-адресе посетителей, типе браузера, времени нахождения на сайте и прочие подобные сведения через сервисы статистики.

Использование информации

Вся полученная информация используется администрацией «https://gurucontext.ru» исключительно в целях связи с клиентом.

Защита персональных данных

Компания «https://gurucontext.ru» обязуется не разглашать сведения, полученные от пользователей, и хранит их в защищённом виде.

Предоставление данных третьим лицам

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

Контакты

Телефон: +7 (499) 955-47-00.
E-mail: info@gurucontext.ru.