RTFM.WIKI

Ordnung muß sein. Ordnung über alles (18+)

Инструменты пользователя

Инструменты сайта


linux:debian:apache_php-fpm

Настройка PHP-FPM и Apache в Debian 12

Исходные данные

  • Debian 12 (Bookworm)
  • PHP из репозитория Sury
  • Apache2 + MPM Event

Задача

  • 2 виртуальных хоста с PHP 8.1 и 8.2 с отдельными fpm-pool с отдельными пользователями
  • Сайт 1: пользователь batman, директория /srv/www/batman
  • Сайт 2: пользователь joker, директория /srv/www/joker
  • Ioncube для PHP 8.1
  • http2
  • SSL Let's Encrypt через модуль mod_md
  • realip для Cloudflare

Установка Apache

apt -y install apache2

Добавить в файл /etc/apache2/apache2.conf

ServerName 127.0.0.1

У меня в Debian 12 по-умолчанию был mpm_event. Для других релизов Debian или для Ubuntu может быть потребуется выключить другие MPM и включить event

a2dismod mpm_prefork
a2dismod mpm_itk
a2enmod mpm_event

SSL/TLS

http2

Проверка через curl http/1.1

# curl -I https://foobar.com
HTTP/1.1 200 OK
Date: Thu, 10 Aug 2023 14:48:56 GMT
Server: Apache/2.4.57 (Debian)
Strict-Transport-Security: max-age=15768000
Last-Modified: Sat, 22 Jul 2023 17:39:30 GMT
ETag: "c-60116dd1dd190"
Accept-Ranges: bytes
Content-Length: 12
Content-Type: text/html

Включаем модуль http2

# a2enmod http2
Enabling module http2.
To activate the new configuration, you need to run:
  systemctl restart apache2

В VirtualHost добавляем

<VirtualHost *:443>
    Protocols h2 http/1.1
</VirtualHost>

Проверка через curl http/2

# curl -I https://foobar.com
HTTP/2 200 
strict-transport-security: max-age=15768000
last-modified: Sat, 22 Jul 2023 17:39:30 GMT
etag: "c-60116dd1dd190"
accept-ranges: bytes
content-length: 12
content-type: text/html
date: Thu, 10 Aug 2023 14:49:41 GMT
server: Apache/2.4.57 (Debian)

Установка PHP

Следуем инструкции из readme.txt

wget -O sury.sh https://packages.sury.org/php/README.txt
chmod +x sury.sh
sh sury.sh

Устанавливаем PHP 8.1

apt install -y php8.1-bcmath php8.2-bz2 php8.1-curl php8.1-fpm php8.1-gd php8.1-intl php8.1-mbstring php8.1-mcrypt php8.1-mysql php8.1-opcache php8.1-xml php8.1-xmlrpc php8.1-zip

В конце установки будет предупреждение

NOTICE: Not enabling PHP 8.1 FPM by default.
NOTICE: To enable PHP 8.1 FPM in Apache2 do:
NOTICE: a2enmod proxy_fcgi setenvif
NOTICE: a2enconf php8.1-fpm
NOTICE: You are seeing this message because you have apache2 package installed.

Устанавливаем PHP 8.2

apt install -y php8.2-bcmath php8.2-bz2 php8.2-curl php8.2-fpm php8.2-gd php8.2-intl php8.2-mbstring php8.2-mcrypt php8.2-mysql php8.2-opcache php8.2-xml php8.2-xmlrpc php8.2-zip

В конце установки будет предупреждение

NOTICE: Not enabling PHP 8.2 FPM by default.
NOTICE: To enable PHP 8.2 FPM in Apache2 do:
NOTICE: a2enmod proxy_fcgi setenvif
NOTICE: a2enconf php8.2-fpm
NOTICE: You are seeing this message because you have apache2 package installed.

PHP-FPM

Проверяем статус php-fpm

systemctl status php8.1-fpm
systemctl status php8.2-fpm

Для работы php-fpm в Apache нужны 2 модуля

Включаем модуль proxy_fcgi и setenvif

# a2enmod proxy_fcgi setenvif
Considering dependency proxy for proxy_fcgi:
Enabling module proxy.
Enabling module proxy_fcgi.
Module setenvif already enabled
To activate the new configuration, you need to run:
  systemctl restart apache2

Для обработки PHP кода добавляем в VirtualHost

Для 8.1

<VirtualHost>
    # <FilesMatch ".+\.ph(ar|p|tml)$">
    # расширенный FilesMatch для .phar, phtml
    <FilesMatch \.php$>
        SetHandler "proxy:unix:/var/run/php/php8.1-fpm.sock|fcgi://localhost/"
    </FilesMatch>
</VirtualHost>

