Выберите продукт

SELinux в AlmaLinux и Rocky Linux: Nginx, Apache, PHP-FPM и разбор audit.log

SELinux часто выглядит как невидимый файрвол для веб-сервера: права в Linux верные, а сайт отвечает 403 или PHP-FPM даёт 502. Разберём, как на AlmaLinux и Rocky Linux читать audit.log, понимать audit2why и исправлять причины безопасно.
SELinux в AlmaLinux и Rocky Linux: Nginx, Apache, PHP-FPM и разбор audit.log

Привет, это Вася из Fastfox. Если вы администрируете AlmaLinux или Rocky Linux, рано или поздно встретите ситуацию: владелец файла правильный, права 0644 или 0755 выставлены, Nginx или Apache перезапущен, PHP-FPM жив, а сайт всё равно отдаёт 403, 404 на существующий файл, 502 или загадочное Permission denied. На Debian-подобных системах мы часто сразу смотрим Unix-права и логи веб-сервера. В RHEL-совместимых дистрибутивах есть ещё один важный слой — SELinux.

Главная мысль статьи: SELinux не нужно отключать при первой ошибке. В режиме Enforcing он действительно может блокировать доступы, которые выглядят нормальными с точки зрения классических прав Linux, но почти всегда это диагностируется через audit.log и исправляется точечно: правильным контекстом, boolean-переключателем или, в редких случаях, небольшим локальным модулем политики.

Если после setenforce 0 сайт внезапно заработал, это не решение, а только подтверждение диагноза. Возвращаем Enforcing и разбираем конкретный запрет.

Как SELinux смотрит на веб-стек

SELinux работает не вместо обычных прав доступа, а вместе с ними. Сначала должны быть корректны владелец, группа, режимы доступа, ACL и права на каждый каталог по пути. Затем вступает в игру SELinux: у процесса есть домен, у файла, сокета или порта — тип, а политика решает, разрешено ли домену работать с этим типом.

Для веб-сервера это особенно заметно. Процессы Nginx и Apache обычно работают в домене, связанном с HTTP-сервисами. Контент сайта должен иметь тип вроде httpd_sys_content_t, директории для записи — httpd_sys_rw_content_t, runtime-сокеты PHP-FPM — тип, который веб-серверу разрешено использовать. Если вы положили сайт в нестандартный путь, например /srv/www/example.com, SELinux не обязан автоматически считать его веб-контентом.

На AlmaLinux и Rocky Linux стандартный веб-контент в /var/www обычно размечается корректно пакетами и базовой политикой. Проблемы чаще появляются после миграции проекта из архива, ручного rsync, переноса на новый диск, bind mount, кастомного пути в /srv или /opt, отдельного пула PHP-FPM с собственным сокетом, нестандартного порта backend-сервиса. Если разворачиваете такой стек на VDS, SELinux стоит включать в план настройки сразу, а не после первого инцидента.

Быстрая проверка состояния SELinux и auditd

Начните с базовой диагностики. Нам нужно понять, включён ли SELinux, в каком он режиме и пишет ли система события аудита.

getenforce
sestatus
systemctl status auditd

Типичные режимы:

  • Enforcing — политика применяется, запрещённые действия блокируются;
  • Permissive — запреты только логируются, но не блокируют работу;
  • Disabled — SELinux отключён на уровне системы.

Для нормальной эксплуатации веб-сервера на AlmaLinux и Rocky Linux я рекомендую держать Enforcing. Временный переход в Permissive допустим на короткое время для подтверждения причины, но после этого лучше вернуть защиту обратно.

setenforce 0
getenforce
setenforce 1
getenforce

Если команд audit2why, audit2allow или semanage нет, установите нужные пакеты. На RHEL-совместимых системах они обычно находятся в policycoreutils-python-utils и связанных пакетах.

dnf install audit policycoreutils policycoreutils-python-utils setroubleshoot-server
systemctl enable --now auditd

Пакет setroubleshoot-server не обязателен, но часто помогает получить более человекочитаемые подсказки. На минимальных VDS его иногда не ставят, чтобы не тащить лишние зависимости; тогда достаточно auditd, ausearch, audit2why и audit2allow.

Анализ событий AVC в audit.log на сервере AlmaLinux или Rocky Linux

Где искать запреты: audit.log, ausearch и journalctl

Основной файл для расследования — /var/log/audit/audit.log. Не советую начинать с простого просмотра всего файла: он быстро разрастается, а строки AVC выглядят шумно. Удобнее фильтровать недавние события.

