Безопасность · 12 min read · Jan 22, 2026

Безопасность PHP-FPM/Nginx в средах совместного хостинга (Debian/Ubuntu)

Безопасность PHP-FPM/Nginx в средах совместного хостинга (Debian/Ubuntu)

Версия 1.0
Автор: Фалко Тимме
Следите за мной в Twitter

Если вы хотите использовать nginx и PHP-FPM для сред совместного хостинга, вам следует задуматься о безопасности. В средах Apache/PHP вы можете использовать suExec и/или suPHP, чтобы PHP выполнялся под индивидуальными учетными записями пользователей вместо системного пользователя, такого как www-data. Для PHP-FPM такого нет, но, к счастью, PHP-FPM позволяет нам настроить “пул” для каждого веб-сайта, который заставляет PHP-скрипты выполняться от имени пользователя/группы, определенных в этом пуле. Это дает вам все преимущества suPHP, и кроме того, у вас не будет проблем с передачей FTP или SCP, потому что PHP-скрипты не должны принадлежать конкретному пользователю/группе, чтобы выполняться от имени пользователя/группы, определенных в пуле.

Я не даю никаких гарантий, что это сработает для вас!

1 Предварительная заметка

Я использую виртуальный хост с именем www.example.com / example.com с корневым каталогом /var/www/www.example.com/web.

У вас должна быть работающая установка LEMP, как показано в этих учебниках:

  • Установка Nginx с поддержкой PHP5 и MySQL на Debian Squeeze
  • Установка Nginx с поддержкой PHP5 (и PHP-FPM) и MySQL на Ubuntu 11.04

Заметка для пользователей Ubuntu:

Поскольку мы должны выполнять все шаги из этого учебника с правами root, мы можем либо предварять все команды в этом учебнике строкой sudo, либо стать root прямо сейчас, набрав

sudo su

2 Что у нас есть на данный момент

На Debian/Ubuntu каталог пула PHP-FPM - это /etc/php5/fpm/pool.d/ - здесь будут созданы новые пулы. Файл php.ini, используемый PHP-FPM, - это /etc/php5/fpm/php.ini. Уже есть один пул, www.conf - давайте взглянем на него:

vi /etc/php5/fpm/pool.d/www.conf

