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

systemd sandboxing для Nginx: ProtectSystem, PrivateTmp, DynamicUser и CapabilityBoundingSet

Пошагово усиливаем Nginx на Linux через systemd sandboxing: включаем NoNewPrivileges и PrivateTmp, делаем файловую систему почти read-only с ProtectSystem и ReadWritePaths, настраиваем DynamicUser и минимальные capabilities для портов 80/443.
systemd sandboxing для Nginx: ProtectSystem, PrivateTmp, DynamicUser и CapabilityBoundingSet

systemd — это не только автозапуск и рестарты сервисов. В современных дистрибутивах он даёт мощный набор механизмов sandboxing/hardening: можно ограничить файловую систему, временные каталоги, права ядра (capabilities) и даже запускать процесс без постоянного системного пользователя.

Ниже — практичный набор настроек systemd для Nginx: ProtectSystem, PrivateTmp, DynamicUser, CapabilityBoundingSet и NoNewPrivileges. Цель простая: уменьшить ущерб, если Nginx или модуль окажется уязвимым — процесс сможет сделать меньше «плохих вещей».

Важно: hardening всегда проверяйте на вашей схеме (статика, проксирование, кеши, ACME, логирование, ротации). Двигайтесь от безопасных опций к более «ломким» и опирайтесь на журнал при отладке.

Быстрый обзор: что именно мы защищаем

У Nginx типичные точки воздействия:

  • запись в каталог логов и временные каталоги (client body temp, proxy temp и т. п.);
  • доступ к конфигам, сертификатам и статике;
  • привилегия на bind к портам ниже 1024 (80/443), если процесс запускается не от root;
  • попытки «поднять привилегии» через exec и setuid-бинарники;
  • доступ к лишним частям файловой системы (например, /home, /root, /etc целиком).

systemd hardening закрывает это через:

  • ProtectSystem — монтирует часть FS как read-only для сервиса;
  • ReadWritePaths — точечно разрешает запись туда, где она реально нужна;
  • PrivateTmp — изолирует /tmp и /var/tmp;
  • DynamicUser — запускает сервис под временным UID/GID (без постоянного пользователя);
  • NoNewPrivileges — запрещает получать новые привилегии через exec;
  • CapabilityBoundingSet — оставляет только необходимые capabilities.

Подготовка: смотрим текущий unit и baseline

Сначала выясните, как Nginx запускается сейчас (unit может отличаться между Debian/Ubuntu, RHEL-подобными, кастомными сборками):

systemctl cat nginx

Полезно посмотреть текущие параметры sandboxing (если дистрибутив уже их включает):

systemd-analyze security nginx

systemd-analyze security не «гарантирует безопасность», но быстро показывает, какие ограничения уже включены и где остаются «дыры» в профиле.

Дальше не правим unit напрямую. Правильный путь — сделать drop-in override:

systemctl edit nginx

systemd создаст файл вида /etc/systemd/system/nginx.service.d/override.conf.

Проверка профиля безопасности сервиса nginx через systemd-analyze security

Шаг 1. NoNewPrivileges: дёшево, безопасно, почти не ломает

Начните с базовой защиты: запрет на получение новых привилегий. Это помогает против сценариев, когда процесс смог выполнить другой бинарник и «надеется» поднять права через setuid или file capabilities.

[Service]
NoNewPrivileges=yes

Примените изменения и перезапустите сервис:

systemctl daemon-reload
systemctl restart nginx
systemctl status nginx --no-pager

Если Nginx стартует — отлично. Обычно эта опция не влияет на работу.

Шаг 2. PrivateTmp: изоляция временных файлов

PrivateTmp даёт сервису отдельные /tmp и /var/tmp. Это снижает риск атак через подмену файлов в общих временных каталогах и уменьшает «соприкосновение» разных сервисов.

[Service]
PrivateTmp=yes

Перезапуск и проверка — как выше. Если у вас есть внешние скрипты или задания, которые ожидают увидеть файлы Nginx в общем /tmp, они перестанут их видеть. Для Nginx это чаще всего нормально, но нестандартные обвязки встречаются.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Шаг 3. ProtectSystem + ReadWritePaths: делаем FS «почти read-only»

