Apache2. Установка и настройка. Часть 1 из 2

15.03.2024

Теги: ApacheCGIFastCGILinuxSSLКонфигурацияНастройкаСерверУстановка

Apache — HTTP-сервер, который был создан с учетом ошибок старого web-сервера NCSA HTTPd. Основные достоинства Apache — кроссплатформенность и модульная система. Веб-сервер разрабатывается и поддерживается открытым сообществом разработчиков под эгидой Apache Software Foundation.

Установка Apache

Установка веб-сервера Apache

$ sudo apt install apache2

Основные команды управления

$ sudo systemctl stop apache2  # остановка сервера
$ sudo systemctl start apache2  # запуск сервера
$ sudo systemctl reload apache2  # перечитать конфигурацию
$ sudo systemctl restart apache2  # перезагрузить сервер
$ systemctl is-active apache2  # проверить состояние
$ sudo systemctl enable apache2  # включить автозапуск
$ sudo systemctl disable apache2  # отключить автозапуск

Файлы конфигурации

Теперь перейдем в директорию с настройками Apache

# cd /etc/apache2
# ls -l
-rw-r--r-- 1 root root  7224 сен 16 15:58 apache2.conf
drwxr-xr-x 2 root root  4096 ноя 11 09:32 conf-available
drwxr-xr-x 2 root root  4096 ноя 11 09:32 conf-enabled
-rw-r--r-- 1 root root  1782 июл 16 21:14 envvars
-rw-r--r-- 1 root root 31063 июл 16 21:14 magic
drwxr-xr-x 2 root root 12288 ноя 11 12:29 mods-available
drwxr-xr-x 2 root root  4096 ноя 11 12:29 mods-enabled
-rw-r--r-- 1 root root   320 июл 16 21:14 ports.conf
drwxr-xr-x 2 root root  4096 ноя 11 09:32 sites-available
drwxr-xr-x 2 root root  4096 ноя 11 09:32 sites-enabled

Рассмотрим назначение файлов и директорий внутри /etc/apache2

  • apache2.conf — главный конфигурационный файл Apache. Изменения в этом файле влияют на глобальную конфигурацию Apache. Этот файл отвечает за загрузку многих других файлов из конфигурационной директории.
  • ports.conf — этот файл определяет порты, которые Apache будет слушать. По умолчанию Apache слушает порт 80, а также порт 443 при условии, что модуль для работы с SSL включен.
  • sites-available — в этой директории хранятся файлы виртуальных хостов. Apache не использует файлы из этой директории, если ссылки на них нет в директории sites-enabled. Обычно настройка всех файлов виртуальных хостов осуществляется в этой директории, а активация хоста происходит путём создания ссылки в другой директории командой a2ensite.
  • sites-enabled — директория, в которой хранятся активированные виртуальные хосты. Обычно это делается путём создания ссылки на файл конфигурации хоста из директории sites-available с помощью команды a2ensite. Apache читает конфигурационный файлы и ссылки из этой директории при запуске или перезапуске.
  • conf-available, conf-enabled — эти директории связаны друг с другом так же, как и sites-available и sites-enabled связаны друг с другом, но используются для хранения фрагментов конфигурации, которые не принадлежат виртуальным хостам. Файлы в директории conf-available могут быть включены командой a2enconf и выключены командой a2disconf.
  • mods-available, mods-enabled — эти директории содержат, соответственно, доступные и активные модули. Файлы, оканчивающиеся на .load, содержат фрагменты для загрузки конкретных модулей, а файлы, оканчивающиеся на .conf, содержат настройки этих модулей. Модули можно активировать командой a2enmod и деактивировать командой a2dismod.

Посмотрим, какие сайты могут быть активированы командой a2ensite

# ls -la /etc/apache2/sites-available
-rw-r--r-- 1 root root 1332 июл 16 21:14 000-default.conf
-rw-r--r-- 1 root root 6338 июл 16 21:14 default-ssl.conf

И какие сайты сейчас доступны, т.е. были активированы командой a2ensite

# ls -la /etc/apache2/sites-enabled/
lrwxrwxrwx 1 root root 35 ноя 11 09:32 000-default.conf -> ../sites-available/000-default.conf

Подключение модулей

Посмотрим, какие модули могут быть активированы командой a2enmod

# ls -l /etc/apache2/mods-available/
total 568
-rw-r--r-- 1 root root  100 дек  4 18:58 access_compat.load
-rw-r--r-- 1 root root  377 дек  4 18:58 actions.conf
-rw-r--r-- 1 root root   66 дек  4 18:58 actions.load
-rw-r--r-- 1 root root  843 дек  4 18:58 alias.conf
-rw-r--r-- 1 root root   62 дек  4 18:58 alias.load
-rw-r--r-- 1 root root   76 дек  4 18:58 allowmethods.load
-rw-r--r-- 1 root root   76 дек  4 18:58 asis.load
-rw-r--r-- 1 root root   94 дек  4 18:58 auth_basic.load
-rw-r--r-- 1 root root   96 дек  4 18:58 auth_digest.load
-rw-r--r-- 1 root root  100 дек  4 18:58 auth_form.load
..........
-rw-r--r-- 1 root root  749 дек  4 18:58 status.conf
-rw-r--r-- 1 root root   64 дек  4 18:58 status.load
-rw-r--r-- 1 root root   72 дек  4 18:58 substitute.load
-rw-r--r-- 1 root root   64 дек  4 18:58 suexec.load
-rw-r--r-- 1 root root   70 дек  4 18:58 unique_id.load
-rw-r--r-- 1 root root  324 дек  4 18:58 userdir.conf
-rw-r--r-- 1 root root   66 дек  4 18:58 userdir.load
-rw-r--r-- 1 root root   70 дек  4 18:58 usertrack.load
-rw-r--r-- 1 root root   74 дек  4 18:58 vhost_alias.load
-rw-r--r-- 1 root root   66 дек  4 18:58 xml2enc.load

И какие модули сейчас доступны, т.е. были активированы командой a2enmod

# ls -l /etc/apache2/mods-enabled/
total 0
lrwxrwxrwx 1 root root 36 мар 15 10:53 access_compat.load -> ../mods-available/access_compat.load
lrwxrwxrwx 1 root root 28 мар 15 10:53 alias.conf -> ../mods-available/alias.conf
lrwxrwxrwx 1 root root 28 мар 15 10:53 alias.load -> ../mods-available/alias.load
lrwxrwxrwx 1 root root 33 мар 15 10:53 auth_basic.load -> ../mods-available/auth_basic.load
lrwxrwxrwx 1 root root 33 мар 15 10:53 authn_core.load -> ../mods-available/authn_core.load
lrwxrwxrwx 1 root root 33 мар 15 10:53 authn_file.load -> ../mods-available/authn_file.load
lrwxrwxrwx 1 root root 33 мар 15 10:53 authz_core.load -> ../mods-available/authz_core.load
lrwxrwxrwx 1 root root 33 мар 15 10:53 authz_host.load -> ../mods-available/authz_host.load
lrwxrwxrwx 1 root root 33 мар 15 10:53 authz_user.load -> ../mods-available/authz_user.load
lrwxrwxrwx 1 root root 32 мар 15 10:53 autoindex.conf -> ../mods-available/autoindex.conf
..........
lrwxrwxrwx 1 root root 32 мар 15 10:53 mpm_event.conf -> ../mods-available/mpm_event.conf
lrwxrwxrwx 1 root root 32 мар 15 10:53 mpm_event.load -> ../mods-available/mpm_event.load
lrwxrwxrwx 1 root root 34 мар 15 10:53 negotiation.conf -> ../mods-available/negotiation.conf
lrwxrwxrwx 1 root root 34 мар 15 10:53 negotiation.load -> ../mods-available/negotiation.load
lrwxrwxrwx 1 root root 33 мар 15 10:53 reqtimeout.conf -> ../mods-available/reqtimeout.conf
lrwxrwxrwx 1 root root 33 мар 15 10:53 reqtimeout.load -> ../mods-available/reqtimeout.load
lrwxrwxrwx 1 root root 31 мар 15 10:53 setenvif.conf -> ../mods-available/setenvif.conf
lrwxrwxrwx 1 root root 31 мар 15 10:53 setenvif.load -> ../mods-available/setenvif.load
lrwxrwxrwx 1 root root 29 мар 15 10:53 status.conf -> ../mods-available/status.conf
lrwxrwxrwx 1 root root 29 мар 15 10:53 status.load -> ../mods-available/status.load

Файлы конфигурации

Посмотрим, какие файлы конфигурации могут быть подключены командой a2enconf

# ls -l /etc/apache2/conf-available/
total 20
-rw-r--r-- 1 root root  315 дек  4 18:58 charset.conf
-rw-r--r-- 1 root root 3224 дек  4 18:58 localized-error-pages.conf
-rw-r--r-- 1 root root  189 дек  4 18:58 other-vhosts-access-log.conf
-rw-r--r-- 1 root root 2174 дек  4 18:58 security.conf