ausearch -m avc -ts recent
ausearch -m avc,user_avc,selinux_err -ts today
ausearch -m avc -ts today | audit2why

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

curl -I http://127.0.0.1/
ausearch -m avc -ts recent | audit2why

Иногда отказ виден и через journal, особенно если установлен setroubleshoot:

journalctl -t setroubleshoot --since today
journalctl -u nginx --since today
journalctl -u httpd --since today
journalctl -u php-fpm --since today

Важно сопоставлять время. Ошибка в error.log Nginx или Apache должна совпадать по времени с AVC-запретом. Если сайт отвечает 403, но в audit.log нет свежих событий, причина может быть не в SELinux: проверьте обычные права, root или alias в Nginx, Require all granted в Apache, правила .htaccess, владельца сокета PHP-FPM.

Как читать AVC-запрет без боли

В AVC-событии нас интересуют несколько полей: какой процесс пытался выполнить действие, над каким объектом, какой доступ был запрошен и какие SELinux-типы участвовали. В выводе audit2why часть этой логики уже разложена.

Пример команды для анализа свежих отказов:

ausearch -m avc -ts recent | audit2why

Если вывод говорит, что доступ был запрещён из-за неверного типа файла, не спешите генерировать модуль через audit2allow. В большинстве веб-кейсов правильнее назначить штатный тип через semanage fcontext и применить restorecon. Локальный модуль нужен тогда, когда у вас действительно нестандартное приложение и штатных типов или boolean-переключателей недостаточно.

Минимальный алгоритм чтения такой:

  1. Найдите процесс: nginx, httpd, php-fpm или дочерний процесс приложения.
  2. Посмотрите объект: файл, каталог, Unix-сокет, TCP-порт, сетевое соединение.
  3. Проверьте текущий контекст объекта через ls -Z, ps -eZ или semanage port -l.
  4. Сравните с ожидаемым типом для веб-контента, writable-директории, сокета или порта.
  5. Исправьте причину штатным способом и снова воспроизведите запрос.

Сценарий 1: Nginx или Apache отдаёт 403 на файлы сайта

Классика: сайт лежит в /srv/www/example.com/public, права выглядят нормально, но веб-сервер пишет Permission denied. Сначала проверяем обычный доступ по цепочке каталогов:

namei -l /srv/www/example.com/public/index.php
ls -la /srv/www/example.com/public

Затем смотрим SELinux-контексты:

ls -Zd /srv /srv/www /srv/www/example.com /srv/www/example.com/public
ls -Z /srv/www/example.com/public

Если файлы имеют что-то вроде default_t, admin_home_t или другой неподходящий тип, веб-сервер не обязан их читать. Назначаем постоянное правило контекста и применяем его:

semanage fcontext -a -t httpd_sys_content_t '/srv/www/example.com/public(/.*)?'
restorecon -Rv /srv/www/example.com/public
ls -Z /srv/www/example.com/public

Почему именно semanage fcontext, а не chcon? chcon меняет метку прямо сейчас, но она может слететь при полном restorecon или переустановке контекстов. semanage fcontext добавляет локальное правило в политику, и после этого restorecon будет восстанавливать нужную метку.

Для нескольких сайтов лучше размечать общий шаблон:

semanage fcontext -a -t httpd_sys_content_t '/srv/www/[^/]+/public(/.*)?'
restorecon -Rv /srv/www

В этом примере регулярное выражение применяется к путям контекстов SELinux. Оно удобно, но не превращайте его в слишком широкое правило на весь /srv, если там лежат не только публичные файлы.

Сценарий 2: PHP-FPM не может писать в cache, sessions или uploads

Для чтения статических файлов достаточно httpd_sys_content_t. Но каталоги, куда приложение пишет во время работы, должны быть размечены иначе. Это касается var/cache, storage, runtime, uploads, временных каталогов фреймворков и директорий с пользовательскими файлами.

Например, для Laravel-подобной структуры:

semanage fcontext -a -t httpd_sys_rw_content_t '/srv/www/example.com/storage(/.*)?'
semanage fcontext -a -t httpd_sys_rw_content_t '/srv/www/example.com/bootstrap/cache(/.*)?'
restorecon -Rv /srv/www/example.com/storage
restorecon -Rv /srv/www/example.com/bootstrap/cache

Для WordPress обычно нужны права записи в wp-content/uploads, иногда в wp-content/cache, если используется плагин кеширования:

semanage fcontext -a -t httpd_sys_rw_content_t '/srv/www/example.com/public/wp-content/uploads(/.*)?'
semanage fcontext -a -t httpd_sys_rw_content_t '/srv/www/example.com/public/wp-content/cache(/.*)?'
restorecon -Rv /srv/www/example.com/public/wp-content

Не размечайте весь сайт как httpd_sys_rw_content_t ради удобства. Это расширяет последствия возможной уязвимости в приложении: процесс PHP получит право менять код, шаблоны и другие файлы, которые должны быть только для чтения. Безопаснее разделять read-only код и writable-данные.

Сценарий 3: 502 Bad Gateway из-за сокета PHP-FPM

При связке Nginx и PHP-FPM частая причина 502 — проблема с Unix-сокетом. Сначала исключаем обычные вещи: php-fpm запущен, путь к сокету совпадает в конфиге Nginx и пуле FPM, владелец и группа позволяют веб-серверу подключаться.

systemctl status php-fpm
systemctl status nginx
ss -xl | grep php-fpm
ls -lZ /run/php-fpm

Если сокет вынесен в нестандартный путь, например /srv/run/example.sock, SELinux может не разрешить Nginx подключение. Для runtime-сокетов веб-стека обычно используют тип httpd_var_run_t.

mkdir -p /srv/run
semanage fcontext -a -t httpd_var_run_t '/srv/run(/.*)?'
restorecon -Rv /srv/run
systemctl restart php-fpm
systemctl restart nginx
ls -lZ /srv/run

Если сокет создаётся заново при рестарте PHP-FPM, важно, чтобы каталог уже имел правильный контекст. Иначе файл сокета может появиться с неподходящей меткой, и проблема вернётся после перезагрузки.

Для Apache с mod_proxy_fcgi логика похожая: проверяем путь к сокету, контекст каталога, права на сам сокет и AVC-запреты в audit.log. Разница в конфигурации веб-сервера, а не в принципах SELinux. Если выбираете веб-сервер под проект, может пригодиться отдельный разбор Nginx и Apache в современных веб-стеках.

FastFox VDS
Облачный VDS-сервер
Виртуальные серверы с быстрым запуском и гибкой конфигурацией от 390₽ / мес
Доступные локации
Россия Нидерланды

Сценарий 4: веб-приложение не может ходить в сеть

SELinux может запрещать HTTP-процессам исходящие сетевые соединения. Это всплывает, когда PHP-приложение обращается к внешнему API, локальному backend на TCP-порту, Redis, Memcached, SMTP или базе данных. В логах приложения вы увидите timeout, connection refused или permission denied, а в audit.log — AVC для сетевого доступа.

Проверьте boolean-переключатели:

getsebool -a | grep httpd_can_network
getsebool httpd_can_network_connect
getsebool httpd_can_network_connect_db

Для исходящих подключений к произвольным TCP-сервисам часто нужен httpd_can_network_connect:

setsebool -P httpd_can_network_connect on

Для подключения к базам данных может быть достаточно более узкого переключателя:

setsebool -P httpd_can_network_connect_db on

Здесь важно не включать всё подряд. Boolean — это штатный механизм политики, но он тоже расширяет возможности веб-процессов. Если приложению нужен только доступ к PostgreSQL или MySQL, проверьте, подходит ли специализированный boolean. Если нужен HTTP-запрос к API, тогда httpd_can_network_connect может быть оправдан.

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

getsebool httpd_can_sendmail
setsebool -P httpd_can_sendmail on

Сценарий 5: нестандартные порты для Nginx, Apache или backend

SELinux контролирует не только файлы, но и порты. Если Apache или Nginx должен слушать нестандартный порт, одного параметра listen в конфиге недостаточно. Нужно, чтобы порт был помечен подходящим типом, например http_port_t.

semanage port -l | grep http_port_t

Если веб-сервер должен слушать TCP-порт 8080, а его нет в списке http_port_t, добавьте:

semanage port -a -t http_port_t -p tcp 8080
systemctl restart nginx

Если порт уже существует с другим типом, команда добавления завершится ошибкой. Тогда нужно не добавлять, а изменить тип:

semanage port -m -t http_port_t -p tcp 8080

Проверяйте это при переносе приложений с Debian/Ubuntu на AlmaLinux или Rocky Linux: сервис, который раньше спокойно слушал 8081 или 8443, на SELinux-системе может не стартовать именно из-за типа порта.

Схема контекстов SELinux для портов, сокетов и файлов веб-сервера

Когда использовать audit2allow, а когда не стоит

