На сервере для деплоя почти всегда появляется отдельная учётная запись: deploy, ci, gitlab-runner, github-runner или похожая. Ей нужно залить релиз, переключить symlink, перезапустить сервис, перечитать конфигурацию Nginx, иногда выполнить миграции. Самый быстрый путь — добавить пользователя в группу sudo или wheel и забыть. Самый правильный путь — дать только те команды, которые реально нужны, и хранить это правило отдельно в /etc/sudoers.d.
В этой статье разберём практическую настройку sudoers для deploy user: как создать пользователя, как работать с sudoers.d, почему редактировать нужно через visudo, где уместен NOPASSWD, а где он превращает деплой в почти root-доступ. Примеры подойдут для Debian, Ubuntu, AlmaLinux, Rocky Linux, CentOS Stream и других RHEL-based систем, если в них используется обычный пакет sudo.
Главная идея: sudo для деплоя должен быть не удобной заменой root, а узким шлюзом к нескольким проверенным операциям.
Такая настройка особенно важна на production-серверах и VDS, где один технический пользователь часто обслуживает приложение, веб-сервер, очереди и CI/CD. Чем уже его права, тем меньше последствия ошибки в pipeline или утечки SSH-ключа.
Что такое sudoers и зачем нужен каталог sudoers.d
sudo решает, можно ли пользователю выполнить команду от имени другого пользователя, по правилам из конфигурации sudoers. Основной файл обычно находится по пути /etc/sudoers. В нём задаются глобальные параметры, группы администраторов и подключение каталога /etc/sudoers.d.
Редактировать всё в одном большом файле неудобно: правила смешиваются, сложнее понять, кто и зачем получил доступ, выше риск сломать синтаксис. Поэтому для отдельных ролей обычно создают отдельные файлы: например, /etc/sudoers.d/90-deploy, /etc/sudoers.d/80-backup, /etc/sudoers.d/70-monitoring.
У такого подхода есть несколько плюсов. Во-первых, правило для deploy user можно удалить или отключить одним файлом. Во-вторых, его легко положить под управление Ansible, Salt, Puppet или другого инструмента конфигурации. В-третьих, при аудите проще ответить на вопрос: какие привилегии есть у конкретной технической учётной записи.
Есть важная деталь: для файлов в /etc/sudoers.d лучше использовать имена без точки и без временных суффиксов редакторов. Надёжный вариант — 90-deploy, а не deploy.conf и не deploy.sudoers. На многих системах sudo игнорирует файлы с точкой или заканчивающиеся на ~, чтобы случайно не загрузить резервные копии редактора.
Почему нельзя править sudoers обычным редактором
Файл sudoers чувствителен к синтаксису. Одна лишняя запятая, неправильный alias или незакрытое правило — и вы можете получить ситуацию, когда sudo перестанет работать для всех. На удалённом сервере это особенно неприятно: если root-вход по SSH отключён, а активной root-сессии нет, исправление превратится в задачу через rescue-режим или консоль провайдера.
visudo защищает от такой ошибки. Он открывает файл в редакторе, блокирует одновременное редактирование и перед сохранением проверяет синтаксис. Для отдельного файла в sudoers.d используется ключ -f.
sudo visudo -f /etc/sudoers.d/90-deployПроверить всю конфигурацию можно отдельной командой:
sudo visudo -cЕсли sudo уже сломан, эти команды нужно выполнять из root-сессии без префикса sudo. Хорошая привычка перед любыми изменениями — открыть вторую SSH-сессию и держать её активной до проверки. Так у вас останется рабочий канал для отката.