| ; Начать новый пул с именем 'www'. ; переменная $pool может быть использована в любой директиве и будет заменена на ; имя пула ('www' здесь) [www] ; Префикс для пула ; Он применяется только к следующим директивам: ; - 'slowlog' ; - 'listen' (unixsocket) ; - 'chroot' ; - 'chdir' ; - 'php_values' ; - 'php_admin_values' ; Если не установлено, применяется глобальный префикс (или /usr). ; Примечание: Эта директива также может быть относительной к глобальному префиксу. ; Значение по умолчанию: нет ;prefix = /path/to/pools/$pool ; Адрес, на котором следует принимать запросы FastCGI. ; Допустимые синтаксисы: ; 'ip.add.re.ss:port' - чтобы слушать на TCP-сокете на конкретном адресе на ; конкретном порту; ; 'port' - чтобы слушать на TCP-сокете на всех адресах на ; конкретном порту; ; '/path/to/unix/socket' - чтобы слушать на unix-сокете. ; Примечание: Это значение обязательно. listen = 127.0.0.1:9000 ; Установить очередь listen(2). Значение '-1' означает неограниченное. ; Значение по умолчанию: 128 (-1 на FreeBSD и OpenBSD) ;listen.backlog = -1 ; Список ipv4-адресов клиентов FastCGI, которым разрешено подключаться. ; Эквивалентно переменной окружения FCGI_WEB_SERVER_ADDRS в оригинальном ; PHP FCGI (5.2.2+). Имеет смысл только с TCP-сокетом. Каждый адрес ; должен быть разделен запятой. Если это значение оставлено пустым, подключения будут ; приниматься с любого ip-адреса. ; Значение по умолчанию: любой ;listen.allowed_clients = 127.0.0.1 ; Установить разрешения для unix-сокета, если он используется. В Linux права на чтение/запись ; должны быть установлены, чтобы разрешить подключения от веб-сервера. Многие ; системы, производные от BSD, разрешают подключения независимо от разрешений. ; Значения по умолчанию: пользователь и группа установлены как работающий пользователь ; режим установлен на 0666 ;listen.owner = www-data ;listen.group = www-data ;listen.mode = 0666 ; Unix пользователь/группа процессов ; Примечание: пользователь обязателен. Если группа не установлена, будет использована группа ; пользователя по умолчанию. user = www-data group = www-data ; Выберите, как менеджер процессов будет контролировать количество дочерних процессов. ; Возможные значения: ; static - фиксированное количество (pm.max_children) дочерних процессов; ; dynamic - количество дочерних процессов устанавливается динамически на основе ; следующих директив: ; pm.max_children - максимальное количество дочерних процессов, которые могут ; быть активны одновременно. ; pm.start_servers - количество дочерних процессов, создаваемых при запуске. ; pm.min_spare_servers - минимальное количество дочерних процессов в 'бездеятельном' ; состоянии (ожидание обработки). Если количество ; 'бездеятельных' процессов меньше этого ; числа, то будут созданы некоторые дочерние процессы. ; pm.max_spare_servers - максимальное количество дочерних процессов в 'бездеятельном' ; состоянии (ожидание обработки). Если количество ; 'бездеятельных' процессов больше этого ; числа, то некоторые дочерние процессы будут убиты. ; Примечание: Это значение обязательно. pm = dynamic ; Количество дочерних процессов, которые будут созданы, когда pm установлен на 'static' и ; максимальное количество дочерних процессов, которые будут созданы, когда pm установлен на 'dynamic'. ; Это значение устанавливает предел на количество одновременных запросов, которые будут ; обслуживаться. Эквивалентно директиве ApacheMaxClients с mpm_prefork. ; Эквивалентно переменной окружения PHP_FCGI_CHILDREN в оригинальном PHP ; CGI. ; Примечание: Используется, когда pm установлен на 'static' или 'dynamic' ; Примечание: Это значение обязательно. pm.max_children = 50 ; Количество дочерних процессов, созданных при запуске. ; Примечание: Используется только когда pm установлен на 'dynamic' ; Значение по умолчанию: min_spare_servers + (max_spare_servers - min_spare_servers) / 2 ;pm.start_servers = 20 ; Желаемое минимальное количество бездействующих серверных процессов. ; Примечание: Используется только когда pm установлен на 'dynamic' ; Примечание: Обязательно, когда pm установлен на 'dynamic' pm.min_spare_servers = 5 ; Желаемое максимальное количество бездействующих серверных процессов. ; Примечание: Используется только когда pm установлен на 'dynamic' ; Примечание: Обязательно, когда pm установлен на 'dynamic' pm.max_spare_servers = 35 ; Количество запросов, которые каждый дочерний процесс должен выполнить перед перезапуском. ; Это может быть полезно для обхода утечек памяти в сторонних библиотеках. Для ; бесконечной обработки запросов укажите '0'. Эквивалентно PHP_FCGI_MAX_REQUESTS. ; Значение по умолчанию: 0 ;pm.max_requests = 500 ; URI для просмотра страницы состояния FPM. Если это значение не установлено, ни один URI не будет ; распознан как страница состояния. По умолчанию страница состояния показывает следующую ; информацию: ; принятые соединения - количество запросов, принятых пулом; ; пул - имя пула; ; менеджер процессов - статический или динамический; ; бездействующие процессы - количество бездействующих процессов; ; активные процессы - количество активных процессов; ; всего процессов - количество бездействующих + активных процессов. ; максимальное количество дочерних процессов достигнуто - количество раз, когда предел процессов был достигнут, ; когда pm пытается запустить больше дочерних процессов (работает только для ; pm 'dynamic') ; Значения 'бездеятельные процессы', 'активные процессы' и 'всего процессов' обновляются каждую секунду. ; Значение 'принятые соединения' обновляется в реальном времени. ; Пример вывода: ; принятые соединения: 12073 ; пул: www ; менеджер процессов: статический ; бездействующие процессы: 35 ; активные процессы: 65 ; всего процессов: 100 ; максимальное количество дочерних процессов достигнуто: 1 ; По умолчанию вывод страницы состояния форматируется как text/plain. Передача либо ; 'html', либо 'json' в качестве строки запроса вернет соответствующий вывод ; синтаксиса. Пример: ; http://www.foo.bar/status ; http://www.foo.bar/status?json ; http://www.foo.bar/status?html ; Примечание: Значение должно начинаться с ведущего слэша (/). Значение может быть ; любым, но может быть не лучшей идеей использовать расширение .php, иначе это ; может конфликтовать с реальным PHP-файлом. ; Значение по умолчанию: не установлено ;pm.status_path = /status ; URI пинга для вызова страницы мониторинга FPM. Если это значение не установлено, ни один ; URI не будет распознан как страница пинга. Это может быть использовано для тестирования извне, ; что FPM жив и отвечает, или для ; - создания графика доступности FPM (rrd или подобное); ; - удаления сервера из группы, если он не отвечает (балансировка нагрузки); ; - вызова оповещений для операционной команды (24/7). ; Примечание: Значение должно начинаться с ведущего слэша (/). Значение может быть ; любым, но может быть не лучшей идеей использовать расширение .php, иначе это ; может конфликтовать с реальным PHP-файлом. ; Значение по умолчанию: не установлено ;ping.path = /ping ; Эта директива может быть использована для настройки ответа на запрос пинга. Ответ ; форматируется как text/plain с кодом ответа 200. ; Значение по умолчанию: pong ;ping.response = pong ; Таймаут для обслуживания одного запроса, после которого рабочий процесс будет ; убит. Этот параметр следует использовать, когда параметр ini 'max_execution_time' ; по какой-то причине не останавливает выполнение скрипта. Значение '0' означает 'выключено'. ; Доступные единицы: s(екунды)(по умолчанию), m(инуты), h(часы) или d(ни) ; Значение по умолчанию: 0 ;request_terminate_timeout = 0 ; Таймаут для обслуживания одного запроса, после которого трассировка PHP будет ; сброшена в файл 'slowlog'. Значение '0s' означает 'выключено'. ; Доступные единицы: s(екунды)(по умолчанию), m(инуты), h(часы) или d(ни) ; Значение по умолчанию: 0 ;request_slowlog_timeout = 0 ; Файл журнала для медленных запросов ; Значение по умолчанию: не установлено ; Примечание: slowlog обязателен, если request_slowlog_timeout установлен ;slowlog = log/$pool.log.slow ; Установить rlimit для открытых файловых дескрипторов. ; Значение по умолчанию: системное определенное значение ;rlimit_files = 1024 ; Установить rlimit для максимального размера ядра. ; Возможные значения: 'неограниченный' или целое число, большее или равное 0 ; Значение по умолчанию: системное определенное значение ;rlimit_core = 0 ; Chroot в этот каталог при старте. Это значение должно быть определено как ; абсолютный путь. Когда это значение не установлено, chroot не используется. ; Примечание: вы можете префиксировать '$prefix', чтобы chroot в префикс пула или один ; из его подкаталогов. Если префикс пула не установлен, будет использован глобальный префикс. ; Примечание: chrooting - это отличная функция безопасности и должна использоваться всякий раз, ; когда это возможно. Однако все пути PHP будут относительными к chroot ; (error_log, sessions.save_path, ...). ; Значение по умолчанию: не установлено ;chroot = ; Chdir в этот каталог при старте. ; Примечание: может использоваться относительный путь. ; Значение по умолчанию: текущий каталог или / при chroot chdir = / ; Перенаправить stdout и stderr рабочих процессов в основной журнал ошибок. Если не установлено, stdout и ; stderr будут перенаправлены в /dev/null в соответствии со спецификациями FastCGI. ; Примечание: в высоконагруженной среде это может вызвать некоторую задержку во времени ; обработки страницы (несколько мс). ; Значение по умолчанию: нет ;catch_workers_output = yes ; Передать переменные окружения, такие как LD_LIBRARY_PATH. Все $VARIABLEs берутся из ; текущей среды. ; Значение по умолчанию: чистая среда ;env[HOSTNAME] = $HOSTNAME ;env[PATH] = /usr/local/bin:/usr/bin:/bin ;env[TMP] = /tmp ;env[TMPDIR] = /tmp ;env[TEMP] = /tmp ; Дополнительные определения php.ini, специфичные для этого пула рабочих процессов. Эти настройки ; перезаписывают значения, ранее определенные в php.ini. Директивы такие же, как и SAPI PHP: ; php_value/php_flag - вы можете установить классические определения ini, которые могут ; быть перезаписаны из вызова PHP 'ini_set'. ; php_admin_value/php_admin_flag - эти директивы не будут перезаписаны вызовом PHP 'ini_set' ; Для php_*flag допустимые значения: on, off, 1, 0, true, false, yes или no. ; Определение 'extension' загрузит соответствующее общее расширение из ; extension_dir. Определение 'disable_functions' или 'disable_classes' не будет ; перезаписывать ранее определенные значения php.ini, но добавит новое значение ; вместо этого. ; Примечание: параметры пути INI могут быть относительными и будут расширены с префиксом ; (пул, глобальный или /usr) ; Значение по умолчанию: ничего не определено по умолчанию, кроме значений в php.ini и ; указанных при запуске с аргументом -d ;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f [email protected] ;php_flag[display_errors] = off ;php_admin_value[error_log] = /var/log/fpm-php.www.log ;php_admin_flag[log_errors] = on ;php_admin_value[memory_limit] = 32M |