audit2allow умеет превращать AVC-запреты в локальный модуль политики. Это мощный инструмент, но опасный при механическом применении. Он не понимает вашу архитектуру и не отличает правильное нестандартное поведение от ошибки разметки файлов.

Плохой подход выглядит так: собрать весь audit.log за неделю, сгенерировать модуль и установить его. Так можно разрешить старые, случайные и уже неактуальные запреты.

Более аккуратный подход:

  1. Воспроизвести одну конкретную проблему.
  2. Собрать только свежие AVC-события.
  3. Сначала проверить контексты, boolean и порты.
  4. Если штатного решения нет, сгенерировать минимальный локальный модуль.
  5. Сохранить исходный .te файл в репозитории инфраструктуры или хотя бы в документации сервера.
ausearch -m avc -ts recent --raw | audit2allow -M webapp_local
semodule -i webapp_local.pp
semodule -l | grep webapp_local

Перед установкой обязательно прочитайте сгенерированный файл webapp_local.te. Если там разрешения на широкие типы, доступы к домашним каталогам, системным файлам или неожиданным процессам, остановитесь и вернитесь к диагностике. Очень часто после внимательного просмотра оказывается, что нужен не модуль, а один restorecon.

Удалить локальный модуль можно так:

semodule -r webapp_local

Полезные команды для ежедневной диагностики

Ниже — небольшой набор команд, который я обычно держу под рукой на веб-серверах с SELinux.

getenforce
sestatus
ps -eZ | grep -E 'nginx|httpd|php-fpm'
ls -Z /var/www
ausearch -m avc -ts recent
ausearch -m avc -ts recent | audit2why
semanage fcontext -l | grep httpd
semanage port -l | grep http_port_t
getsebool -a | grep httpd

Если нужно быстро проверить конкретный путь:

matchpathcon /srv/www/example.com/public/index.php
ls -Z /srv/www/example.com/public/index.php

matchpathcon показывает, какой контекст должен быть у пути согласно правилам. Если фактический ls -Z отличается, применяйте restorecon.

restorecon -Rv /srv/www/example.com

Практический runbook: 403, 502 или Permission denied

Когда инцидент уже случился, удобно идти по короткому плану, а не прыгать между конфигами.

Если Nginx или Apache отдаёт 403

  1. Проверьте путь к файлу и обычные права: namei -l, ls -la.
  2. Проверьте контексты: ls -Z для файла и всех важных каталогов.
  3. Посмотрите свежий audit.log: ausearch -m avc -ts recent | audit2why.
  4. Для публичных файлов назначьте httpd_sys_content_t.
  5. Для каталогов записи используйте httpd_sys_rw_content_t, но только там, где запись действительно нужна.

Если Nginx показывает 502 на PHP

  1. Проверьте статус php-fpm и веб-сервера.
  2. Сверьте путь к Unix-сокету или TCP-адресу.
  3. Проверьте владельца, группу и контекст сокета через ls -lZ.
  4. Для нестандартного каталога сокета задайте httpd_var_run_t.
  5. Если PHP подключается к сети, проверьте boolean-переключатели.

Если приложение не подключается к API или базе

  1. Проверьте обычную сетевую доступность: порт слушает, firewall не блокирует, DNS работает.
  2. Посмотрите AVC-события для момента ошибки.
  3. Проверьте httpd_can_network_connect и httpd_can_network_connect_db.
  4. Включайте только тот boolean, который соответствует реальной задаче приложения.

Типовые ошибки при работе с SELinux

Первая ошибка — отключать SELinux навсегда. Это действительно быстро убирает симптом, но вместе с ним убирает важный слой защиты. На публичном веб-сервере, особенно с CMS и PHP-приложениями, этот слой часто ограничивает ущерб от уязвимости в коде или плагине.

Вторая ошибка — использовать chmod 777. Если запрет идёт от SELinux, расширение Unix-прав не поможет, зато ухудшит модель безопасности. Более того, после таких прав приложение может начать писать туда, куда не должно, и диагностика станет ещё сложнее.

Третья ошибка — применять chcon без постоянного правила. Для быстрого теста это допустимо, но в рабочей конфигурации лучше фиксировать правило через semanage fcontext. Тогда после восстановления контекстов сервер останется в ожидаемом состоянии.

Четвёртая ошибка — генерировать модуль audit2allow на весь накопленный audit.log. В журнале могут быть события от старых экспериментов, сканеров, неудачных деплоев и уже исправленных проблем. Локальная политика должна быть минимальной и понятной.