Для 8.2

<VirtualHost>
    <FilesMatch \.php$>
        SetHandler "proxy:unix:/var/run/php/php8.2-fpm.sock|fcgi://localhost/"
    </FilesMatch>
</VirtualHost>

Создаём пользователей и директории для PHP 8.1 и 8.2

useradd batman
passwd batman
useradd joker
passwd joker
mkdir -p /srv/www/batman
mkdir -p /srv/www/joker
chown batman:batman /srv/www/batman
chown joker:joker /srv/www/joker

Для дополнительной безопасности при создании пользователя можно добавить -s /bin/false или /usr/sbin/nologin в качестве шелла. Но из моего опыта 99% людей этого не делает т.к. надо или разворачивать git или придумывать ещё какие-то велосипеды, чтобы загружать/редактировать файлы сайта. Так что каждый решает сам, как лучше. Можно настроить авторизацию по ключам и уже будет лучше. Есть ещё вариант с ACL (setfacl). Но это всё частные случаи. Деплой только через git. К сожалению я и сейчас часто наблюдаю внесение правок на корпоративные сайты Битрикс через FTP. ССЗБ :)

Создаём файлы для вывода phpinfo();

echo '<?php phpinfo(); ?>' > /srv/www/batman/info.php 
echo '<?php phpinfo(); ?>' > /srv/www/joker/info.php 

Создаём виртуальные хосты с именами batman.conf и joker.conf в /etc/apache2/sites-available/

Файл /etc/apache2/sites-available/batman.conf

<VirtualHost *:80>
    Protocols h2 http/1.1
    DocumentRoot /srv/www/batman
    ServerAdmin admin@foobar.com
    ServerName batman.foobar.com
    ServerAlias bruce.foobar.com
 
    CustomLog       "/var/log/apache2/batman_access.log" combined
    ErrorLog        "/var/log/apache2/batman_error_log"
 
    # php-fpm handler
    <FilesMatch ".+\.ph(ar|p|tml)$">
        SetHandler "proxy:unix:/var/run/php/php8.1-fpm.sock|fcgi://localhost"
    </FilesMatch>
</VirtualHost>
 
<Directory /srv/www/batman>
      Options -Indexes +FollowSymLinks
      AllowOverride All
      Require all granted
</Directory>

Файл /etc/apache2/sites-available/joker.conf

<VirtualHost *:80>
    Protocols h2 http/1.1
    DocumentRoot /srv/www/joker
    ServerAdmin admin@foobar.com
    ServerName joker.foobar.com
    ServerAlias harley.foobar.com
 
    CustomLog       "/var/log/apache2/joker_access.log" combined
    ErrorLog        "/var/log/apache2/joker_error_log"
 
    # php-fpm handler
    <FilesMatch ".+\.ph(ar|p|tml)$">
        SetHandler "proxy:unix:/var/run/php/php8.2-fpm.sock|fcgi://localhost"
    </FilesMatch>
</VirtualHost>
 
<Directory /srv/www/joker>
      Options -Indexes +FollowSymLinks
      AllowOverride All
      Require all granted
</Directory>

Включаем сайты через a2ensite

a2ensite batman joker
systemctl restart apache2

FPM-POOL

Файл /etc/php/8.1/fpm/pool.d/batman-pool.conf

[batman-pool]
listen = /var/run/php/php8.1-fpm.sock
listen.owner = batman
listen.group = batman
listen.mode = 0660
user = batman
group = batman
pm = dynamic

Файл /etc/php/8.2/fpm/pool.d/joker-pool.conf

[joker-pool]
listen = /var/run/php/php8.2-fpm.sock
listen.owner = joker
listen.group = joker
listen.mode = 0660
user = joker
group = joker
pm = dynamic

Это минимальный конфиг для работы.

Добавляем пользователя www-data в группу batman и joker.

usermod -a -G batman www-data
usermod -a -G joker www-data

Ещё раз про chown

❌ Неправильно:

  • www-data:www-data
  • batman:www-data
  • www-data:batman

✅ Правильно:

  • batman:batman
  • joker:joker

Если не добавить в группу будет нечто подобное

[proxy:error] [pid 50599:tid 140087278896832] (13)Permission denied: AH02454: FCGI: attempt to connect to Unix domain socket /var/run/php/php8.2-fpm.sock (*:80) failed
[proxy_fcgi:error] [pid 50599:tid 140087278896832] [client 192.168.100.100:17782] AH01079: failed to make connection to backend: httpd-UDS