И какие файлы конфигурации используются, т.е. были подключены командой a2enconf

# ls -l /etc/apache2/conf-enabled/
total 0
lrwxrwxrwx 1 root root 30 мар 15 10:53 charset.conf -> ../conf-available/charset.conf
lrwxrwxrwx 1 root root 44 мар 15 10:53 localized-error-pages.conf -> ../conf-available/localized-error-pages.conf
lrwxrwxrwx 1 root root 46 мар 15 10:53 other-vhosts-access-log.conf -> ../conf-available/other-vhosts-access-log.conf
lrwxrwxrwx 1 root root 31 мар 15 10:53 security.conf -> ../conf-available/security.conf
lrwxrwxrwx 1 root root 36 мар 15 10:53 serve-cgi-bin.conf -> ../conf-available/serve-cgi-bin.conf

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

В файле конфигурации /etc/apache2/envvars устанавливаются переменные, которые используют другие файлы конфигурации и команда apache2ctl.

# ... прочие директивы конфигурации ...

# Since there is no sane way to get the parsed apache2 config in scripts, some
# settings are defined via environment variables and then used in apache2ctl,
# /etc/init.d/apache2, /etc/logrotate.d/apache2, etc.
export APACHE_RUN_USER=www-data
export APACHE_RUN_GROUP=www-data
# temporary state file location. This might be changed to /run in Wheezy+1
export APACHE_PID_FILE=/var/run/apache2${SUFFIX}/apache2.pid
export APACHE_RUN_DIR=/var/run/apache2${SUFFIX}
export APACHE_LOCK_DIR=/var/lock/apache2${SUFFIX}
# Only /var/log/apache2 is handled by /etc/logrotate.d/apache2.
export APACHE_LOG_DIR=/var/log/apache2${SUFFIX}

# ... прочие директивы конфигурации ...

Прослушиваемые порты

В файле конфигурации /etc/apache2/ports.conf с помощью директивы Listen задаются интерфейсы и порты, которые должен прослушивать веб-сервер. Если есть только один сетевой интерфейс, то достаточно указать только порты.

# If you just change the port or add more ports here, you will likely also have to
# change the VirtualHost statement in /etc/apache2/sites-enabled/000-default.conf
Listen 80
<IfModule ssl_module>
    Listen 443
</IfModule>
<IfModule mod_gnutls.c>
    Listen 443
</IfModule>

Главный файл конфигурации

Главный файл конфигурации — это /etc/apache2/apache2.conf, в нем с помощью директив Include и IncludeOptional подключаются все прочие файлы конфигурации.

# Директива ServerRoot определяет путь к домашней директории Apache2 с
# конфигурационными файлами. По умолчанию имеет значение /etc/apache2.
# Внимание, завершающий слэш в пути не допускается, это будет ошибкой!
#ServerRoot "/etc/apache2"

# Директива DefaultRuntimeDir задает каталог, в котором сервер будет
# создавать различные файлы времени выполнения.
DefaultRuntimeDir ${APACHE_RUN_DIR}

# Файл для хранения идентификатора процесса после запуска сервера
PidFile ${APACHE_PID_FILE}

# Директива TimeOut определяет время, в течение которого Apache будет
# ждать получения данных при запросе от клиента и ждать подтверждения
# клиентом получения данных при ответе на запрос.
Timeout 30

# Расширение Keep-Alive для HTTP/1.0 и функция постоянного соединения
# HTTP/1.1 обеспечивают долгоживущие сеансы HTTP, которые позволяют
# отправлять несколько запросов через одно TCP-соединение.
KeepAlive On

# Директива MaxKeepAliveRequests ограничивает число запросов на одно
# постоянное соединение, когда KeepAlive включен.
MaxKeepAliveRequests 100

# Директива KeepAliveTimeout задает время ожидания в секундах очередного
# запроса через постоянное соединение, когда KeepAlive включен.
KeepAliveTimeout 5

# Пользователь и группа, от имени которых работает сервер
User ${APACHE_RUN_USER}
Group ${APACHE_RUN_GROUP}

# Директива AddDefaultCharset задает кодировку по умолчанию
AddDefaultCharset utf-8

# Директива HostnameLookups позволяет резолвить доменные имена клиентов
HostnameLookups Off

# Директива ErrorLog задает расположение файла логов ошибок
ErrorLog ${APACHE_LOG_DIR}/error.log

# Директива LogLevel задает уровень ошибки, начиная с которого они
# будут записываться в лог ошибок: trace8, ..., trace1, debug, info,
# notice, warn, error, crit, alert, emerg. Можно установить общий
# уровень и уровни для отдельных модулей — LogLevel info ssl:warn.
LogLevel warn

