К основному содержимому

Безопасная конфигурация PHP: настройки php.ini для продакшн

Безопасность · 19.04.2026 · 4 мин чтения
Иллюстрация к статье «Безопасная конфигурация PHP: настройки php.ini для продакшн»

Настройки PHP по умолчанию оптимизированы для разработки, а не для продакшн. На публичном сервере они раскрывают версию PHP и внутренние пути, позволяют опасные операции.

Найти php.ini

php --ini | grep "Loaded Configuration"
# /etc/php/8.3/fpm/php.ini

У PHP-FPM и PHP CLI разные файлы конфигурации даже в одной системе — обычно /etc/php/8.3/fpm/php.ini и /etc/php/8.3/cli/php.ini. Правки в одном не применяются к другому: настройки для cron-задач и консольных скриптов нужно менять отдельно.

После правки любого php.ini изменения не подхватываются автоматически — PHP-FPM читает файл один раз при старте и держит его в памяти воркера. Забытый перезапуск сервиса — частая причина, по которой «настройка вроде бы применена», но проверка показывает старое значение.

sudo systemctl restart php8.3-fpm

Обязательные настройки безопасности

display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/errors.log
expose_php = Off

open_basedir = /var/www/site/:/tmp/:/var/lib/php/sessions/

disable_functions = exec,passthru,shell_exec,system,proc_open,popen,phpinfo

file_uploads = On
upload_max_filesize = 10M
max_file_uploads = 5
max_execution_time = 30
memory_limit = 256M

Разбор ошибок конфигурации

display_errors = On на продакшне

Если в этой настройке остался On, любая необработанная ошибка PHP выводится прямо в HTML-ответ и видна каждому посетителю:

Fatal error: Uncaught PDOException: SQLSTATE[HY000] [1045]
Access denied for user 'app'@'localhost' in /var/www/site/db.php:12
Stack trace:
#0 /var/www/site/index.php(5): connect()
#1 {main}
  thrown in /var/www/site/db.php on line 12

Такое сообщение раскрывает полный путь к файлам на сервере, имя пользователя базы данных и структуру кода — этого достаточно, чтобы спланировать следующий шаг атаки. Ошибки должны попадать в error_log, а не в браузер.

open_basedir настроен только для FPM

Если ограничение добавлено в /etc/php/8.3/fpm/php.ini, но не в cli/php.ini, консольные скрипты и cron-задачи выполняются без него. Атака через веб-форму, которая передаёт данные в фоновую задачу на CLI, обходит защиту, рассчитанную только на веб-запросы.

У каждого виртуального хоста на общем сервере может быть собственный пул PHP-FPM со своим файлом настроек — если правите глобальный php.ini, но конкретный сайт запущен через пул с отдельным php_admin_value, переопределяющим нужный параметр, изменение в общем файле не подействует именно на этот сайт.

Что меняет каждая настройка

ПараметрЗначение по умолчаниюЧто даёт хардненная настройка
expose_phpOnУбирает версию PHP из заголовка ответа сервера
display_errorsOnОшибки идут в лог, а не в HTML-ответ посетителю
open_basedirне заданPHP не может читать файлы вне указанных директорий
disable_functionsпустоБлокирует запуск shell-команд из PHP-кода

Безопасность сессий

session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = Strict
session.use_only_cookies = 1
session.use_strict_mode = 1
session.gc_maxlifetime = 1440
session.save_path = /var/lib/php/sessions

Проверка конфигурации

php -r "echo ini_get('expose_php');"
php -r "echo ini_get('display_errors');"

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

curl -I https://ваш-домен/ | grep -i x-powered-by
# Пустой вывод — заголовок не отдаётся, всё верно
open_basedir ограничивает PHP доступом к файлам вне указанных директорий. Это блокирует атаки Path Traversal и LFI (Local File Inclusion), когда злоумышленник пытается прочитать /etc/passwd.
disable_functions можно обойти через PHP-расширения. Это дополнительный слой защиты, но не замена правильному коду и регулярным обновлениям.

Когда жёсткие настройки мешают работе

  • Приложению для обработки изображений или видео нужен внешний бинарник через shell_exec (например, вызов отдельного конвертера) — тогда из disable_functions убирают только конкретно нужную функцию, а не оставляют список пустым полностью.
  • Общий хостинг с несколькими сайтами под разными пользователями на одном PHP-FPM пуле — единый open_basedir для всех не подойдёт, каждому виртуальному хосту нужен собственный FPM-пул со своим значением.
  • Легаси-код, который читает файлы за пределами каталога сайта (например, из общего каталога с шаблонами) — такой код нужно исправлять, расширять open_basedir «на всякий случай» возвращает ту же уязвимость, от которой он защищает.

Настройка веб-сервера перед PHP-FPM разобрана в статье хардненинг Nginx, а защита самого WordPress поверх этих настроек — в хардненинг WordPress.

Статья помогла?
← Назад в базу знаний Задать вопрос поддержке