На что обратить внимание

  • Каждый пул должен использовать отдельный сокет. Если несколько пулов используют один и тот же сокет будут проблемы.
  • Директивы user и group задают пользователя/группу, от имени которых будет запускаться процесс FPM. Они не связаны с пользователем/группой для сокета.
  • Директивы listen.owner и listen.group задают пользователя/группу, которую сокет использует для этого пула.
  • Директивы пула listen.* работают только для пулов. Их нельзя использовать в глобальном конфиге, вы должны указать их для каждого пула.
  • Права к сокету 0660

Для примера и в качестве заметки для себя приложу свой 💥 боевой конфиг

Показать

Скрыть

[rtfm-74]
user = rtfm
group = rtfm
listen = /var/run/php7.4-fpm-rtfm.sock
listen.owner = rtfm
listen.group = rtfm
listen.backlog = 65535
;;listen.mode = 0660
 
pm = dynamic
pm.max_children = 35
pm.start_servers = 5
pm.min_spare_servers = 1
pm.max_spare_servers = 25
 
slowlog = /home/rtfm/data/logs/php-fpm_slow.log
request_slowlog_timeout = 5s
request_terminate_timeout = 300s
 
;;chdir = /
security.limit_extensions = .php .phar
catch_workers_output = yes
 
pm.status_path = /fpm-status
ping.path = /ping
 
php_admin_value[date.timezone] = UTC
php_admin_value[disable_functions] = passthru,shell_exec,system
php_admin_value[cgi.fix_pathinfo] = 0
php_admin_value[memory_limit] = 850M
php_admin_value[post_max_size] = 100M
php_admin_value[upload_max_filesize] = 100M
php_admin_value[max_file_uploads] = 35
 
;; admin_flags
php_admin_flag[expose_php] = off
php_admin_flag[display_errors] = off
php_admin_flag[display_startup_errors] = off
php_admin_flag[log_errors] = on
php_admin_flag[allow_url_fopen] = on
;;php_admin_flag[allow_url_include] = off
;;php_admin_flag[file_uploads] = off
php_admin_flag[session.cookie_httponly] = on
;;php_admin_flag[session.use_cookies] = on
php_admin_flag[session.cookie_secure] = on
 
;; admin_values sessions
php_admin_value[session.cookie_lifetime] = 0
php_admin_value[session.gc_maxlifetime] = 86400
php_admin_value[session.save_handler] = files
php_admin_value[session.save_path] = /home/rtfm/data/sessions
;;php_admin_value[session.name] = rtfm
 
;; admin flag cookie
php_admin_flag[session.cookie_httponly] = on
php_admin_flag[session.use_cookies] = on
php_admin_flag[session.cookie_secure] = on
 
;; admin values other
php_admin_value[error_reporting] = E_ALL & ~E_NOTICE
php_admin_value[upload_tmp_dir] = /home/rtfm/data/tmp
php_admin_value[open_basedir] = /home/rtfm/data:/foobar
 
;; logs
php_admin_value[mail.log] =  /home/rtfm/data/logs/php_mail.log
php_admin_value[error_log] = /home/rtfm/data/logs/php-fpm_error.log
 
;; opcache
;;php_admin_flag[opcache.enable] = 0
;;php_admin_flag[opcache.enable_cli] = 0
;;php_admin_flag[opcache.save_comments] = 1
;;php_admin_flag[opcache.revalidate_freq] = 1
;;php_admin_flag[opcache.validate_timestamps] = 0
;;php_admin_flag[opcache.fast_shutdown] = On
 
;;php_admin_value[opcache.interned_strings_buffer] = 8
;;php_admin_value[opcache.max_accelerated_files] = 16384
;;php_admin_value[opcache.memory_consumption] = 128
;;php_admin_value[opcache.error_log] = "/var/log/php_opcache.log"
;;php_admin_value[opcache.log_verbosity_level] = 1
 
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /home/rtfm/data/tmp
env[TMPDIR] = /home/rtfm/data/tmp
env[TEMP] = /home/rtfm/data/tmp

phpinfo

Проверяем вывод phpinfo по ссылкам batman.foobar.com/info.php и joker.foobar.com/info.php

php 8.1

php 8.2

Проверка FPM-POOL

Создаём файл uid-check.php

echo "<?php echo exec('id'); ?>" > /srv/www/batman/uid-check.php
echo "<?php echo exec('id'); ?>" > /srv/www/joker/uid-check.php

В браузере uid-check.php должен показать следующее

uid=1000(batman) gid=1000(batman) groups=1000(batman)
uid=1001(joker) gid=1001(joker) groups=1001(joker)

FPM status

Настраиваем страницу состояния FPM.

В конфиг пула добавляем

pm.status_path = /status
ping.path = /ping

В конфиг виртуального хоста добавляем

<LocationMatch "/(ping|status)">
    SetHandler "proxy:unix:/var/run/php/php8.1-fpm.sock|fcgi://localhost"