# Подключение модулей и файлов конфигураций для этих модулей
IncludeOptional mods-enabled/*.load
IncludeOptional mods-enabled/*.conf

# Файл конфигурации списка портов, которые нужно прослушивать
Include ports.conf

# Запрещаем доступ к файловой системе веб-сервера, начиная с корня
<Directory />
    Options FollowSymLinks
    AllowOverride None
    Require all denied
</Directory>
# Разрешаем доступ к /usr/share, которую используют веб-приложения
<Directory /usr/share>
    AllowOverride None
    Require all granted
</Directory>
# Разрешаем доступ к /var/www/, если нам нужен полноценный веб-сервер
<Directory /var/www/>
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

# Директива AccessFileName устанавливает имя файла конфигурации, который
# Apache будет искать в каждой обслуживаемой директории. Директивы из
# этого файла применяются только для этой директории и ее поддиректорий.
AccessFileName .htaccess

# Запрет для всех на просмотр файлов .htaccess и .htpasswd
<FilesMatch "^\.ht">
    Require all denied
</FilesMatch>

# Директива LogFormat позволяет задать формат одной строки файла логов
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent

# Дополнительные файлы конфигурации
IncludeOptional conf-enabled/*.conf

# Файлы конфигурации виртуальных хостов
IncludeOptional sites-enabled/*.conf

Утилита управления apache2ctl

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

# apache2ctl -h
Usage: /usr/sbin/apache2 [-D name] [-d directory] [-f file]
                         [-C "directive"] [-c "directive"]
                         [-k start|restart|graceful|graceful-stop|stop]
                         [-v] [-V] [-h] [-l] [-L] [-t] [-T] [-S] [-X]
Options:
  -D name            : define a name for use in <IfDefine name> directives
  -d directory       : specify an alternate initial ServerRoot
  -f file            : specify an alternate ServerConfigFile
  -C "directive"     : process directive before reading config files
  -c "directive"     : process directive after reading config files
  -e level           : show startup errors of level (see LogLevel)
  -E file            : log startup errors to file
  -v                 : show version number
  -V                 : show compile settings
  -h                 : list available command line options (this page)
  -l                 : list compiled in modules
  -L                 : list available configuration directives
  -t -D DUMP_VHOSTS  : show parsed vhost settings
  -t -D DUMP_RUN_CFG : show parsed run settings
  -S                 : a synonym for -t -D DUMP_VHOSTS -D DUMP_RUN_CFG
  -t -D DUMP_MODULES : show all loaded modules
  -M                 : a synonym for -t -D DUMP_MODULES
  -t -D DUMP_INCLUDES: show all included configuration files
  -t                 : run syntax check for config files
  -T                 : start without DocumentRoot(s) check
  -X                 : debug mode (only one worker, do not detach)

Для примера — проверим на ошибки файлы конфигурации после внесения изменений

# apache2ctl -t
Syntax OK

Страница состояния сервера

Модуль status_module предоставляет информацию об активности и производительности сервера.

<Location "/server-status">
    SetHandler server-status
    # Доступ разрешен только с ip-адреса 192.168.110.2
    Require ip 192.168.110.2
</Location>

Управление доступом

Директива Require разрешает или запрещает доступ к сайту или отдельной директории, проверяя условия, которые указываются как аргументы.

Require [not] метод значение [значение] ...
Require [not] all|env|method|expr|user|group|host|ip|local значение [значение] ...

Если в директиве для метода указано через пробел несколько значений, которые не являются зарезервированными ключевыми словами, а представляют собой значения для сравнения указанным методом, то все такие аргументы воспринимаются объединенными логическим ИЛИ и достаточно, чтобы один из таких аргументов получил соответствие в методе проверки, чтобы метод суммарно вернул истину как результат проверки.

Пример разрешения доступа всем без ограничений, что аналогично устаревшей Allow from all

<Directory "/var/www/example.net/html">
    Require all granted
</Directory>

Пример запрещения доступа всем, что аналогично устаревшей Deny from all

<Directory "/var/www/example.net/html">
    Require all denied
</Directory>

Пример разрешения доступа на основе логического выражения

<Directory "/var/www/example.net/html">
    Require expr %{HTTP_USER_AGENT} != "BadBot"
</Directory>

Групповая директива RequireAll требует, чтобы все директивы Require внутри нее вернули истину

# Разрешается доступ всем, но запрещается доступ с ip-адреса
# 123.123.123.123 и с хоста с именем host.example.net
<Directory "/var/www/example.net/html">
    <RequireAll>
        Require all granted
        Require not ip 123.123.123.123
        Require not host host.example.net
    </RequireAll>
</Directory>
Соблюдайте осторожность при использовании директивы <Location "URL">, аргумент URL которой перекрывается с файловым путем в файловой системе сервера. Это связано с тем, что по умолчанию <Location> перезаписывает директивы <Directory> и <Files>, так как <Location> обрабатываются в порядке их появления в файле конфигурации, после того как <Directory>, <Files> и .htaccess уже прочитаны.

Групповая директива RequireAny требует, чтобы хотя бы одна директива Require внутри нее вернула истину

# Запрещается доступ всем, но разрешается доступ с ip-адреса
# 123.123.123.123 и с хоста с именем host.example.net
<Directory "/var/www/example.net/html">
    <RequireAny>
        Require all denied
        Require ip 123.123.123.123
        Require host host.example.net
    </RequireAny>
</Directory>

Директива Require может быть указана многократно, каждая следующая с новой строки. В этом случае все такие директивы считаются по умолчанию обернутыми в групповой тег RequireAny. А это значит, что доступ будет разрешен, если хотя бы одна из таких директив исполнится. И в этом случае все остальные директивы, после первой исполненной, будут проигнорированы.

Директивы Allow, Deny, Order модуля mod_access_compat нежелательны к использованию и считаются устаревшими, хотя и поддерживаются еще в версиях Apache 2.3 и 2.4, но в следующих версиях они будут удалены.

Виртуальные хосты

1. Как это работает

Для создания виртуального хоста используется групповая директива VirtualHost. Когда сервер получает запрос — он просматривает все директивы VirtualHost на предмет соответствия ip-адреса и порта. У сервера может быть несколько сетевых интерфейсов, какие из них будет прослушивать веб-сервер — задается директивой Listen, директива VirtualHost на это не влияет. Если веб-сервер прослушивает только один сетевой интерфейс — нет смысла указывать ip-адрес, можно использовать звездочку.

<VirtualHost addr[:port] [addr[:port]] ...>
    # директивы виртуального хоста
</VirtualHost>

Эта директива сработает, если запрос пришел на порт 80

<VirtualHost *:80>
    # директивы виртуального хоста
</VirtualHost>

Эта директива сработает, если запрос пришел на порт 443

<VirtualHost *:443>
    # директивы виртуального хоста
</VirtualHost>

Эта директива сработает, если запрос пришел на порт 80 или 443

<VirtualHost *:80 *:443>
    # директивы виртуального хоста
</VirtualHost>

Давайте посмотрим, какие директивы обычно используются внутри групповой директивы VirtualHost

<VirtualHost *:80>
    ServerName example.net
    ServerAlias www.example.net
    ServerAdmin webmaster@example.net
    DocumentRoot /var/www/example.net/html
    LogLevel warn ssl:error
    ErrorLog ${APACHE_LOG_DIR}/exampl-net-error.log
    CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined
</VirtualHost>

Директивы ServerName и ServerAlias устанавливают доменное имя, директива DocumentRoot — корневую директорию виртуального хоста, директивы ErrorLog и CustomLog задают имена файлов для логов доступа и ошибок.

После того, как веб-сервер просмотрел все директивы VirtualHost и нашел наиболее подходящую комбинацию ip-адреса и порта — выполняется сравнение заголовка Host http-запроса с доменами, указанными в директивах ServerName и ServerAlias. Если было совпадение — виртуальный хост найден и выполняются все директивы внутри этого VirtualHost. Если не было совпадения — будет использован виртуальный хост по умолчанию. Другими словами — первый в списке виртуальных хостов.

Давайте посмотрим на конфигурацию виртуального хоста по умолчанию — это файл /etc/apache2/site-available/000-default.conf (обратите внимание, что имя начинается с 000 — чтобы этот виртуальный хост всегда был первым).

<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

2. Виртуальный хост

Давайте создадим виртуальный хост — для этого нужен файл конфигурации

# nano /etc/apache2/sites-available/example.net.conf
<VirtualHost *:80>
    ServerName example.net
    ServerAlias www.example.net
    ServerAdmin webmaster@example.net
    DocumentRoot /var/www/example.net/html
    ErrorLog ${APACHE_LOG_DIR}/example-net-error.log
    CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined
</VirtualHost>

Создаем директорию виртуального хоста и файл index.html

# mkdir -p /var/www/example.net/html
# nano /var/www/example.net/html/index.html
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Apache2: It works</title>
  </head>
  <body>
    <h3>Виртуальный хост example.net</h3>
    <p>Файл /var/www/example.net/html/index.html</p>
  </body>
</html>

Активируем новый виртуальный хост и перечитываем конфигурацию

# a2ensite example.net.conf
# systemctl reload apache2.service

Чтобы обращаться к веб-серверу по домену example.net — добавляем запись в файл hosts.

MPM Prefork и MPM Worker

Apache спроектирован для работы на разных платформах. Разные платформы имеют разные способы реализации одного функционала. Модульная конструкция Apache позволяет выбирать, какие функции будут включены в сервер. То есть, какие модули загружать либо во время компиляции, либо во время выполнения.

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

После установке Apache на сервер Linux, можно использовать два основных модуля многопроцессорной обработки (MPM) — Prefork и Worker.

MPM Prefork — это традиционный MPM, который существует с первых дней существования Apache. В этом случаем главный процесс Apache создает несколько дочерних процессов, каждый из которых может обрабатывать один запрос за раз. Это позволяет Apache обрабатывать несколько запросов одновременно без необходимости создавать новый процесс для каждого запроса.

MPM Worker — использует многопоточный подход, при котором каждый дочерний процесс обрабатывает несколько запросов одновременно, используя несколько потоков. Это уменьшает объем используемой памяти, поскольку каждый дочерний процесс может обрабатывать множество запросов. Но также означает, что Apache должен использовать только потокобезопасные модули.

1. MPM Prefork

MPM Prefork — более стабильный и безопасный, поэтому используется по умолчанию. Это хороший выбор, если используются модули Apache, которые не умеют работать с потоками (threads).

# ls -la /etc/apache2/mods-available | grep mpm
-rw-r--r-- 1 root root   668 дек  4 18:58 mpm_event.conf
-rw-r--r-- 1 root root   106 дек  4 18:58 mpm_event.load
-rw-r--r-- 1 root root   571 дек  4 18:58 mpm_prefork.conf
-rw-r--r-- 1 root root   108 дек  4 18:58 mpm_prefork.load
-rw-r--r-- 1 root root   836 дек  4 18:58 mpm_worker.conf
-rw-r--r-- 1 root root   107 дек  4 18:58 mpm_worker.load
# ls -la /etc/apache2/mods-enabled | grep mpm
lrwxrwxrwx 1 root root   34 мар 17 10:33 mpm_prefork.conf -> ../mods-available/mpm_prefork.conf
lrwxrwxrwx 1 root root   34 мар 17 10:33 mpm_prefork.load -> ../mods-available/mpm_prefork.load

Давайте посмотрим настройки MPM Prefork

$ cat /etc/apache2/conf-available/mpm_prefork.conf
# StartServers: number of server processes to start
# MinSpareServers: minimum number of server processes which are kept spare
# MaxSpareServers: maximum number of server processes which are kept spare
# MaxRequestWorkers: maximum number of server processes allowed to start
# MaxConnectionsPerChild: maximum number of requests a server process serves

<IfModule mpm_prefork_module>
    StartServers               5
    MinSpareServers            5
    MaxSpareServers           10
    MaxRequestWorkers        150
    MaxConnectionsPerChild     0
</IfModule>

StartServers — определяет, сколько дочерних процессов создается при запуске Apache.

MinSpareServers — минимальное количество дочерних процессов, которые должны оставаться свободными и ждать запросов от клиентов.

MaxSpareServers — максимальное количество дочерних процессов, которые должны оставаться свободными и ждать запросов от клиентов.

MaxRequestWorkers — число запросов, которые могут обслуживаться одновременно. Для prefork — это максимальное количество дочерних процессов, поскольку все они обслуживаются в один поток. Любые попытки подключения, превышающие этот лимит, обычно ставятся в очередь — вплоть до числа, определяемого директивой ListenBacklog.

MaxConnectionsPerChild — лимит по кол-ву запросов от клиентов, которые может обслужить один дочерний процесс. Процесс прекращает существовать, обслужив установленное количество. Значение по-умолчанию равно нулю, т.е. время жизни дочернего процесса не ограничено. Ограничение ставят, чтобы избежать утечки памяти.

ServerLimit — по умолчанию равно 256 и задает лимит на кол-во дочерних процессов. Директива нужна для защиты от установки слишком большого значения для MaxRequestWorkers. Эту директиву следует использовать только в том случае, если для MaxRequestWorkers нужно задать значение больше 256. Если для ServerLimit и MaxRequestWorkers установлены значения, превышающие возможности системы — Apache может не запуститься или система может стать нестабильной.

2. MPM Worker

Теперь отключим модуль mpm_prefork и включим модуль mpm_worker

# a2dismod mpm_prefork
# a2enmod mpm_worker
# systemctl restart apache2.service
При выборе между MPM Prefork и MPM Worker нужно учитывать, какие модули Apache зависят от модуля mpm_prefork, а какие — от модуля mpm_worker. Может оказаться так, что хотелось использовать MPM Worker, но получается — только MPM Prefork.

Давайте посмотрим настройки MPM Worker

$ cat /etc/apache2/mods-available/mpm_worker.conf
# StartServers: initial number of server processes to start
# MinSpareThreads: minimum number of worker threads which are kept spare
# MaxSpareThreads: maximum number of worker threads which are kept spare
# ThreadLimit: ThreadsPerChild can be changed to this maximum value during a
#                         graceful restart. ThreadLimit can only be changed by stopping
#                         and starting Apache.
# ThreadsPerChild: constant number of worker threads in each server process
# MaxRequestWorkers: maximum number of threads
# MaxConnectionsPerChild: maximum number of requests a server process serves

<IfModule mpm_worker_module>
    StartServers               2
    MinSpareThreads           25
    MaxSpareThreads           75
    ThreadLimit               64
    ThreadsPerChild           25
    MaxRequestWorkers        150
    MaxConnectionsPerChild     0
</IfModule>

StartServers — определяет, сколько дочерних процессов создается при запуске Apache.

MinSpareThreads — минимальное количество потоков, которые должны оставаться свободными и ждать запросов от клиентов.

MaxSpareServers — максимальное количество потоков, которые должны оставаться свободными и ждать запросов от клиентов.

ThreadLimit — по умолчанию равно 64 и задает лимит на кол-во потоков на один дочерний процесс. Директива нужна для защиты от установки слишком большого значения для ThreadsPerChild. Если для ThreadLimit и ThreadsPerChild установлены значения, превышающие возможности системы, Apache httpd может не запуститься или система может стать нестабильной.

ThreadsPerChild — фиксированное количество потоков, которое создает каждый дочерний процесс.

MaxRequestWorkers — число запросов, которые могут обслуживаться одновременно. Для worker — это максимальное количество потоков во всех дочерних процессах. Любые попытки подключения, превышающие этот лимит, обычно ставятся в очередь — вплоть до числа, определяемого директивой ListenBacklog.

MaxConnectionsPerChild — лимит по кол-ву запросов от клиентов, которые может обслужить один дочерний процесс. Процесс прекращает существовать, обслужив установленное количество. Значение по-умолчанию равно нулю, т.е. время жизни дочернего процесса не ограничено. Ограничение ставят, чтобы избежать утечки памяти.

Перед тем, как двигаться дальше — вернем MPM Prefork, как было по умолчанию

# a2dismod mpm_worker
# a2enmod mpm_prefork
# systemctl restart apache2.service

Выполнение CGI-скриптов

1. Подготовка к выполнению CGI-скриптов

Давайте посмотрим на файл конфигурации /etc/apache2/conf-available/serve-cgi-bin.conf, который активирован сразу после установки пакета веб-сервера.

# cat /atc/apache2/conf-available/serve-cgi-bin.conf
<IfModule mod_alias.c>
    <IfModule mod_cgi.c>
        Define ENABLE_USR_LIB_CGI_BIN
    </IfModule>

    <IfModule mod_cgid.c>
        Define ENABLE_USR_LIB_CGI_BIN
    </IfModule>

    <IfDefine ENABLE_USR_LIB_CGI_BIN>
        ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
        <Directory "/usr/lib/cgi-bin">
            AllowOverride None
            Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
            Require all granted
        </Directory>
    </IfDefine>
</IfModule>

Видим, что для запуска CGI-скриптов нужно активировать модуль mod_cgid

# a2enmod cgid
# systemctl restart apache2.service

Теперь создадим отдельную директорию и разрешим для нее выполнение CGI-скриптов

# mkdir /var/www/example.net/cgi-bin
# nano /etc/apache2/site-available/example.net.conf
<VirtualHost *:80>
    ServerName example.net
    ServerAlias www.example.net
    ServerAdmin webmaster@example.net
    DocumentRoot /var/www/example.net/html
    <IfDefine ENABLE_USR_LIB_CGI_BIN>
        ScriptAlias /cgi-bin/ /var/www/example.net/cgi-bin/
        <Directory "/var/www/example.net/cgi-bin">
            AllowOverride None
            Require all granted
        </Directory>
    </IfDefine>
    ErrorLog ${APACHE_LOG_DIR}/example-net-error.log
    CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined
</VirtualHost>
# systemctl restart apache2.service
Директива ScriptAlias сообщает Apache, что для CGI-скриптов выделен определенный каталог. Apache будет считать, что каждый файл в этом каталоге является CGI-скриптом, и попытается выполнить его, когда клиент запрашивает этот конкретный ресурс.

2. Выполнение bash-скрипта как CGI

Давайте напишем bash-скрипт, который будет показывать место на диске веб-сервера

# nano /var/www/example.net/cgi-bin/disk-usage.sh
#!/bin/bash
echo 'Content-type: text/html'
echo ''
echo '<h4>Disk usage</h4>'
echo '<pre>'
IFS=$'\n'
for line in $(df -h); do
    echo $line
done
echo '</pre>'
# chmod +x /var/www/example.net/cgi-bin/disk-usage.sh

Открываем в браузере страницу http://example.net/cgi-bin/disk-usage.sh

3. Выполнение php-скрипта как CGI

Для написания скриптов можно использовать не только bash. Давайте установим пакет php-cli и напишем скрипт на PHP, который будет показывать место на диске веб-сервера.

# apt install php-cli
# nano /var/www/example.net/cgi-bin/disk-usage.php
#!/usr/bin/php
<?php
echo "Content-type: text/html\r\n";
echo "\r\n";
echo '<h4>Disk usage</h4>';
echo '<pre>';
exec('df -h', $output);
foreach ($output as $line) {
    echo $line, "\r\n";
}
echo '</pre>';
# chmod +x /var/www/example.net/cgi-bin/disk-usage.php

4. Директивы запуска CGI-скриптов

Мы размещаем все CGI-скрипты в одной директории /var/www/example.net/cgi-bin, это может быть bash, php, python, perl. Можно разрешить выполнение только php-скриптов только в выбранной директории. Или разрешить выполнение php-скриптов как CGI в корневой директории виртуального хоста.

<VirtualHost *:80>
    ServerName example.net
    ServerAlias www.example.net
    ServerAdmin webmaster@example.net
    DocumentRoot /var/www/example.net/html
    <IfDefine ENABLE_USR_LIB_CGI_BIN>
        # Разрешаем выполенение любых cgi-скриптов в каталоге
        # /var/www/example.net/cgi-bin/, скрипты будут доступны
        # при обращении к URL http://example.net/cgi-bin/foo.sh
        ScriptAlias /cgi-bin/ /var/www/example.net/cgi-bin/

        # Разрешаем выполнение php-скритов как cgi в каталоге
        # /var/www/example.net/cgi-php/, скрипты будут доступны
        # при обращении к URL http://example.net/cgi-php/foo.php.
        # Доступ к любым другим файлам в этом каталоге запрещен.
        Alias /cgi-php/ /var/www/example.net/cgi-php/
        <Directory "/var/www/example.net/cgi-php">
            Require all denied
            Options ExecCGI
            <FilesMatch ".+\.php$">
                Require all granted
                SetHandler cgi-script
            </FilesMatch>
        </Directory>

        # Разрешаем выполнение php-скритов как cgi в корневом
        # каталоге этого виртуального хоста и во всех потомках
        <FilesMatch ".+\.php$">
            Options ExecCGI
            SetHandler cgi-script
        </FilesMatch>
    </IfDefine>
    ErrorLog ${APACHE_LOG_DIR}/example-net-error.log
    CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined
</VirtualHost>

Модуль mod_php, CGI, FastCGI и PHP-FPM

Есть модуль для Apache, позволяющий ему выполнять php-скрипты. Это самый простой способ подружить Apache и PHP. Модуль не использует ни CGI, ни FastCGI. Есть свои минусы — скрипты работают под пользователем веб-сервера, невозможно использовать больше одной версии PHP.

PHP как CGI — самый старый способ выполнения php-скриптов веб-сервером. В настоящее время используется редко в силу малой производительности. Выполнение php-скрипта как CGI вынуждает каждый раз запускать интерпретатор PHP.

FastCGI — интерпретатор PHP не запускается каждый раз, когда нужно выполнить php-скрипт. Несколько процессов интерпретатора PHP заранее запущены и готовы к обработке запросов от веб-сервера. Время от времени они останавливаются во избежание утечки памяти — но на смену остановленным запускаются новые.

PHP-FPM (FastCGI Process Manager) — менеджер процессов FastCGI. Это альтернативная реализация FastCGI режима в PHP с несколькими дополнительными возможностями, которые обычно используются для высоконагруженных сайтов.

PHP как модуль Apache

Устанавливаем модуль Apache для работы с PHP — это пакет libapache2-mod-php

# apt install libapache2-mod-php

Посмотрим список доступных и активных модулей Apache

# ls -la /etc/apache2/mods-available/ | grep php
-rw-r--r-- 1 root root   855 авг 18  2023 php8.1.conf
-rw-r--r-- 1 root root   101 авг 18  2023 php8.1.load
# ls -la /etc/apache2/mods-enabled/ | grep php
lrwxrwxrwx 1 root root   29 мар 17 10:33 php8.1.conf -> ../mods-available/php8.1.conf
lrwxrwxrwx 1 root root   29 мар 17 10:33 php8.1.load -> ../mods-available/php8.1.load

Посмотрим файл конфигурации модуля php8.1.conf

$ cat /etc/apache2/mods-available/php8.1.conf
<FilesMatch ".+\.ph(ar|p|tml)$">
    SetHandler application/x-httpd-php
</FilesMatch>
<FilesMatch ".+\.phps$">
    SetHandler application/x-httpd-php-source
    # Deny access to raw php sources by default
    Require all denied
</FilesMatch>
# Deny access to files without filename (e.g. '.php')
<FilesMatch "^\.ph(ar|p|ps|tml)$">
    Require all denied
</FilesMatch>

# Running PHP scripts in user directories is disabled by default
<IfModule mod_userdir.c>
    <Directory /home/*/public_html>
        php_admin_flag engine Off
    </Directory>