ProtectSystem — ключевая ручка sandboxing. В режиме strict systemd делает read-only почти всё, кроме виртуальных путей. После этого нужно явно перечислить, куда Nginx имеет право писать.

Типичные пути записи для Nginx:

  • /var/log/nginx — логи (если пишете в файлы);
  • /run/nginx — PID-файл и/или сокеты (зависит от конфигурации);
  • /var/lib/nginx или /var/cache/nginx — кеши и temp-файлы (зависит от сборки и директив).

Включаем жёсткий режим и разрешаем запись точечно:

[Service]
ProtectSystem=strict
ReadWritePaths=/var/log/nginx
ReadWritePaths=/run/nginx
ReadWritePaths=/var/lib/nginx
ReadWritePaths=/var/cache/nginx

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

Как быстро понять, куда Nginx пытался писать

После неудачного старта смотрите журнал:

journalctl -u nginx -b --no-pager -n 200

И проверьте temp-пути в конфиге (директивы client_body_temp_path, proxy_temp_path, fastcgi_temp_path, uwsgi_temp_path, scgi_temp_path). Если они указывают на нестандартные каталоги — именно их и нужно добавить в ReadWritePaths или перенести на системные каталоги.

Шаг 4. DynamicUser: без постоянного пользователя и «домика» в системе

DynamicUser=yes просит systemd запускать сервис под динамическим UID/GID. Пользователь не живёт в /etc/passwd постоянно. Это удобно, когда вы хотите минимизировать «след» и не держать отдельные аккаунты.

Практическая оговорка для Nginx: исторически он стартует master-процессом с root ради bind на 80/443 и затем форкает worker’ы под пользователем из директивы user в конфиге. При DynamicUser удобнее запускать Nginx сразу под динамическим UID, а право на bind дать через capabilities (следующий шаг).

Минимальный вариант:

[Service]
DynamicUser=yes

И добавьте управляемые systemd каталоги (чтобы systemd сам создавал их с корректными правами на старте):

[Service]
RuntimeDirectory=nginx
RuntimeDirectoryMode=0755
StateDirectory=nginx
CacheDirectory=nginx
LogsDirectory=nginx

Если ваш unit уже готовит каталоги через ExecStartPre, не дублируйте логику. Выберите один «источник правды»: либо StateDirectory/CacheDirectory/LogsDirectory, либо подготовительные скрипты.

Подводные камни DynamicUser для Nginx

Чаще ломается не Nginx, а окружение:

  • сертификаты и ключи лежат в каталогах с правами «только root», и динамический пользователь не может их читать;
  • используются UNIX-сокеты (например, PHP-FPM) с жёсткими правами/группами: нужно согласовать владельцев и группы или перейти на TCP;
  • нестандартные пути cache/temp: их нужно перенести на каталоги, созданные systemd, или разрешить запись отдельно.

Частый компромисс: оставить статического пользователя (например, www-data), но всё равно включить ProtectSystem, PrivateTmp и capabilities-ограничения. DynamicUser особенно хорошо подходит для «чистых» reverse-proxy узлов.

Шаг 5. CapabilityBoundingSet: оставляем только то, что нужно

Linux capabilities позволяют дать процессу конкретные «частичные root-права» без полного root. Для Nginx в типичном сценарии нужна только возможность слушать привилегированные порты 80/443: CAP_NET_BIND_SERVICE.

Ограничиваем набор capabilities и делаем нужную capability доступной процессу без root:

[Service]
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=yes

После этого Nginx можно запускать не от root. Если в unit не задан пользователь, задайте его явно или используйте DynamicUser=yes:

[Service]
User=www-data
Group=www-data

Не добавляйте capabilities «наугад». Если что-то сломалось — сначала разберитесь по логам, что именно требуется процессу и почему.

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Рабочий пример override.conf (как отправная точка)

Ниже профиль, который часто подходит для «обычного» Nginx (статик + reverse proxy). Его почти всегда нужно подогнать под ваши пути логов, cache/temp и расположение сертификатов.

