NOPASSWD drops the password prompt, which a deploy pipeline needs. deploy ALL=(ALL) NOPASSWD: ALL is an unauthenticated root shell for whoever steals the deploy key, and even a narrow grant leaks root if the command can run something else. Never grant an editor, pager or interpreter (vi, less, find, python, systemctl edit; see GTFOBins 291,790 (https://gtfobins.github.io/ 126 )), spell arguments out (systemctl restart * allows any service), and never grant a command that writes a file you name (tee, cp, dd).
# Install as /etc/sudoers.d/deploy after: sudo visudo -cf deploy.sudoers
Cmnd_Alias DEPLOY_CMDS = /usr/bin/systemctl reload php8.5-fpm, \
/usr/bin/systemctl restart queue
deploy ALL=(root) NOPASSWD: DEPLOY_CMDSsudo visudo -cf deploy.sudoers && \
sudo install -o root -g root -m 0440 deploy.sudoers /etc/sudoers.d/deploy
sudo -l -U deploy # the account's grants
sudo -u deploy sudo -n -l /usr/bin/systemctl reload php8.5-fpm # allowed? prints it
sudo -u deploy sudo -n /usr/bin/systemctl restart apache2 # not in the list
sudo journalctl -t sudo -o cat --since "1 min ago" | grep 'USER=deploy' | tail -3deploy.sudoers: parsed OK
User deploy may run the following commands on PHANG:
(root) NOPASSWD: /usr/bin/systemctl reload php8.5-fpm, /usr/bin/systemctl restart queue
/usr/bin/systemctl reload php8.5-fpm
sudo: I'm sorry deploy. I'm afraid I can't do that
dev : PWD=/tmp/demo ; USER=deploy ; COMMAND=/usr/bin/sudo -n -l /usr/bin/systemctl reload
php8.5-fpm
dev : PWD=/tmp/demo ; USER=deploy ; COMMAND=/usr/bin/sudo -n /usr/bin/systemctl restart
apache2sudo reads /etc/sudoers.d in lexical order and skips any file whose name contains a dot or ends in ~: a grant saved as deploy.conf is silently ignored, so the scratch file loses its extension on install. Keep drop-ins root-owned, mode 0440 and single-purpose, so that deleting one is a complete revocation.
Log lines name the invoking user, directory, target user and command, in the journal and /var/log/auth.log (/var/log/secure on Red Hat). Note what is missing: sudo-rs 0.2.13 refused the apache2 restart but logged only the outer, permitted sudo -u deploy, so do not rely on it to report refusals. Session recording (log_output, sudoreplay) and sudo_logsrvd exist only in the original sudo.
Privilege does not only mean root: ask web permission questions as the web server's account.
sudo -u www-data id
sudo -u www-data test -w storage/logs && echo writable || echo "not writable"
sudo chgrp www-data storage/logs && sudo chmod 2775 storage/logs
sudo -u www-data test -w storage/logs && echo writable || echo "not writable"uid=33(www-data) gid=33(www-data) groups=33(www-data) not writable writable
If that says yes and PHP still fails, suspect open_basedir or AppArmor 454,622 (PHP). su - and sudo -i both open root's full login shell; su and sudo -s keep your directory. su wants root's password, sudo yours, so prefer sudo -i: root stays locked and the log names who opened the shell. Better still, run one logged sudo -u user command instead of any shell.