</IfModule>

Давайте создадим php-скрипт в корневой директории виртуального хоста

# nano /var/www/example.net/html/index.php
<h3>Вывод функции phpinfo()</h3>
<?php
phpinfo();
?>

Набираем в адресной строке браузера http://example.net/index.php

Есть еще смысл изменить значение директивы DirectoryIndex для виртуального хоста, чтобы при обращении без указания имени файла — сервер сначала пытался найти index.php, а потом index.html. И запретим для виртуального хоста выдавать список файлов директории, если нет индексного файла.

<VirtualHost *:80>
    ServerName example.net
    ServerAlias www.example.net
    ServerAdmin webmaster@example.net

    DocumentRoot /var/www/example.net/html
    DirectoryIndex index.php index.html
    Options -Indexes

    ErrorLog ${APACHE_LOG_DIR}/example-net-error.log
    CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined
</VirtualHost>

Применяем новую конфигурацию и обращаемся к http://example.net/

# systemctl reload apache2.service

PHP как модуль, файл php.ini

Когда PHP работает как модуль Apache — для всех виртуальных хостов действует один файл конфигурации /etc/php/8.1/apache2/php.ini. Чтобы установить индивидуальные значения конфигурации PHP для виртуального хоста — их нужно прописать в файле конфигурации этого хоста.

