RTFM.WIKI

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

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

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


Stylesheet conf/userstyle.css not found, please contact the developer of "dokuwiki_2024" template.
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

<tabbox php 8.1>

<tabbox php 8.2> </tabbox>

Проверка 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

URL

<tabbox HTML> <tabbox HTML full> </tabbox>

JSON

URL

<tabbox JSON> <tabbox JSON full> </tabbox>

XML

URL

<tabbox XML>

<tabbox XML full> </tabbox>

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

Ссылки

Обсуждение

Error: Call to undefined method helper_plugin_avatar::getXHTML()

Error: Call to undefined method helper_plugin_avatar::getXHTML()

An unforeseen error has occured. This is most likely a bug somewhere. It might be a problem in the discussion plugin.

More info has been written to the DokuWiki error log.

To improve the quality of our site, we track visitors anonymously. It doesn't hurt you, but helps us tremendously. We'd be grateful if you'd give us your consent.