Linux VPS

Give a user sudo access to specific commands only

Grant a Linux user root rights for a short list of commands, such as restarting one service, using Cmnd_Alias and full paths, and avoid the wildcard and shell-escape traps.

AdvancedLinux server5 min read

Not every helper needs full root. A developer who only restarts their app, or a support account that reads logs, can get exactly those commands through sudo and nothing more. This guide shows how to write tight sudoers rules, which mistakes quietly hand out full root anyway, and how to test the result.

Before you start

  • A Linux VPS with Ubuntu 22.04 or 24.04, Debian 12, or AlmaLinux or Rocky Linux 8 or 9.
  • Root or full sudo access, and a second root shell open while you edit.
  • The account you want to limit, for example deploy. It must not also be in the sudo or wheel group, or it keeps full rights.
  • Familiarity with visudo. If you are new to it, read Edit the sudoers file safely with visudo first.

Warning: A syntax error in sudoers can break sudo for every user. Always edit with visudo, and keep a root session open until a fresh login proves the rule works.

Remove full admin rights first

Check the user's groups:

Bash
id deploy

If you see sudo (Ubuntu, Debian) or wheel (AlmaLinux, Rocky), remove it:

Bash
sudo gpasswd -d deploy sudo

Use wheel instead of sudo on AlmaLinux and Rocky. The change applies at the user's next login.

Find the full path of each command

sudoers rules must use absolute paths. Look them up on your own server, because they can differ between distributions:

Bash
command -v systemctl journalctl
/usr/bin/systemctl
/usr/bin/journalctl

Write the rule

Create a drop-in file:

Bash
sudo visudo -f /etc/sudoers.d/deploy

Allow restarting, reloading and checking one service, and reading its logs:

Cmnd_Alias MYAPP_CTL = /usr/bin/systemctl restart myapp.service, \
                       /usr/bin/systemctl reload myapp.service, \
                       /usr/bin/systemctl status myapp.service, \
                       /usr/bin/journalctl -u myapp.service *

deploy ALL=(root) MYAPP_CTL

How the matching works:

  • A command with no arguments listed, such as /usr/bin/systemctl, allows that program with any arguments. That would include systemctl stop sshd. Always list the arguments.
  • A command with arguments listed must be typed exactly that way. sudo systemctl restart myapp does not match myapp.service above. Pick one form and tell your users.
  • Adding "" as the only argument, like /usr/bin/df "", allows the command with no arguments at all.

Add NOPASSWD: before the alias only if automation needs it; see Allow sudo without a password (NOPASSWD).

Avoid the common traps

Wildcards match more than you think

A * in sudoers matches any characters, including spaces. So this rule:

deploy ALL=(root) /usr/bin/systemctl restart *

lets the user restart any service, including SSH and the database. Path wildcards are just as loose. A rule for /usr/bin/cat /var/log/* also matches cat /var/log/../../etc/shadow, because * does not stop at ... In the journalctl example above the wildcard only adds options to a read-only command for one unit, which is an accepted trade-off. Never put a wildcard after a command that changes the system or reads arbitrary files.

Programs that can spawn a shell

Many ordinary tools can run other commands or edit files. Giving any of these via sudo is the same as giving full root:

  • Editors and pagers: vim, nano, less, more, man.
  • File tools with exec options: find, tar, rsync, zip, awk, sed.
  • Interpreters: bash, sh, python3, perl, php.
  • cp, mv, tee, chmod or chown on arbitrary paths, which can overwrite /etc/sudoers or /etc/shadow.

Scripts the user can edit

If you allow /home/deploy/restart.sh, the user can change the script and run anything. Put scripts in a root-owned path such as /usr/local/sbin/, owned by root:root and not writable by others.

Let a user edit one file safely

To let someone change a single config file, use sudoedit instead of granting an editor:

deploy ALL=(root) sudoedit /etc/nginx/sites-available/example.com

The user runs sudoedit /etc/nginx/sites-available/example.com. Their own editor opens a temporary copy as their own user, and sudo writes it back. No root shell is ever exposed.

Test the rule

Check the syntax of all sudoers files:

Bash
sudo visudo -c

Then, as root, list what the user may run without logging in as them:

Bash
sudo -l -U deploy
User deploy may run the following commands on web01:
    (root) /usr/bin/systemctl restart myapp.service, /usr/bin/systemctl reload myapp.service,
        /usr/bin/systemctl status myapp.service, /usr/bin/journalctl -u myapp.service *

Check it works

Log in as deploy in a new session and run one allowed and one blocked command:

Bash
sudo systemctl restart myapp.service
sudo systemctl stop sshd

The first runs after the password prompt. The second is refused with a message like Sorry, user deploy is not allowed to execute '/usr/bin/systemctl stop sshd' as root. Refusals are logged to the system journal, which you can read with journalctl -t sudo.

Troubleshooting

  • Allowed command is refused: the user typed it differently from the rule, for example without .service. Match the rule exactly or add both forms.
  • User can still run everything: they are still in sudo or wheel, or another file grants ALL. Check id deploy and sudo -l -U deploy.
  • "command not allowed" for a command with a relative path: sudoers matches the resolved full path. Make sure command -v on the server matches what you wrote.
  • visudo -c reports an error in your alias: alias names must be uppercase, and a line continuation needs \ as the very last character.

Next steps

  • Lock down remote access too: Limit SSH logins with AllowUsers and AllowGroups.
  • Record what admins run: Log root commands with auditd.
Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.