sudo-rs on Ubuntu: what changes for sudoers
Ubuntu 26.04 makes sudo-rs the default sudo. Here is the sudoers delta that matters: wildcard argument rules stop matching, and what to write instead.
What sudo-rs changes on Ubuntu
Ubuntu 26.04 LTS ships sudo-rs as the default sudo, so the sudo command on a fresh server runs the Rust reimplementation instead of the original C program. Most sudoers files keep working exactly as they did. The rule that breaks is the one with a wildcard inside a command's arguments, because sudo-rs does not match glob patterns against argument text.
Ubuntu 25.10 made the switch first and 26.04 LTS kept it. Ubuntu 24.04 LTS is not affected, since it still selects the original sudo unless you install sudo-rs by hand. The moment this matters is the moment you upgrade from Ubuntu 24.04 to 26.04, or the moment you build a new box on the newer release. If you also run the interim releases, how LTS and interim Ubuntu releases differ on a server explains which machine meets a change like this first.
Check which sudo your server is actually running
Do not work this out from the release number. Ask the machine.
sudo --version
update-alternatives --config sudo
dpkg -l 'sudo*'Trust sudo --version on your own box over any version table on the internet, including this page. update-alternatives --config sudo is the other half of the answer: it lists every installed provider of /usr/bin/sudo and marks the selected one. A package being installed is not the same as it being selected, so read the selection, not the package list.
Both implementations are packaged during the transition. The Rust one is sudo-rs, at version 0.2.13 in 26.04 as of August 2026. The original, maintained by Todd C. Miller, is still the sudo package; what changed is that its programs carry a .ws suffix so both can be installed at once: /usr/bin/sudo.ws and /usr/bin/visudo.ws, alongside cvtsudoers.ws and sudoreplay.ws. Verified against the 26.04 archive in September 2026: dpkg -L sudo lists the suffixed binaries, and sudo-rs ships /usr/bin/sudo-rs beside them.
Why Ubuntu switched to sudo-rs
sudo is setuid root. Any user on the box can start it, and it starts with full privileges, so a memory bug inside it is a local root exploit. CVE-2021-3156 was exactly that: a heap buffer overflow reachable by any local user, and it sat in released code for about ten years. Rust catches that class of bug at compile time, which is the whole argument for the rewrite.
The second reason is scope, and it is the one that reaches your config. The original sudo has collected a large feature set over three decades, and every feature is more code running as root. sudo-rs implements a subset on purpose. Anything its authors judged niche or actively harmful was left out, so a sudoers construct that worked for years can be simply absent. Your wildcard rule is one of those.
Memory safety removes one class of bug. It does not make a program bug free, and sudo-rs has shipped security fixes of its own since it became the default. Patch it like anything else.
Which sudoers rules still work
The file is the same file. sudo-rs reads /etc/sudoers and the drop-in files in /etc/sudoers.d/, and the ordinary things a server operator writes are supported:
deploy ALL=(ALL:ALL) ALL, and group forms such as%sudo ALL=(ALL:ALL) ALL- the
NOPASSWD:andPASSWD:tags User_Alias,Runas_Alias,Host_AliasandCmnd_Alias- a command with an exact argument list, for example
/usr/bin/systemctl restart app-api - a command followed by
"", which permits the command only with no arguments at all - a command followed by
*as its final argument, which permits any trailing arguments - a directory path ending in
/, which permits any command in that directory !to subtract a command from a list- a useful subset of
Defaults, includingsecure_path,env_keep,env_check,timestamp_timeout,passwd_tries,editor,umask,targetpw,rootpwanduse_pty
Two defaults behave differently and catch people out. env_reset cannot be turned off in sudo-rs: it is always on. use_pty is on by default, so the command runs in its own pseudo-terminal.
Why your wildcard sudoers rule stopped matching
Wildcards are still allowed in one place: the command's file name. A rule of %ops ALL = /sbin/fsck* still permits sudo fsck and sudo fsck_exfat, because the * is part of the path being matched against the filesystem.
Inside the argument list, sudo-rs accepts only two special forms, and neither is a pattern. "" means no arguments. A final * means any trailing arguments. Every other argument is compared as literal text. So %ops ALL = /sbin/service ntp * is fine, because ntp is literal and the * is last. A rule like this one, however, grants nothing you intended:
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart app-*app-* is a pattern in the middle of an argument. sudo-rs does not expand it, so the rule does not cover systemctl restart app-api and sudo refuses the command. Two commands tell you the truth about any rule on your own server: sudo -l -U deploy, run as root, prints what that account may actually run, and sudo visudo -c tells you whether the file parses at all. Run them before you start editing at random.
The wildcard rule was always a hole
Under the original sudo, the arguments you type are joined into one string and matched against the rule's argument string with a glob. A glob matches whitespace. That is the part almost everybody misses.
The sudo-rs documentation gives the clearest demonstration. A rule of /bin/rm *.txt also permits sudo rm -rf /home .txt, because the one * swallows -rf /home and the joined string still ends in .txt. The rule reads as "only text files". It means "any arguments at all, as long as the line ends in .txt".
The same applies to the systemctl example. Because the arguments are compared as one joined string, a trailing pattern also matches whatever you append after it, so restart app-* covers restart app-api plus any further arguments the caller adds. A pattern inside an argument gives away the arguments around it, and arguments are where a command's power lives. sudo-rs refuses the construct rather than trying to make it safe, because there is no safe general form of it.
Replace the wildcard with an explicit command list
Most wildcard rules exist because somebody did not want to type four lines. Type the four lines.
Cmnd_Alias APP_RESTART = /usr/bin/systemctl restart app-api, /usr/bin/systemctl restart app-worker
Cmnd_Alias APP_STATUS = /usr/bin/systemctl status app-api, /usr/bin/systemctl status app-worker
deploy ALL=(root) NOPASSWD: APP_RESTART, APP_STATUSGet the path right. A rule naming /bin/systemctl on a system where the binary is /usr/bin/systemctl never matches, and the failure looks identical to a permissions problem. Confirm with command -v systemctl and paste what it prints.
Put the rule in its own drop-in file rather than in /etc/sudoers, so a package upgrade never argues with your edit:
sudo visudo -f /etc/sudoers.d/90-deploy
sudo visudo -c
sudo -l -U deployName the file without a dot and without a trailing tilde. The original sudo ignores files in sudoers.d whose names contain a dot, so 90-deploy.conf is a classic silent no-op, and keeping the convention costs nothing.
Use a root-owned wrapper when the list gets long
When the allowed set is too large to list, move the decision out of sudoers and into a small program that root owns.
sudo tee /usr/local/sbin/app-restart >/dev/null <<'EOF'
#!/bin/sh
set -eu
case "${1:-}" in
app-api|app-worker) ;;
*) echo "app-restart: not allowed: ${1:-}" >&2; exit 1 ;;
esac
exec /usr/bin/systemctl restart "$1"
EOF
sudo chown root:root /usr/local/sbin/app-restart
sudo chmod 0755 /usr/local/sbin/app-restart
ls -l /usr/local/sbin/app-restartThe sudoers side then names one command:
deploy ALL=(root) NOPASSWD: /usr/local/sbin/app-restart *The trailing * is acceptable here because the script, not sudo, decides what is allowed. That only holds while the script is owned by root and writable by nobody else. If deploy can write to the file, deploy can replace its contents and run anything as root, which is worse than the wildcard rule you removed. Check the mode with ls -l, and if the output is not obvious to you, reading the drwxr-xr-x permission string takes five minutes to learn. The same rule covers the directory: /usr/local/sbin must not be writable by the account either, because a writable directory means the file can be replaced wholesale.
Give the job its own account instead of a sudo rule
The better question is often why the command needs root at all. A service that runs as its own user can be managed by that user, and no sudoers line is involved. For system units, systemd already delegates that decision to polkit, so a rule can name one unit and one operator:
polkit.addRule(function(action, subject) {
if (action.id == "org.freedesktop.systemd1.manage-units" &&
action.lookup("unit") == "app-api.service" &&
subject.user == "deploy") {
return polkit.Result.YES;
}
});Save that as /etc/polkit-1/rules.d/50-app-api.rules and deploy can run systemctl restart app-api with no sudo at all. Test it from the exact context that will use it, because a rule that works in your SSH session is worth confirming from cron before you rely on it. Either way, the account doing the work should exist for that work alone, which is the same argument behind least-privilege user accounts on a VPS.
What else sudo-rs leaves out
sudo -E is not implemented. Name the variables you need with Defaults env_keep += "HTTP_PROXY HTTPS_PROXY NO_PROXY" instead, and remember that env_reset is always on, so anything not kept is cleared.
Central sudoers storage in LDAP is gone. sudoers.ldap and cvtsudoers are not implemented, and the sudo-ldap package was removed in 26.04. LDAP authentication through PAM or SSSD still works. It is the policy-in-a-directory part that is out of scope.
INTERCEPT, which tried to stop shell escapes from a permitted command, is not implemented. It never held against a determined user anyway. If a rule lets someone run an editor or an interpreter as root, they have root, and no sudo option changes that.
Session recording is not implemented, so there is no I/O log and no sudoreplay. Logging goes to syslog only, and there is no logfile option to redirect it somewhere else, so sudo messages land wherever your system already sends syslog.
Should you switch back to sudo.ws?
You can, and during the 26.04 cycle the original stays packaged for exactly this reason.
sudo apt install sudo
update-alternatives --config sudo
sudo update-alternatives --set sudo /usr/bin/sudo.wsCopy the exact paths from the --config output rather than from this page, since that is the list your own system will accept. Going back to sudo-rs later means setting the alternative to the sudo-rs binary path from the same list.
Keep a second SSH session open, logged in and idle, before you touch anything that affects sudo. A sudoers file that fails to parse, or an alternative pointing at a binary that is not installed, can leave you with no way to become root on a remote box. That habit belongs with everything else you do in the first ten minutes on a new VPS.
Treat the switch back as a deadline rather than a fix. It buys you a week to rewrite the rules properly, and the rewrite is worth doing on its own merits, because every wildcard rule you delete was granting more than its author read into it.
FAQ
Why did my sudoers wildcard rule stop working on Ubuntu 26.04?
Because Ubuntu 26.04 LTS selects sudo-rs as the default sudo, and sudo-rs does not match wildcard patterns inside a command's arguments. It allows a wildcard in the command's file name, "" to mean no arguments, and a single * as the final argument. A rule such as /usr/bin/systemctl restart app-* puts a pattern in the middle of an argument, so it grants nothing and the command is refused. Run sudo -l -U deploy as root to see what the account really has, then replace the rule with the exact commands or with a root-owned wrapper script.
How do I switch back to the original sudo on Ubuntu 26.04?
The original ships in the sudo package, whose binaries carry the .ws suffix. Install it with sudo apt install sudo, then point the alternative at it with sudo update-alternatives --set sudo /usr/bin/sudo.ws. Run update-alternatives --config sudo first to read the exact paths your system offers, and keep a second SSH session open while you change it. This does not bring back sudo-ldap, which was removed from 26.04 regardless of which implementation you select.
Does sudo-rs read the same /etc/sudoers file?
Yes. sudo-rs reads /etc/sudoers and the drop-in files under /etc/sudoers.d/, with the same syntax for users, groups, aliases, run-as specifications and the NOPASSWD tag. It implements a subset of the sudoers language, so the differences show up as constructs that are missing rather than constructs that behave differently. Edit with sudo visudo, then verify with sudo visudo -c before you close your session.
What replaces sudo -E in sudo-rs?
sudo -E is not implemented, and it was already discouraged in the original sudo, because handing a root process an environment the caller controls is a well-known way to change how that process behaves. Name the variables you actually need in sudoers instead, with a line such as Defaults env_keep += "HTTP_PROXY HTTPS_PROXY NO_PROXY". env_reset is always on in sudo-rs and cannot be disabled, so every variable you do not keep is cleared.