Как вы видите, этот пул слушает на порту 9000 на локальном хосте (127.0.0.1), и он выполняется от имени пользователя и группы www-data.

Давайте взглянем на конфигурацию PHP в вашем виртуальном хосте:

vi /etc/nginx/sites-available/example.com.vhost

| server { [...] location ~ \.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_script_name; include /etc/nginx/fastcgi_params; } [...] } |

Важная часть - это строка fastcgi_pass 127.0.0.1:9000; - это заставляет nginx передавать запросы PHP процессу PHP-FPM, слушающему на порту 9000 на локальном хосте (127.0.0.1) - как вы помните, это наш пул, определенный в /etc/php5/fpm/pool.d/www.conf, что означает, что PHP-скрипты выполняются от имени пользователя и группы www-data.

3 Определение индивидуального пула для каждого веб-сайта

Мой веб-сайт example.com принадлежит пользователю web1 и группе client0, поэтому я хочу, чтобы мои PHP-скрипты выполнялись от имени этого пользователя и группы. Поэтому я определяю новый пул /etc/php5/fpm/pool.d/example.com.conf:

vi /etc/php5/fpm/pool.d/example.com.conf

| [example.com] listen = 127.0.0.1:9001 listen.allowed_clients = 127.0.0.1 user = web1 group = client0 pm = dynamic pm.max_children = 50 pm.start_servers = 20 pm.min_spare_servers = 5 pm.max_spare_servers = 35 chdir = / |