</LocationMatch>
 
<IfModule alias_module>
    Alias /realtime-status "/usr/share/php/8.1/fpm/status.html"
</IfModule>

Формат данных - html, json, openmetrics (какая-то новинка), xml

Также стоит добавить в конфиг Require local или Require ip 192.168.100.0/24, чтобы ограничить доступ к данным.

HTML

JSON

XML

Realtime

URL - https://foobar.com/realtime-status (файл /usr/share/php/ВЕРСИЯ_PHP/fpm/status.html)

GIF 400+ КБ

fpm status realtime

fpm status realtime

На странице fpm_get_status увидел ссылку на PHP файл, который якобы будет работать без дополнительной настройки веб сервера - PHP-FPM real-time status page (Single file without the need for web server configuration).

Cloudflare realip/remoteip

Если для домена подключен Cloudflare, то необходима настройка для отображения реальных IP адресов с помощью mod_remoteip - Apache Module mod_remoteip. Пример настройки есть в статье Cloudflare: SSL сертификаты Full Strict

Ioncube

Основная статья Установка ionCube Loader, но она немного устарела.

Смотрим extension_dir для версии PHP 8.1

# php8.1 -i | grep extension_dir
extension_dir => /usr/lib/php/20210902 => /usr/lib/php/20210902

Копируем .so файл по указанному выше пути

cd ioncube && cp ./ioncube_loader_lin_8.1.so /usr/lib/php/20210902/ioncube_loader_lin_8.1.so

Подключаем .so модуль к PHP

# echo "; priority=10\nzend_extension=/usr/lib/php/20210902/ioncube_loader_lin_8.1.so" >> /etc/php/8.1/mods-available/ioncube.ini

Теперь нужно активировать модуль через phpenmod. Т.к. установлено 2 версии PHP (Managing Multiple Versions)

# update-alternatives --query php
Name: php
Link: /usr/bin/php
Slaves:
 php.1.gz /usr/share/man/man1/php.1.gz
Status: auto
Best: /usr/bin/php8.2
Value: /usr/bin/php8.2
 
Alternative: /usr/bin/php8.1
Priority: 81
Slaves:
 php.1.gz /usr/share/man/man1/php8.1.1.gz
 
Alternative: /usr/bin/php8.2
Priority: 82
Slaves:
 php.1.gz /usr/share/man/man1/php8.2.1.gz

необходимо указать версию PHP, по-умолчанию phpenmod работает с версией PHP 8.2

phpenmod -v 8.1 ioncube

Если не указать priority=10, то будет ошибка

NOTICE: PHP message: PHP Fatal error:  [ionCube Loader] The Loader must appear as the first entry in the php.ini file in Unknown on line 0

И у ioncube будет приоритет 20

# ls -la /etc/php/8.1/fpm/conf.d/ | grep ioncube
lrwxrwxrwx 1 root root   39 Jul 19 12:28 20-ioncube.ini -> /etc/php/8.1/mods-available/ioncube.ini

Проверяем, что модуль подключился

# php8.1 -v
PHP 8.1.21 (cli) (built: Jul 16 2023 11:01:21) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.21, Copyright (c) Zend Technologies
    with the ionCube PHP Loader v12.0.5, Copyright (c) 2002-2022, by ionCube Ltd.
    with Zend OPcache v8.1.21, Copyright (c), by Zend Technologies

mod_proxy

Вроде бы прокси запросы по-умолчанию запрещены, но лучше себя обезопасить дополнительно

If you want to use apache2 as a forward proxy, uncomment the
# 'ProxyRequests On' line and the <Proxy *> block below.
# WARNING: Be careful to restrict access inside the <Proxy *> block.
# Open proxy servers are dangerous both to your network and to the
# Internet at large.

Добавляем в /etc/apache2/mods-enabled/proxy.conf

<Proxy *>
   Require all denied
   Require local
   Require ip 192.168.100.0/24
</Proxy>

Дополнительные настройки

Включаем mod_rewrite - Apache Module mod_rewrite

a2enmod rewrite

Включаем mod_headers - Apache Module mod_headers

a2enmod headers

Ссылки

Обсуждение

ВасильВасиль, 2024/02/09 13:36

Я давно искал, как запускать скрипты от имени другого пользователя, чтобы он мог создавать файлы и папки и удалять их. В хостинг-поддержке спрашивал - никто не знает, как это сделать. Насилу нашел эту статью. Спасибо создателям и авторам. Помогли. От Кво мастера примет всем)

Ваш комментарий. Вики-синтаксис разрешён:
 
linux/debian/apache_php-fpm.txt · Последнее изменение: 2023/09/19 01:35 — dx