<VirtualHost *:80>
    ServerName example.net
    ServerAlias www.example.net
    ServerAdmin webmaster@example.net

    DocumentRoot /var/www/example.net/html
    DirectoryIndex index.php index.html
    Options -Indexes

    # значения конфигурации php для этого виртуального хоста
    <IfModule php_module>
        php_value session.save_handler files
        php_value session.save_path /var/www/example.net/session
        <Directory "/var/www/example.net/html/img">
            php_admin_flag engine off
        </Directory>
    </IfModule>

    ErrorLog ${APACHE_LOG_DIR}/example-net-error.log
    CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined
</VirtualHost>
<VirtualHost *:80>
    ServerName example.org
    ServerAlias www.example.org
    ServerAdmin webmaster@example.org

    DocumentRoot /var/www/example.org/html
    DirectoryIndex index.php index.html
    Options -Indexes

    # значения конфигурации php для этого виртуального хоста
    <IfModule php_module>
        php_value session.save_handler files
        php_value session.save_path /var/www/example.org/session
        <Directory "/var/www/example.org/html/pdf">
            php_admin_flag engine off
        </Directory>
    </IfModule>

    ErrorLog ${APACHE_LOG_DIR}/example-org-error.log
    CustomLog ${APACHE_LOG_DIR}/example-org-access.log combined
</VirtualHost>

Директива php_admin_value используется для установки строковых значений, директива php_admin_flag — для установки флагов типа on или off. Директивы php_value и php_flag позволяют задать значения, которые потом можно перезаписать с помощью функции ini_set. Директивы с использованием admin перезаписать нельзя.

PHP как FastCGI

1. Директивы конфигурации FastCGI

Процессы PHP как FastCGI настраиваются с использованием директивы FcgidWrapper и соответствующего shell-скрипта (wrapper). Этот wrapper позволяет установить переменные среды, необходимые приложению — например PHP_FCGI_MAX_REQUESTS. Для каждого виртуального хоста можно создать отдельный wrapper — запускаемые процессы будут иметь разные настройки. Можно создать несколько wrapper для одного виртуального хоста — запускаемые процессы будут иметь разные настройки. Переменные среды также можно установить с помощью FcgidInitialEnv — но они применяются к серверу или виртуальному хосту в целом (хотя можно переопределить внутри wrapper).

Некоторые директивы управляют обработкой класса процесса. Класс процесса — это набор процессов, которые были запущены одним wrapper и для одного виртуального хоста. Директивы, которые зависят от виртуального хоста (например FcgidInitialEnv) или директивы класса процессов, различают виртуальные хосты только в том случае, если они имеют разное значение ServerName.

В случае FcgidInitialEnv, если два виртуальных хоста имеют одинаковое значение ServerName, но разные значения преременных окружения, заданных через директиву FcgidInitialEnv — запускаемый процесс получит переменные окружения, заданные для виртуального хоста, которые запускет этот процесс.

FcgidMaxProcesses — максимальное количество процессов, которые могут быть активны одновременно. Значение по умолчанию — 1000.

FcgidMaxProcessesPerClass — максимальное количество процессов одного класса, которые обрабатывают запросы клиентов. Если лимит достигнут — новые процессы не запускаются. Значение по умолчанию — 100.

FcgidMinProcessesPerClass — минимальное количество процессов одного класса, которые обрабатывают запросы клиентов. Даже если запросов от клиентов вовсе нет — они продолжают работать. Значение по умолчанию — 3.

FcgidMaxRequestsPerProcess — процесс будет завершен после обработки указанного количества запросов, это защита от возможной утечки памяти. Значение по умолчанию — ноль, то есть без ограничений.

PHP имеет собственный механизм завершения работы после обработки определенного количества запросов. Это задается переменной PHP_FCGI_MAX_REQUESTS, которая по умолчанию имеет значение 500. Лимит нужно устанавливать либо для PHP, либо для FastCGI — дублировать нежелательно.

FcgidIOTimeout — максимальный период времени, в течение которого модуль будет ждать при попытке передачи данных в приложение или получения данных от приложения. Приложение должно начать генерировать ответ в течение этого периода времени. Значение по умолчанию — 40 секунд.

FcgidBusyTimeout — максимальное время в секундах на обработку запроса. Если запрос не будет обработан за это время — процесс будет помечен на завершение. Проверка времени обработки выполняется с интервалом FcgidBusyScanInterval. Значение по умолчанию — 300 секунд. Есть смысл уменьшить до 30 секунд.

FcgidBusyScanInterval — интервал сканирования процессов, которые обрабатывают запрос слишком долго. Значение по умолчанию — 120 секунд. Есть смысл уменьшить до 30 секунд.

FcgidErrorScanInterval — интервал запуска «киллера» процессов, помеченных на завершение. Помечены они могут быть по причине долгой обработки запроса (зависшие процессы), по причине превышения максимального времени жизни, по причине простоя без дела (нет запросов на обработку). Завершение происходит путем отправки сигнала SIGTERM, а если процесс не реагирует — то сигнала SIGKILL. Значение по умолчанию — 3 секунды.

FcgidProcessLifeTime — максимальное время жизни процесса секундах. Простаивающий без дела процесс, существующий дольше этого времени, будет помечен на завершение, если количество процессов для класса превысит FcgidMinProcessesPerClass. Значение ноль отключает проверку. Проверка времени жизни процесса выполняется с частотой FcgidIdleScanInterval. Значение по умолчанию — 3600 секунд.

Процессы можно завершать либо по кол-ву обработанных запросов либо по максимальному времени жизни. Нужно использовать один способ заверешения процессов — дублировать нежелательно.

FcgidIdleTimeout — простаивающий без дела процесс, не обработавший ни одного запроса за этот период времени, будет помечен на завершение — если количество процессов для класса превысит FcgidMinProcessesPerClass. Значение ноль отключает проверку. Проверка выполняется с частотой FcgidIdleScanInterval. Значение по умолчанию — 300 секунд.

FcgidIdleScanInterval — интервал сканирования простаивающих процессов в секундах. Модуль mod_fcgid будет искать процессы, которые превысили FcgidProcessLifeTime или FcgidIdleTimeout — и помечать их на завершение. Значение по умолчанию — 120 секунд.