[Service]
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict

RuntimeDirectory=nginx
RuntimeDirectoryMode=0755
StateDirectory=nginx
CacheDirectory=nginx
LogsDirectory=nginx

DynamicUser=yes

CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE

ReadWritePaths=/run/nginx
ReadWritePaths=/var/cache/nginx
ReadWritePaths=/var/lib/nginx
ReadWritePaths=/var/log/nginx

Применение:

systemctl daemon-reload
systemctl restart nginx
systemctl status nginx --no-pager

Проверка: что hardening реально включился

Проверьте оценку безопасности:

systemd-analyze security nginx

Проверьте, под каким пользователем работает master/worker и какие effective capabilities у процессов:

ps -eo user,pid,ppid,cap_eff,cmd | grep -E 'nginx: master|nginx: worker' | grep -v grep

Проверьте, что Nginx слушает порты:

ss -lntp | grep nginx

Если включён HTTPS, отдельно проверьте чтение ключей и сертификатов: проблемы почти всегда видны в journald.

Диагностика ошибок прав доступа Nginx в journald после включения ProtectSystem

Типовые проблемы и быстрые решения

Nginx не может открыть access.log или error.log

Сверьте пути логов в конфиге и права. Если вы используете LogsDirectory=nginx, проще всего привести пути к /var/log/nginx. Если логи пишутся в нестандартный каталог — добавьте его в ReadWritePaths.

Ошибка создания temp-файлов (client_body_temp, proxy_temp)

Приведите temp-пути Nginx к /var/cache/nginx или /var/lib/nginx и убедитесь, что эти каталоги доступны на запись. При DynamicUser в связке с CacheDirectory=nginx чаще всего удобнее опираться на системный cache-каталог.

Не стартует из-за PID-файла

Либо уберите явный PID-файл из конфигурации (если он не нужен), либо используйте RuntimeDirectory=nginx и путь /run/nginx/nginx.pid, который гарантированно доступен сервису.

Куда двигаться дальше: дополнительные ручки systemd hardening

Мы сфокусировались на ключевых параметрах: ProtectSystem, PrivateTmp, DynamicUser, CapabilityBoundingSet, NoNewPrivileges. Но у systemd есть и другие ограничения, которые часто хорошо ложатся на Nginx:

  • ProtectHome=yes — закрыть доступ к /home;
  • ProtectKernelTunables=yes и ProtectKernelModules=yes — запретить менять tunables и модули;
  • RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX — оставить только нужные семейства;
  • SystemCallFilter= — фильтрация системных вызовов (мощно, но чаще всего самое «ломкое»).

Практика: усиливайте постепенно, фиксируйте изменения, и после каждого шага прогоняйте чек-лист (reload/restart, запросы на 80/443, загрузка больших файлов, проксирование, вебхуки, ACME, ротации логов).

Итог

systemd sandboxing — быстрый способ повысить безопасность Nginx без сторонних инструментов: сделать файловую систему почти read-only (ProtectSystem), изолировать временные каталоги (PrivateTmp), запретить получение новых привилегий (NoNewPrivileges), убрать лишние root-возможности и оставить только bind на низкие порты через capabilities (CapabilityBoundingSet и AmbientCapabilities). А при желании — уйти от постоянного пользователя через DynamicUser.

Главное — относиться к hardening как к инженерной настройке: минимально необходимые права, прозрачные пути записи и обязательная проверка по журналам при любом отказе.

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

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

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину

Ошибка mount: wrong fs type, bad option, bad superblock в Debian/Ubuntu может означать и простую опечатку в имени раздела, и пробл ...
Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление

Если XFS-раздел внезапно стал доступен только для чтения, а сервер ушёл в emergency mode, главное — не спешить. Разберём безопасны ...
Debian/Ubuntu: как исправить Failed to fetch при apt update OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить Failed to fetch при apt update

Ошибка Failed to fetch при apt update в Debian и Ubuntu обычно связана не с самим APT, а с DNS, сетью, зеркалом, прокси, временем ...