Проверяем sudo и создаём deploy user
На большинстве серверных образов sudo уже установлен. Но на минимальных шаблонах Linux это не всегда так. Сначала проверим наличие утилит:
command -v sudo
command -v visudo
sudo -V | head -n 1Debian и Ubuntu
Если sudo не установлен, зайдите под root или используйте консоль сервера и установите пакет:
apt update
apt install sudoAlmaLinux, Rocky Linux, CentOS Stream, Oracle Linux
В RHEL-based дистрибутивах пакет обычно тоже есть в базовой системе, но на минимальном образе его можно поставить так:
dnf install sudoТеперь создадим отдельного пользователя для деплоя. Команды ниже одинаково подходят для Debian, Ubuntu, AlmaLinux, Rocky Linux и большинства близких систем, потому что useradd, passwd и install есть в базовой системе.
sudo useradd --create-home --shell /bin/bash deploy
sudo passwd --lock deploy
sudo install -d -o deploy -g deploy -m 0700 /home/deploy/.ssh
sudo install -o deploy -g deploy -m 0600 /tmp/deploy_authorized_keys /home/deploy/.ssh/authorized_keysЗдесь предполагается, что публичный SSH-ключ заранее лежит в файле /tmp/deploy_authorized_keys. Блокировка пароля через passwd --lock запрещает парольный вход, но не мешает SSH-аутентификации по ключу. Это нормальная практика для технических пользователей: доступ должен быть через ключи, а не через общий пароль, который неизбежно окажется в чате или CI-переменной.
Проверить пользователя можно так:
id deploy
sudo -l -U deployПока правил нет, sudo -l -U deploy либо покажет отсутствие привилегий, либо выведет только общие defaults. Это хороший исходный уровень: deploy user существует, но ещё ничего не может делать от root.
Модель least privilege для деплоя
Принцип least privilege означает: пользователь получает минимальный набор прав, достаточный для работы, и ничего сверх этого. Для деплоя это особенно важно, потому что CI/CD часто запускается автоматически: по push в репозиторий, по тегу, по webhook или по расписанию. Если pipeline скомпрометирован, злоумышленник унаследует ровно те права, которые вы дали deploy user.
Типовые задачи деплоя лучше разделить на две группы. Первая — операции с файлами приложения: загрузить новый релиз, распаковать архив, обновить зависимости, переключить символическую ссылку, очистить кеш в каталоге проекта. Для этого обычно не нужен sudo. Правильнее настроить владельца, группу, ACL или setgid-бит на директориях приложения.
Вторая группа — операции с системными сервисами: systemctl reload nginx, systemctl restart myapp.service, запуск безопасного скрипта, который делает ровно одну административную задачу. Вот для этой части sudo может быть оправдан.
Плохое правило выглядит так:
deploy ALL=(ALL) NOPASSWD: ALLОно удобно, но по смыслу почти не отличается от выдачи root-доступа. Если deploy user может выполнить любую команду через sudo без пароля, компрометация его SSH-ключа или CI-токена становится компрометацией всего сервера.
Лучше идти от списка конкретных действий. Например: деплой может проверить конфигурацию Nginx, перечитать Nginx и перезапустить один application service. Он не должен уметь устанавливать пакеты, менять пользователей, редактировать системные unit-файлы, читать приватные ключи TLS или открывать произвольный root shell.
Базовое правило в sudoers.d для deploy user
Перед тем как писать правило, узнайте реальные пути к командам. В sudoers лучше указывать абсолютные пути, потому что команда без пути зависит от окружения и PATH.
command -v systemctl
command -v nginxНа современных Debian, Ubuntu, AlmaLinux и Rocky Linux systemctl обычно находится в /usr/bin/systemctl, а nginx — в /usr/sbin/nginx. Но проверять всё равно стоит: серверы бывают с нестандартными пакетами и кастомными путями.
Откроем файл правила:
sudo visudo -f /etc/sudoers.d/90-deployПример минимального правила:
Defaults:deploy env_reset
Defaults:deploy secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Cmnd_Alias DEPLOY_NGINX = /usr/sbin/nginx -t, /usr/bin/systemctl reload nginx.service
Cmnd_Alias DEPLOY_APP = /usr/bin/systemctl restart myapp.service
deploy ALL=(root) NOPASSWD: DEPLOY_NGINX, DEPLOY_APPРазберём по строкам. Defaults:deploy env_reset сбрасывает окружение для конкретного пользователя. secure_path задаёт безопасный путь поиска программ при выполнении sudo-команд. Cmnd_Alias объединяет разрешённые команды в читаемые группы. Последняя строка разрешает пользователю deploy запускать эти команды от имени root без пароля.
После сохранения обязательно проверьте синтаксис:
sudo visudo -cИ посмотрите итоговые права пользователя:
sudo -l -U deployТестировать лучше из отдельной SSH-сессии под deploy:
sudo /usr/sbin/nginx -t
sudo /usr/bin/systemctl reload nginx.service
sudo /usr/bin/systemctl restart myapp.serviceЕсли команда не совпадает с правилом по пути или аргументам, sudo её не разрешит. Это не баг, а нужная нам защита. Например, если разрешено /usr/bin/systemctl restart myapp.service, то /usr/bin/systemctl restart sshd.service не должен проходить.
NOPASSWD: когда можно, а когда лучше не надо
NOPASSWD часто используют для CI/CD, потому что pipeline не умеет вводить интерактивный пароль. Сам по себе NOPASSWD не является злом: проблема начинается, когда он применяется к слишком широкому набору команд.
Допустимый вариант: NOPASSWD на один-два безопасных systemctl действия или на root-owned wrapper script, который нельзя изменять пользователю deploy. Опасный вариант: NOPASSWD на ALL, на пакетный менеджер, на редактор, на shell, на интерпретаторы вроде Python, Perl, Ruby, PHP, Node.js, на tar, rsync, find, vim, less и другие инструменты, через которые можно выполнить произвольную команду.
Особенно аккуратно с wildcard. В sudoers символ * может оказаться шире, чем кажется. Правило с произвольными аргументами для команды почти всегда требует отдельного анализа. Если нужно сложное поведение, лучше вынести его в небольшой скрипт, принадлежащий root, и разрешить через sudo только запуск этого скрипта без аргументов или с жёстко проверяемыми аргументами.
Wrapper script вместо сложных sudoers-правил
Представим, что деплой должен выполнить несколько действий: проверить Nginx, перечитать его и перезапустить application service. Можно перечислить все команды в sudoers, но иногда удобнее сделать один административный скрипт. Главное условие: deploy user не должен иметь права изменять этот скрипт или каталог, где он лежит.
Создадим файл /usr/local/sbin/deploy-reload-myapp от root:
sudo install -o root -g root -m 0755 /dev/null /usr/local/sbin/deploy-reload-myapp
sudoedit /usr/local/sbin/deploy-reload-myappСодержимое скрипта:
#!/bin/sh
set -eu
/usr/sbin/nginx -t
/usr/bin/systemctl reload nginx.service
/usr/bin/systemctl restart myapp.serviceПроверим владельца и права:
sudo chown root:root /usr/local/sbin/deploy-reload-myapp
sudo chmod 0755 /usr/local/sbin/deploy-reload-myapp
ls -l /usr/local/sbin/deploy-reload-myappТеперь правило sudoers становится короче:
Defaults:deploy env_reset
Defaults:deploy secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
deploy ALL=(root) NOPASSWD: /usr/local/sbin/deploy-reload-myappПлюс такого подхода — в sudoers нет сложной логики. Минус — нужно внимательно следить за правами на скрипт и все файлы, которые он использует. Если deploy user может изменить скрипт, unit-файл сервиса или исполняемый файл приложения, который запускается как root, ограничение теряет смысл.