FcgidOutputBufferSize — максимальное значение в байтах, которые модуль mod_fcgid прочитает из ответа приложения FastCGI (в нашем случае — от php-скрипта), чтобы отправить клиенту. Значение по умолчанию — 65536 байт (64 Кбайт). Есть смысл увеличить до 1048576 байт (1 Мбайт).

FcgidMaxRequestLen — максимальное значение в байтах тела запроса, которые модуль mod_fcgid готов принять от клиента. Это значение не должно быть меньше значения LimitRequestBody веб-сервера Apache и не должно быть больше значения post_max_size файла конфигурации PHP. Значение по умолчанию — 131072 байт (128 Кбайт). Если планируется загружать большие файлы — есть смысл увеличить до 33554432 байт (32 Мбайт).

FcgidMaxRequestInMem — модуль считывает в память все тело запроса от клиента перед отправкой его FastCGI приложению. Если размер тела запроса в байтах больше этого значения — оставшаяся часть сохраяется во временном файле. Значение по умолчанию — 65536 байт (64 Кбайт).

FcgidInitialEnv — имя и значение переменной среды для передачи в приложение FastCGI. Эта директива применяется ко всем приложениям вируального хоста.

# Установка PHP_FCGI_MAX_REQUESTS в ноль может вызывать проблемы, если есть утечки памяти.
# Если установить значение, отличное от нуля — это может приводить к ошибке 500 Internal
# Server Error. Так происходит, когда модуль успел отправить запрос в php-процесс, но как
# раз в этот момент процесс завершается по лимиту обработанных запросов.
FcgidInitialEnv PHP_FCGI_MAX_REQUESTS 0
# PHP не должен управлять дочерними процессами при работе с модулем mod_fcgid — поэтому
# переменная окружения PHP_FCGI_CHILDREN всегда должна быть равна нулю.
FcgidInitialEnv PHP_FCGI_CHILDREN 0
В документации Apache написано, что PHP не должен управлять дочерними процессами при работе с модулем mod_fcgid — поэтому переменная окружения PHP_FCGI_CHILDREN всегда должна быть равна нулю.

FcgidWrapper — команда или shell-скрипт для для запуска процессов FCGI. Если эта директива не используется, вместо нее будет использоваться файл, на который указывает URL-адрес запроса. Параметры команды можно включить, если зпключить команду и параметры в кавычки.

2. Пример файла конфигурации

Пример файла конфигурации Apache с двумя виртуальными хостами, каждый из которых обслуживает два php-приложения.

# общие настройки FCGI для всего веб-сервера в целом

# максимальное количество одновременно работающих процессов
FcgidMaxProcesses 500
# запрещаем PHP самому управлять дочерними процессами
FcgidInitialEnv PHP_FCGI_CHILDREN 0
# отключаем завершение работы процессов по кол-ву запросов
FcgidMaxRequestsPerProcess 0
FcgidInitialEnv PHP_FCGI_MAX_REQUESTS 0
# максимальное время жизни процесса приложения FastCGI
FcgidProcessLifeTime 3600
# интервал проверки времени жизни процессов приложения
FcgidIdleScanInterval 120

<VirtualHost *:80>
    ServerName one.com
    DocumentRoot /var/www/one.com/html
    
    # максимальное количество работающих процессов для этого виртуального хоста
    FcgidMaxProcessesPerClass 300
    # минимальное количество работающих процессов для этого виртуального хоста
    FcgidMinProcessesPerClass 30

    # Переменные окружения для запуска процессов можем задать здесь через директиву
    # FcgidInitialEnv или в shell-скрипте, который указан в директиве FcgidWrapper.
    # Нет смысла задавать в двух местах сразу — за исключением случая, когда есть
    # два wrapper для этого виртуального хоста, каждый из которых запускает процессы
    # для обслуживания своего приложения с разными настройками.

    # Следующие две директивы не имеют смысла, значения уже были заданы глобально
    FcgidInitialEnv PHP_FCGI_MAX_REQUESTS 0
    FcgidInitialEnv PHP_FCGI_CHILDREN 0

    Alias /app-one/ /var/www/one.com/app-one/
    <Directory "/var/www/one.com/app-one/">
        Require all granted
        Options ExecCGI
        <FilesMatch ".+\.php$">
            SetHandler fcgid-script
            FcgidWrapper /var/www/one.com/php-run/app-one-wrapper.sh
        </FilesMatch>
    </Directory>
    
    Alias /app-two/ /var/www/one.com/app-two/
    <Directory "/var/www/one.com/app-two/">
        Require all granted
        Options ExecCGI
        <FilesMatch ".+\.php$">
            SetHandler fcgid-script
            FcgidWrapper /var/www/one.com/php-run/app-two-wrapper.sh
        </FilesMatch>
    </Directory>
</VirtualHost>

<VirtualHost *:80>
    ServerName two.com
    DocumentRoot /var/www/two.com/html

    FcgidMaxProcessesPerClass 200
    FcgidMinProcessesPerClass 20

    # Следующие две директивы не имеют смысла, значения уже были заданы глобально
    FcgidInitialEnv PHP_FCGI_MAX_REQUESTS 0
    FcgidInitialEnv PHP_FCGI_CHILDREN 0

    Alias /app-one/ /var/www/two.com/app-one/
    <Directory "/var/www/two.com/app-one/">
        Require all granted
        Options ExecCGI
        <FilesMatch ".+\.php$">
            SetHandler fcgid-script
            FcgidWrapper /var/www/two.com/php-run/app-one-wrapper.sh
        </FilesMatch>
    </Directory>
    
    Alias /app-two/ /var/www/two.com/app-two/
    <Directory "/var/www/two.com/app-two/">
        Require all granted
        Options ExecCGI
        <FilesMatch ".+\.php$">
            SetHandler fcgid-script
            FcgidWrapper /var/www/two.com/php-run/app-two-wrapper.sh
        </FilesMatch>
    </Directory>
</VirtualHost>

Четыре wrapper для четырех php-приложений на двух виртуальных хостах

# cat /var/www/one.com/php-run/app-one-wrapper.sh
#!/bin/bash
# директория размещения файла конфигурации php.ini
PHPRC="/var/www/one.com/php-ini/app-one/"
export PHPRC
exec /usr/bin/php-cgi
# cat /var/www/one.com/php-run/app-two-wrapper.sh
#!/bin/bash
# директория размещения файла конфигурации php.ini
PHPRC="/var/www/one.com/php-ini/app-two/"
export PHPRC
exec /usr/bin/php-cgi
# cat /var/www/two.com/php-run/app-one-wrapper.sh
#!/bin/bash
# директория размещения файла конфигурации php.ini
PHPRC="/var/www/two.com/php-ini/app-one/"
export PHPRC
exec /usr/bin/php-cgi
# cat /var/www/two.com/php-run/app-two-wrapper.sh
#!/bin/bash
# директория размещения файла конфигурации php.ini
PHPRC="/var/www/two.com/php-ini/app-two/"
export PHPRC
exec /usr/bin/php-cgi

3. Установка и настройка PHP FastCGI

Первым делом деактивируем модуль Apache для работы с PHP

# ls -la /etc/apache2/mods-enabled | grep php
lrwxrwxrwx 1 root root   29 мар 25 08:59 php8.1.conf -> ../mods-available/php8.1.conf
lrwxrwxrwx 1 root root   29 мар 25 08:59 php8.1.load -> ../mods-available/php8.1.load
# a2dismod php8.1

Устанавливаем модуль Apache для работы с FastCGI — это пакет libapache2-mod-fcgid

# apt install libapache2-mod-fcgid

Проверим, что нам теперь доступен модуль mod_fcgid

# ls -la /etc/apache2/mods-available | grep fcgid
-rw-r--r-- 1 root root   133 сен  4  2018 fcgid.conf
-rw-r--r-- 1 root root    62 сен  4  2018 fcgid.load

Устанавливаем пакет php-cgi, который позволяет запускать PHP-скрипты как CGI. Это отличается от того, как мы запускали скрипты из каталога cgi-bin — там в начале скрипта мы прописывали путь к интерпретатору PHP и скрипт выполнял php-cli.

# apt install php-cgi

Активируем модуль Apache для работы с FastCGI

# a2enmod fcgid

Отредактируем файл конфигурации этого модуля

# cat /etc/apache2/mods-available/fcgid.conf
<IfModule fcgid_module>
    # максимальное количество одновременно работающих процессов
    FcgidMaxProcesses 500
    # запрещаем PHP самому управлять дочерними процессами
    FcgidInitialEnv PHP_FCGI_CHILDREN 0
    # отключаем завершение работы процессов по кол-ву запросов
    FcgidMaxRequestsPerProcess 0
    FcgidInitialEnv PHP_FCGI_MAX_REQUESTS 0
    # максимальное время жизни процесса приложения FastCGI
    FcgidProcessLifeTime 3600