Как вы видите, я заставляю этот пул слушать на порту 9001 вместо 9000, и я определяю пользователя как web1 и группу как client0. Вы можете определить столько пулов, сколько хотите, но убедитесь, что вы используете неиспользуемый порт для каждого пула (9002, 9003 и т.д.).

Перезагрузите PHP-FPM:

/etc/init.d/php5-fpm reload

Теперь мы изменяем конфигурацию нашего виртуального хоста, чтобы использовать новый пул. Все, что вам нужно изменить, это порт в строке fastcgi_pass:

vi /etc/nginx/sites-available/example.com.vhost

| server { [...] location ~ \.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9001; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_script_name; include /etc/nginx/fastcgi_params; } [...] } |

Перезагрузите nginx после этого:

/etc/init.d/nginx reload

Вот и все! PHP-скрипты теперь выполняются от имени пользователя web1 и группы client0.

Вы можете сделать PHP еще более безопасным, изменив настройки PHP индивидуально для каждого виртуального хоста. Посмотрите внизу файла /etc/php5/fpm/pool.d/www.conf, там есть несколько примеров, как это сделать.

Например, вы можете установить open_basedir или disable_functions в пуле /etc/php5/fpm/pool.d/example.com.conf.

vi /etc/php5/fpm/pool.d/example.com.conf