Особенности systemctl и сервисов приложения
systemctl restart myapp.service безопасен только настолько, насколько безопасен сам unit. Если unit запускает приложение от пользователя www-data, nginx, apache или отдельного app, это одна история. Если сервис запускается от root и исполняемый файл лежит в каталоге, доступном deploy user на запись, это уже путь к повышению привилегий.
Для web-приложений лучше, чтобы systemd service запускался от отдельного непривилегированного пользователя. Деплой обновляет файлы приложения, а сервис при рестарте читает их уже с нужными правами. Если приложению нужен bind к порту ниже 1024, лучше решать это через reverse proxy, capability для конкретного бинарника или socket activation, а не через запуск всего приложения от root.
Не давайте deploy user право на systemctl daemon-reload, если он может менять unit-файлы. Комбинация изменения unit и daemon-reload часто эквивалентна root-доступу. То же касается systemctl edit, systemctl enable, systemctl start для произвольных сервисов и любых правил с широким wildcard по имени сервиса. Если приложение упирается в ограничения systemd по памяти или CPU, лучше настраивать их отдельно: пригодится разбор про лимиты systemd для сервисов.
Debian, Ubuntu и RHEL-based: что отличается
Синтаксис sudoers одинаковый во всех этих дистрибутивах, но отличаются группы администраторов, имена сервисов и иногда SELinux-контекст на RHEL-based системах.
В Debian и Ubuntu полные административные права обычно выдаются через группу sudo:
getent group sudoВ AlmaLinux, Rocky Linux, CentOS Stream и Oracle Linux чаще используется группа wheel:
getent group wheelДля нашего сценария добавлять deploy user в эти группы не нужно. Наоборот, если пользователь уже там, это стоит пересмотреть:
id deploy
sudo gpasswd -d deploy sudo
sudo gpasswd -d deploy wheelКоманды удаления из группы могут завершиться сообщением, что пользователь не является членом группы или что такой группы нет. Это нормально. Важно состояние после проверки id deploy: deploy user не должен быть глобальным администратором, если его задача — только деплой.
Имена PHP-FPM сервисов тоже отличаются. В Debian и Ubuntu сервис часто называется по версии, например php8.3-fpm.service. В RHEL-based системах из стандартных репозиториев чаще встречается php-fpm.service. Поэтому перед созданием правила проверьте:
systemctl list-unit-files | grep fpm
systemctl status nginx.service --no-pagerНе копируйте sudoers-правило вслепую между серверами. Лучше параметризовать имена сервисов в Ansible или хотя бы держать отдельный файл правила для каждой роли сервера.
Права на файлы проекта без sudo
Частая причина избыточного sudo — неправильно настроенные права на каталог приложения. Если deploy user вынужден делать sudo chown, sudo chmod или sudo rsync при каждом релизе, стоит сначала пересмотреть модель владения файлами.
Простой вариант: код приложения принадлежит пользователю deploy, а веб-сервер или PHP-FPM имеют только чтение там, где это возможно. Каталоги для runtime-записи — uploads, cache, sessions, storage — выделяются отдельно и имеют права для пользователя процесса приложения.
Для командной работы удобно использовать общую группу, например webapp, и setgid-бит на каталогах:
sudo groupadd --system webapp
sudo usermod -aG webapp deploy
sudo usermod -aG webapp www-data
sudo chgrp -R webapp /var/www/myapp
sudo chmod 2775 /var/www/myappНа AlmaLinux и Rocky Linux вместо www-data часто используется пользователь nginx или apache, в зависимости от стека:
sudo usermod -aG webapp nginx
sudo usermod -aG webapp apacheНе обязательно выполнять обе команды: выберите фактического пользователя вашего веб-сервера или PHP-FPM пула. Его можно посмотреть в конфигурации сервиса, PHP-FPM pool или через процессы:
ps -eo user,group,comm | grep php-fpm
ps -eo user,group,comm | grep nginxЕсли файловая модель настроена правильно, sudo остаётся только для узких системных операций. Это проще поддерживать и намного безопаснее.
Аудит: как понять, что deploy user реально может
После настройки полезно не только проверить синтаксис, но и посмотреть эффективные права пользователя:
sudo -l -U deployИз-под самого пользователя:
sudo -lЕсли в выводе внезапно видите ALL, пакетный менеджер, интерпретаторы, редакторы или shell, это повод остановиться и пересмотреть правило. Для deploy user нормальный вывод должен быть скучным: несколько конкретных команд с абсолютными путями.
Логи sudo обычно попадают в journald или системный auth log. На Debian и Ubuntu часто полезны такие команды:
sudo journalctl -t sudo
sudo grep sudo /var/log/auth.logНа AlmaLinux, Rocky Linux, CentOS Stream и Oracle Linux:
sudo journalctl -t sudo
sudo grep sudo /var/log/secureПо логам можно увидеть, кто запускал команду, от чьего имени, из какого TTY или с какой сессии. Для CI/CD дополнительно полезно логировать идентификатор pipeline на уровне самого deploy-скрипта, но не через расширение sudo до произвольных команд.
Типовые ошибки в sudoers.d
Первая ошибка — файл с неправильными правами. Файлы sudoers должны принадлежать root и обычно иметь режим 0440. Если права слишком широкие, sudo может отказаться читать файл.
sudo chown root:root /etc/sudoers.d/90-deploy
sudo chmod 0440 /etc/sudoers.d/90-deploy
sudo visudo -cВторая ошибка — правило не применяется из-за имени файла. Используйте имя без точки: 90-deploy. Если файл называется deploy.conf, переименуйте и проверьте конфигурацию.
sudo mv /etc/sudoers.d/deploy.conf /etc/sudoers.d/90-deploy
sudo visudo -cТретья ошибка — путь к команде не совпадает. В sudoers указан /bin/systemctl, а на сервере команда находится в /usr/bin/systemctl. Или разрешён systemctl restart myapp, а CI запускает systemctl restart myapp.service. Для sudoers это разные команды.
Четвёртая ошибка — разрешить команду, которая сама открывает интерактивный pager или shell. Например, systemctl status без --no-pager может быть нежелателен в интерактивной сессии. Если статус действительно нужен, лучше разрешать точную форму:
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl --no-pager status myapp.serviceПятая ошибка — пытаться решить sudoers-правилом проблемы файловых прав. Если deploy не может записать релиз в /var/www/myapp, не спешите давать ему sudo rsync. Чаще правильнее изменить владельца, группу, ACL или структуру каталогов.
Безопасный откат, если sudo сломался
Перед изменениями держите активную root-сессию или консоль сервера. Если после сохранения visudo -c показывает ошибку, не закрывайте терминал. Откройте проблемный файл снова:
visudo -f /etc/sudoers.d/90-deployЕсли нужно быстро отключить правило, можно переименовать файл так, чтобы sudo его не читал, а затем проверить конфигурацию:
mv /etc/sudoers.d/90-deploy /etc/sudoers.d/90-deploy.disabled
visudo -cПосле исправления верните файл на место:
mv /etc/sudoers.d/90-deploy.disabled /etc/sudoers.d/90-deploy
chmod 0440 /etc/sudoers.d/90-deploy
visudo -cЕсли доступа через sudo уже нет и root-сессии тоже нет, остаётся консоль управления сервером или rescue-режим. Поэтому простое правило эксплуатации: любые изменения sudoers сначала проверяются в отдельной сессии, а текущая рабочая сессия не закрывается до успешного теста.
Чеклист для production-сервера
- Deploy user создан отдельно и входит только в нужные прикладные группы.
- Парольный вход для deploy user отключён, доступ выполняется по SSH-ключу.
- Пользователь не состоит в группах
sudoиwheel, если ему не нужен полный root-доступ. - Правило хранится в
/etc/sudoers.d/90-deploy, имя файла без точки. - Файл принадлежит root и имеет права
0440. - Все изменения выполняются через
visudo -f, затем проверяются черезvisudo -c. - В sudoers указаны абсолютные пути к командам.
NOPASSWDприменяется только к конкретным безопасным командам или root-owned wrapper script.- Нет разрешений на
ALL, shell, редакторы, пакетные менеджеры и произвольные интерпретаторы. - Файловые права приложения настроены так, чтобы деплой не требовал лишнего sudo.
Хорошо настроенный sudoers.d почти незаметен в ежедневной работе: CI спокойно выкладывает релизы, администратор видит понятные логи, а deploy user не превращается в запасной root-аккаунт. Это именно тот случай, когда несколько строгих строк в sudoers экономят часы расследований и снижают риск серьёзного инцидента на сервере.