</IfModule>

Отредактируем файл конфигурации виртуального хоста example.net

# cat /etc/apache2/sites-available/example.net.conf
<VirtualHost *:80>
    ServerName example.net
    ServerAlias www.example.net
    ServerAdmin webmaster@example.net

    DocumentRoot /var/www/example.net/html
    DirectoryIndex index.html
    Options -Indexes

    # Разрешаем выполнение php-скритов как fcgi в каталоге
    # /var/www/example.net/fcgi-php/, скрипты будут доступны
    # при обращении к URL http://example.net/fcgi-php/foo.php.
    # Доступ к любым другим файлам в этом каталоге запрещен.
    Alias /fcgi-php/ /var/www/example.net/fcgi-php/
    <IfModule fcgid_module>
        FcgidMaxProcessesPerClass 200
        FcgidMinProcessesPerClass 20

        FcgidInitialEnv PHPRC /var/www/example.net/php-ini/
        FcgidInitialEnv PHP_FCGI_MAX_REQUESTS 0
        FcgidInitialEnv PHP_FCGI_CHILDREN 0
        <Directory "/var/www/example.net/fcgi-php">
            Require all denied
            Options ExecCGI
            <FilesMatch ".+\.php$">
                Require all granted
                SetHandler fcgid-script
                FcgidWrapper /var/www/example.net/php-run/wrapper.sh
            </FilesMatch>
        </Directory>
    </IfModule>

    ErrorLog ${APACHE_LOG_DIR}/example-net-error.log
    CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined
</VirtualHost>

Создаем скрипт для запуска процессов PHP как FastCGI для example.net

# nano /var/www/example.net/php-run/wrapper.sh
#!/bin/bash
# переменные окружения уже заданы директивой FcgidInitialEnv,
# нет смысла их повторять в скрипте запуска процессов FastCGI
exec /usr/bin/php-cgi
# chmod +x /var/www/example.net/php-run/wrapper.sh

Копируем файл конфигурации PHP — у виртуального хоста будет свой php.ini

# mkdir /var/www/example.net/php-ini
# cp /etc/php8.1/cgi/php.ini /var/www/example.net/php-ini/

Создаем директорию для php-скриптов и скрипт phpinfo.php

# mkdir /var/www/example.net/fcgi-php
# nano /var/www/example.net/fcgi-php/phpinfo.php
<h3>Вывод функции phpinfo()</h3>
<?php
phpinfo();
?>

Перезапускаем Apache, чтобы применить новую конфигурацию

# systemctl restart apache2.service

Набираем в адресной строке браузера http://example.net/fcgi-php/phpinfo.php

Если с запуском процессов что-то не так — можно проверить переменные окружения

# nano /var/www/example.net/fcgi-php/env-vars.php
<h3>Переменные окружения</h3>
<pre>
<?php
exec('env', $output);
print_r($output);
?>
<pre>

4. Логирование ошибок PHP FastCGI

Ошибки php-скриптов сейчас записываются в лог ошибок Apache, есть смысл создать отдельный лог

# nano /var/www/example.net/php-ini/php.ini
display_errors = Off
log_errors = On
error_log = /var/log/php/example-net-errors.log

Создаем директорию для хранения логов ошибок и делаем владельцем пользователя www-data

# mkdir /var/log/php
# chown www-data:www-data /var/log/php

Перезапускаем Apache, чтобы применить новую конфигурацию

# systemctl restart apache2.service
Но нужно еще настроить ротацию логов ошибок php-скриптов — иначе файл лога будет разрастаться бесконечно, пока не займет все место.
# cat /var/log/php/example-net-errors.log
[...] PHP Parse error: syntax error, unexpected end of file in /var/www/example.net/fcgi-php/phpinfo.php on line 4
[...] PHP Fatal error: Uncaught Error: Call to undefined function info() in /var/www/example.net/fcgi-php/phpinfo.php:3
Stack trace:
#0 {main}
  thrown in /var/www/example.net/fcgi-php/phpinfo.php on line 3

5. Создаем второй виртуальный хост

Пусть это будет example.org — для него тоже настроим запуск PHP как FastCGI. Как создавать виртуальный хост — повторять не буду. Считаем, что создали файл конфигурации и создали корневую директорию хоста.

# nano /etc/apache2/sites-available/example.org.conf
<VirtualHost *:80>
    ServerName example.org
    ServerAlias www.example.org
    ServerAdmin webmaster@example.org

    DocumentRoot /var/www/example.org/html
    DirectoryIndex index.html
    Options -Indexes

    <IfModule fcgid_module>
        FcgidMaxProcessesPerClass 300
        FcgidMinProcessesPerClass 30

        FcgidInitialEnv PHP_FCGI_MAX_REQUESTS 0
        FcgidInitialEnv PHP_FCGI_CHILDREN 0

        Alias /shop/ /var/www/example.org/shop/
        <Directory "/var/www/example.org/shop/">
            Require all granted
            Options ExecCGI
            <FilesMatch ".+\.php$">
                SetHandler fcgid-script
                # опция -c предписывает искать php.ini в /var/www/example.org/php-ini/shop/
                FcgidWrapper "/usr/bin/php-cgi -c /var/www/example.org/php-ini/shop/"
            </FilesMatch>
        </Directory>

        Alias /blog/ /var/www/example.org/blog/
        <Directory "/var/www/example.org/blog/">
            Require all granted
            Options ExecCGI
            <FilesMatch ".+\.php$">
                SetHandler fcgid-script
                # опция -c предписывает искать php.ini в /var/www/example.org/php-ini/blog/
                FcgidWrapper "/usr/bin/php-cgi -c /var/www/example.org/php-ini/blog/"
            </FilesMatch>
        </Directory>
    </IfModule>

    ErrorLog ${APACHE_LOG_DIR}/example-org-error.log
    CustomLog ${APACHE_LOG_DIR}/example-org-access.log combined
</VirtualHost>

Мы здесь не используем скрипты wrapper для запуска процессов, потому что переменные PHP_FCGI_MAX_REQUESTS и PHP_FCGI_CHILDREN устанавливаем для виртуального хоста в целом, а отдельный php.ini для каждого PHP приложения задаем с помощью опции для php-cgi.

# mkdir /var/www/example.org/php-ini/
# mkdir /var/www/example.org/php-ini/shop/
# mkdir /var/www/example.org/php-ini/blog/
# cp /etc/php/8.1/cgi/php.ini /var/www/example.org/php-ini/shop/
# cp /etc/php/8.1/cgi/php.ini /var/www/example.org/php-ini/blog/

Перезапускаем Apache, чтобы применить новую конфигурацию

# systemctl restart apache2.service

Запись ошибок php-скриптов в отдельный файл пропустим — все по аналогии с виртуальным хостом example.net, нет смысла повторять.

Установка службы PHP-FPM

FPM расшифровывается как Fastcgi Process Manager, менеджер процессов FastCGI. PHP-FPM запускается как отдельный процесс и взаимодействует с веб-сервером через порт 9000 или сокетный файл. Является альтернативной реализацией PHP FastCGI с несколькими дополнительными возможностями, обычно используемыми для высоконагруженных сайтов.

Деактивируем модуль для работы с FastCGI и модуль для работы с PHP

# a2dismod fcgid
# a2dismod php8.1

Теперь нужно установить пакет php-fpm

# apt install php8.1-fpm
..........
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-FPM

# systemctl is-active php8.1-fpm.service
active

Файл конфигурации службы — это /etc/php/8.1/fpm/php-fpm.conf, мы его трогать не будем — это тема для отдельного разговора

[global]
; Pid file
; Note: the default prefix is /var
; Default Value: none
; Warning: if you change the value here, you need to modify systemd
; service PIDFile= setting to match the value here.
pid = /run/php/php8.1-fpm.pid

; Error log file
; If it's set to "syslog", log is sent to syslogd instead of being written
; into a local file.
; Note: the default prefix is /var
; Default Value: log/php-fpm.log
error_log = /var/log/php8.1-fpm.log

; ..... прочие директивы конфигурации .....