| [example.com] listen = 127.0.0.1:9001 listen.allowed_clients = 127.0.0.1 user = web1 group = client0 pm = dynamic pm.max_children = 50 pm.start_servers = 20 pm.min_spare_servers = 5 pm.max_spare_servers = 35 chdir = / php_admin_value[open_basedir] = /var/www/www.example.com:/usr/share/php5:/tmp:/usr/share/phpmyadmin:/etc/phpmyadmin:/var/lib/phpmyadmin php_admin_value[disable_functions] = dl,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source |

Перезагрузите PHP-FPM:

/etc/init.d/php5-fpm reload

3.1 Использование сокетов вместо TCP-соединений

До сих пор мы использовали TCP-соединения для нашего пула PHP-FPM (127.0.0.1:9000, 127.0.0.1:9001 и т.д.). Это вызывает некоторые накладные расходы. К счастью, мы можем использовать Unix-сокеты вместо TCP-соединений для наших пулов и избавиться от этих накладных расходов. Поэтому Unix-сокеты более производительны, чем TCP-соединения.

Я хочу, чтобы сокеты создавались в каталоге /var/run/php5-fpm, поэтому сначала мы должны создать этот каталог:

mkdir /var/run/php5-fpm

Чтобы использовать Unix-сокет, мы просто изменяем строку listen в нашем определении пула, комментируем или удаляем строку listen.allowed_clients (имеет смысл только для TCP-соединений) и добавляем строки listen.owner (определяет владельца сокета), listen.group (определяет группу сокета) и listen.mode (определяет разрешения сокета):

vi /etc/php5/fpm/pool.d/example.com.conf

| [example.com] listen = /var/run/php5-fpm/example.com.sock ;listen.allowed_clients = 127.0.0.1 listen.owner = web1 listen.group = client0 listen.mode = 0660 user = web1 group = client0 pm = dynamic pm.max_children = 50 pm.start_servers = 20 pm.min_spare_servers = 5 pm.max_spare_servers = 35 chdir = / |

Перезагрузите PHP-FPM после этого:

/etc/init.d/php5-fpm reload

Посмотрите в каталог /var/run/php5-fpm:

ls -l /var/run/php5-fpm

Вы должны найти сокет example.com.sock там с разрешениями 0660, принадлежащий пользователю web1 и группе client0:

root@server1:~# ls -l /var/run/php5-fpm  
 total 0  
 srw-rw---- 1 web1 client0 0 2011-09-21 11:08 example.com.sock  
 root@server1:~#

Наконец, мы должны изменить строку fastcgi_pass в нашем виртуальном хосте nginx на fastcgi_pass unix:/var/run/php5-fpm/example.com.sock;:

vi /etc/nginx/sites-available/example.com.vhost

| server { [...] location ~ \.php$ { try_files $uri =404; fastcgi_pass unix:/var/run/php5-fpm/example.com.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_script_name; include /etc/nginx/fastcgi_params; } [...] } |

Перезагрузите nginx после этого:

/etc/init.d/nginx reload

Вот и все!

4 Ссылки

Об авторе

Фалко Тимме является владельцем Timme Hosting (ультра-быстрый веб-хостинг на nginx). Он является ведущим куратором HowtoForge (с 2005 года) и одним из основных разработчиков ISPConfig (с 2000 года). Он также внес вклад в книгу O’Reilly “Администрирование систем Linux”.

Share: X/Twitter LinkedIn

Get new posts in your inbox

No spam. Unsubscribe anytime.