SELinux — только один слой защиты. После базовой настройки полезно отдельно проверить TLS, заголовки браузерной безопасности и поведение прокси. Для веб-сервера пригодится практическая памятка по HTTP security headers в Nginx и Apache.

Нюансы для AlmaLinux и Rocky Linux разных версий

AlmaLinux и Rocky Linux следуют RHEL-экосистеме, но версии политик, пакетов Nginx, Apache и PHP-FPM могут отличаться между 8-й, 9-й и будущими ветками. На практике это значит: не копируйте команды без проверки текущих типов и boolean. Сначала смотрите, что есть в системе.

cat /etc/os-release
rpm -q selinux-policy selinux-policy-targeted
rpm -q nginx httpd php-fpm

Если Nginx установлен из стороннего репозитория, а не из базовых репозиториев ОС, пути к логам, runtime-каталогам и unit-файлам могут отличаться. SELinux при этом продолжает оценивать реальные процессы, файлы и сокеты, поэтому диагностика через audit.log остаётся тем же источником правды.

С Apache обычно больше готовых правил из коробки, потому что исторически RHEL-экосистема тесно интегрирована с httpd. Но это не означает, что Nginx плохо работает с SELinux. Просто для нестандартных раскладок Nginx плюс PHP-FPM чаще приходится явно размечать пути и сокеты.

Как безопасно документировать изменения

SELinux-настройки легко забыть, особенно если сервер живёт годами. Хорошая практика — хранить рядом с проектом короткий файл с описанием локальных правил: какие пути размечены, какие boolean включены, какие порты добавлены, есть ли локальный модуль политики.

Снять текущую картину можно так:

semanage fcontext -l -C
semanage port -l -C
semanage boolean -l -C
semodule -l | grep local

Ключ -C показывает локальные изменения относительно базовой политики. Это удобно при миграции на новый VDS, восстановлении из бэкапа или расследовании, почему два почти одинаковых сервера ведут себя по-разному.

Для инфраструктуры, управляемой Ansible, Salt или другим CM-инструментом, лучше описывать SELinux-правила декларативно. Тогда новый сервер получает не только Nginx, Apache и PHP-FPM, но и корректную SELinux-разметку с первого деплоя.

Итоги

SELinux в AlmaLinux и Rocky Linux — не враг веб-администратора, а строгий контролёр границ. Он мешает только тогда, когда система не знает, что ваш нестандартный путь, сокет, порт или сетевое подключение являются нормальной частью веб-приложения. Вместо отключения защиты лучше читать audit.log, прогонять события через audit2why, проверять контексты и исправлять причину штатными инструментами.

Для Nginx, Apache и PHP-FPM чаще всего хватает четырёх приёмов: semanage fcontext плюс restorecon для файлов, httpd_sys_rw_content_t только для каталогов записи, httpd_var_run_t для нестандартных сокетов и аккуратного включения boolean-переключателей для сети. audit2allow оставляйте как последний инструмент, когда вы точно понимаете, какое поведение разрешаете.

Если вы привыкнете начинать расследование с ausearch -m avc -ts recent | audit2why, SELinux перестанет быть магией. Он станет ещё одним понятным слоем диагностики — таким же привычным, как error.log, systemctl status и проверка прав на файлы.

Поделиться статьей

Вам будет интересно

AlmaLinux и Rocky Linux: настройка firewalld на VDS и rich rules OpenAI Статья написана AI (GPT 5)

AlmaLinux и Rocky Linux: настройка firewalld на VDS и rich rules

Разберём, как аккуратно настроить firewalld на VDS с AlmaLinux или Rocky Linux: открыть SSH и веб-порты 80/443, проверить зоны, пр ...
PostgreSQL major upgrade в Linux: pg_upgrade, backup и rollback OpenAI Статья написана AI (GPT 5)

PostgreSQL major upgrade в Linux: pg_upgrade, backup и rollback

Major upgrade PostgreSQL нельзя делать как обычное обновление пакетов. Разбираем рабочий сценарий для Linux: подготовка, резервная ...
BorgBackup на Debian/Ubuntu: бэкап VDS по SSH через systemd timer OpenAI Статья написана AI (GPT 5)

BorgBackup на Debian/Ubuntu: бэкап VDS по SSH через systemd timer

Покажу, как на Debian и Ubuntu настроить BorgBackup для VDS: отдельный SSH-ключ, репозиторий на удалённом сервере, скрипт с borg c ...