; Include one or more files. If glob(3) exists, it is used to include a bunch of
; files from a glob(3) pattern. This directive can be used everywhere in the
; file.
; Relative path can also be used. They will be prefixed by:
;  - the global prefix if it's been set (-p argument)
;  - /usr otherwise
include=/etc/php/8.1/fpm/pool.d/*.conf

После установки PHP-FPM есть файл конфигурации пула www.conf для обслуживания запросов от веб-сервера. В PHP-FPM каждый запрос обрабатывается заранее запущенным процессом из пула. Потому что запуск процесса — дорогая операция, лучше запустить все процессы заранее. Все процессы в пуле — однотипные, предназначены для обработки запросов от одного сайта. Для нашего сайта example.net мы создадим отдельный пул.

# cp /etc/php/8.1/fpm/pool.d/www.conf /etc/php/8.1/fpm/pool.d/example-net.conf

И отредактируем файл конфигурации пула, изменим только имя и директиву listen. Настройка пула — это тема для отдельного разговора.

# nano /etc/php/8.1/fpm/pool.d/example-net.conf
; имя пула, должно быть обязательно задано и быть уникальным
[example-net]

; пользователь и группа, от имени которого работают процессы
user = www-data
group = www-data

listen = /run/php/php8.1-fpm-example-net.sock

; пользователь и группа, которые могут читать и записывать unix-сокет
; здесь указываются пользователь и группа, под которым работает apache
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

Вообще, было бы правильно создать отдельного пользователя, от имени которого PHP-FPM запускал бы процессы для этого пула. Но упростим себе немного задачу — пусть процессы запускаются от имени уже существующего пользователя www-data.

Файл пула www.conf нужно удалить или переименовать — чтобы этот пул не создавался и мы не тратили ресурсы сервера впустую.

# mv /etc/php/8.1/fpm/pool.d/www.conf /etc/php/8.1/fpm/pool.d/www.back

Перезапускаем службу, чтобы применить новые настройки

# systemctl restart php8.1-fpm.service

Проверяем работу службы PHP-FPM с новыми настройками

# systemctl is-active php8.1-fpm.service
active

Смотрим запущенные процессы пользователя www-data

# ps -u www-data -F
UID          PID    PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
www-data    3559    3557  0 50794  7232   0 10:38 ?        00:00:00 php-fpm: pool example-net
www-data    3560    3557  0 50794  7232   0 10:38 ?        00:00:00 php-fpm: pool example-net
www-data    3642    3641  0 188345 4928   0 10:40 ?        00:00:00 /usr/sbin/apache2 -k start
www-data    3643    3641  0 188349 5216   0 10:40 ?        00:00:00 /usr/sbin/apache2 -k start

Их всего четыре, два дочерних процесса Apache и два процесса из пула example-net.

Взаимодействие Apache и PHP-FPM

Чтобы принимать запросы FastCGI от Apache, PHP-FPM может прослушивать TCP/IP сокет или UNIX сокет. Сокеты UNIX являются средством межпроцессного взаимодействия, которое обеспечивает эффективный обмен данными между процессами, работающими на одном сервере, в то время как сокеты TCP/IP позволяют процессам обмениваться данными по сети.

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

Таким образом, сокет UNIX является безопасным, поскольку его могут использовать только процессы на локальном хосте. Сокет TCP/IP может быть доступен из интернета, и это может представлять угрозу безопасности, если не будут приняты дополнительные меры безопасности, такие как настройка брандмауэра.

1. Подключение через UNIX сокет

При установке пакета php-fpm была подсказка, как разрешить взаимодействие сервера Apache и службы PHP-FPM — давайте так и сделаем

# a2enmod proxy_fcgi setenvif
# a2enconf php8.1-fpm
# systemctl restart apache2.service

Давайте посмотрим на файл конфигурации /etc/apache2/conf-available/php8.1-fpm.conf

# Redirect to local php-fpm if mod_php is not available
<IfModule !mod_php8.c>
    <IfModule proxy_fcgi_module>
        # Enable http authorization headers
        <IfModule setenvif_module>
            SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1
        </IfModule>

        <FilesMatch ".+\.ph(ar|p|tml)$">
            SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost"
        </FilesMatch>

        # Deny access to raw php sources by default
        <FilesMatch ".+\.phps$">
            Require all denied
        </FilesMatch>
        # Deny access to files without filename
        <FilesMatch "^\.ph(ar|p|ps|tml)$">
            Require all denied
        </FilesMatch>
    </IfModule>
</IfModule>

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

<VirtualHost *:80>
    ServerName example.net
    ServerAlias www.example.net
    ServerAdmin webmaster@example.net

    DocumentRoot /var/www/example.net/html
    DirectoryIndex index.php index.html
    Options -Indexes

    <FilesMatch ".+\.ph(ar|p|tml)$">
        SetHandler "proxy:unix:/run/php/php8.1-fpm-example-net.sock|fcgi://localhost"
    </FilesMatch>

    ErrorLog ${APACHE_LOG_DIR}/example-net-error.log
    CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined
</VirtualHost>

2. Подключение через TCP/IP сокет

Редактируем файл конфигурации пула, изменяем значение директивы listen

# nano /etc/php/8.1/fpm/pool.d/example-net.conf
; имя пула, должно быть обязательно задано и быть уникальным
[example-net]

; пользователь и группа, от имени которого работают процессы
user = www-data
group = www-data

listen = 127.0.0.1:9000

; пользователь и группа, которые могут читать и записывать unix-сокет
; здесь указываются пользователь и группа, под которым работает apache
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
# systemctl restart php8.1-fpm.service

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

<VirtualHost *:80>
    ServerName example.net
    ServerAlias www.example.net
    ServerAdmin webmaster@example.net

    DocumentRoot /var/www/example.net/html
    DirectoryIndex index.php index.html
    Options -Indexes

    <FilesMatch ".+\.ph(ar|p|tml)$">
        SetHandler "proxy:fcgi://127.0.0.1:9000"
    </FilesMatch>

    ErrorLog ${APACHE_LOG_DIR}/example-net-error.log
    CustomLog ${APACHE_LOG_DIR}/example-net-access.log combined
</VirtualHost>
# systemctl reload apache2.service

Файл php.ini для PHP-FPM

Если мы создаем новый виртуальный хост и хотим обрабатывать php-файлы — нужно создать новый пул процессов php-fpm и для директивы listen задать другое значение (хост и порт или файл сокета). Соответственно, в файле конфигурации нового виртуального хоста для директивы SetHandler указать новое значение.

# nano /etc/php/8.1/fpm/pool.d/example-org.conf
; имя пула, должно быть обязательно задано и быть уникальным
[example-org]

; пользователь и группа, от имени которого работают процессы
user = www-data
group = www-data

listen = /run/php/php8.1-fpm-example-org.sock

; пользователь и группа, которые могут читать и записывать unix-сокет
; здесь указываются пользователь и группа, под которым работает apache
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
# nano /etc/apache2/sites-available/example.org.conf
<VirtualHost *:80>
    ServerName example.org
    ServerAlias www.example.org
    ServerAdmin webmaster@example.org

    DocumentRoot /var/www/example.org/html
    DirectoryIndex index.php index.html
    Options -Indexes

    <FilesMatch ".+\.ph(ar|p|tml)$">
        SetHandler "proxy:unix:/run/php/php8.1-fpm-example-org.sock|fcgi://localhost"
    </FilesMatch>

    ErrorLog ${APACHE_LOG_DIR}/example-org-error.log
    CustomLog ${APACHE_LOG_DIR}/example-org-access.log combined
</VirtualHost>

При этом, оба пула процессов будут использовать один файл конфигурации /etc/php/8.1/fmp/php.ini. Чтобы установить индивидуальные значения конфигурации PHP для каждого пула — их нужно прописать в файле конфигурации пула процессов.

# nano /etc/php/8.1/fpm/pool.d/example-net.conf
[example-net]
# ... прочие директивы конфигурации ...
listen = /run/php/php8.1-fpm-example-net.sock
# ... прочие директивы конфигурации ...
request_slowlog_timeout = 5
slowlog = /var/log/php-fpm/example-net-slow.log
# ... прочие директивы конфигурации ...
php_flag[display_errors] = off
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php-fpm/example-net-error.log
php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 16M
php_value[session.save_handler] = files
php_value[session.save_path] = /var/www/example.net/session
# nano /etc/php/8.1/fpm/pool.d/example-org.conf
[example-org]
# ... прочие директивы конфигурации ...
listen = /run/php-fpm/php8.1-fpm-example-org.sock
# ... прочие директивы конфигурации ...
request_slowlog_timeout = 5
slowlog = /var/log/php-fpm/example-org-slow.log
# ... прочие директивы конфигурации ...
php_flag[display_errors] = off
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php-fpm/example-org-error.log
php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 32M
php_value[session.save_handler] = files
php_value[session.save_path] = /var/www/example.org/session

Директива php_admin_value используется для установки строковых значений, директива php_admin_flag — для установки флагов типа on или off. Директивы php_value и php_flag позволяют задать значения, которые потом можно перезаписать с помощью функции ini_set. Директивы с использованием admin перезаписать нельзя.

Создаем директорию для хранения логов ошибок и делаем владельцем пользователя www-data

# mkdir /var/log/php-fpm
# chown www-data:www-data /var/log/php-fpm
Но нужно еще настроить ротацию логов в директории /var/log/php-fpm — иначе файлы логов будут разрастаться бесконечно, пока не займут все место.

Создаем директории для хранения файлов сессий и делаем владельцем пользователя www-data

# mkdir /var/www/example.net/session
# chown www-data:www-data /var/www/example.net/session
# mkdir /var/www/example.org/session
# chown www-data:www-data /var/www/example.org/session

Поиск: Apache • CGI • FastCGI • Linux • SSL • Конфигурация • Настройка • Установка • Сервер

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.