Fix: sudo: command not found on Linux
Part of: Docker, DevOps & Infrastructure
Quick Answer
How to fix 'sudo: command not found' on Linux — caused by sudo not installed, missing PATH in scripts, Docker containers without sudo, and su vs sudo confusion on minimal systems.
The Binary You Took for Granted Is Not Here
Personally, I think sudo: command not found is one of the most misread Linux errors. People reach for /etc/sudoers immediately when the actual problem is that no sudo binary exists on the box at all. Most fresh container images and many minimal server installs simply do not ship it. You run a command with sudo on a Linux system and get:
bash: sudo: command not foundOr:
sudo: command not foundOr in a shell script:
./setup.sh: line 5: sudo: command not foundThe system cannot find the sudo binary. This is different from a permissions error like Permission denied or sudo: user is not in the sudoers file; those mean sudo was found but access was denied.
Quick Reference Before You Dive In
If you arrived here from Google with a fresh failure, the five facts that resolve roughly 90 percent of cases:
sudo: command not foundmeans the BINARY is missing or unreachable. It NEVER means you lack permission. A permission failure is a different error message. Thesudomanual page and thesudoersconfiguration reference are the canonical sources.- Inside Docker containers, you almost never need
sudo.RUNsteps execute as root by default. The fix is to drop thesudoprefix, not to install the package. - On RHEL / CentOS / Rocky / Fedora the privileged group is
wheel, notsudo. Adding a user to a group calledsudoon these systems is a no-op; you must add towheel. - Alpine, distroless, and busybox-based images intentionally omit
sudo. For Alpine,apk add sudoworks. For distroless, the answer is to restructure the build to run privileged steps in an earlier stage. - In cron jobs and non-login SSH commands,
$PATHmay not include/usr/bin. Use the full path/usr/bin/sudoor setPATH=explicitly at the top of the script.
The rest of this article walks through each cause in detail, plus the failure modes most other guides skip.
How the Shell Decides sudo Is Not There
sudo is not installed on every Linux system by default. It is a separate package that distributions can include, exclude, or replace. On a desktop install of Ubuntu or Fedora you almost always have it. On a server install, a container base image, or a cloud-provider golden image, you often do not. The shell’s command not found message is the kernel-agnostic way of saying that no binary named sudo exists in any directory listed in your current $PATH.
Three categories of root cause cover almost every report. First, the binary is genuinely missing; the package was never installed (typical for Alpine, distroless, busybox, Debian slim, and many CI base images). Second, the binary is installed but $PATH does not include /usr/bin; common inside cron, inside non-login SSH commands, and inside su (without the dash) where the parent shell’s environment is inherited unchanged. Third, you are running on a distribution that ships doas, pkexec, or run0 instead of sudo, and your scripts assume sudo is the only choice.
It is also worth distinguishing this from related failures. Permission denied and user is not in the sudoers file both mean sudo was found and executed; it just refused you. bash: sudo: command not found is strictly a PATH / installation issue and never a permissions one. If you fix the wrong layer (for example, editing /etc/sudoers when the binary is simply not installed) nothing will change.
Common situations where sudo is missing or unreachable:
- Minimal Docker containers (Alpine, Debian slim, Ubuntu minimal, distroless, busybox) do not include
sudo; it is not needed since the container runs as root. - Freshly provisioned Debian/Ubuntu servers from some cloud providers or VPS hosts ship without
sudo. - Alpine Linux uses
doasinstead ofsudoin many configurations. - Shell scripts run with
su: when you switch to root withsu, the PATH may not include/usr/binwheresudolives. - Non-interactive SSH sessions: the PATH set up by
.bashrcor.bash_profilemay not apply. - Docker
RUNcommands: eachRUNis executed as a new shell with a minimal environment.
Platform and Environment Differences
The same sudo: command not found message has very different root causes depending on where you are running. Knowing the platform shortcuts the fix.
Debian and Ubuntu ship sudo as a separate package in the main archive. Server ISOs and the official Docker ubuntu:* images install it, but debian:*-slim and ubuntu:*-minimal images do not. The package is just sudo; an sudo-ldap package also exists for sites that authenticate sudoers through LDAP, and the two conflict; installing one removes the other.
RHEL, CentOS Stream, Rocky, AlmaLinux, and Fedora ship sudo by default on most installs, but the privileged group is wheel, not sudo. Adding a user to a group called sudo on these distros silently does nothing because /etc/sudoers only references %wheel. The same applies to most container images derived from ubi, rockylinux, or fedora.
Alpine Linux and other musl-based distros historically pushed doas (from OpenBSD) over sudo. sudo is available via apk add sudo, but the package adds significant size; for one-off privilege escalation doas is usually preferred. Alpine also enforces stricter group rules; adding to wheel is required, plain sudo group will not work without editing /etc/sudoers.
Distroless and scratch images (gcr.io/distroless/*, scratch) intentionally have no shell at all, let alone sudo. The fix is never to add sudo; it is to redesign the build so privileged work happens in an earlier stage that does have a shell, and the final image only carries the application binary. Trying to install sudo into distroless defeats its security posture.
WSL2 runs a normal Linux user-mode environment, so sudo installs the same way as on bare-metal Ubuntu or Debian. The catch is that WSL distributions imported via wsl --import from a rootfs tarball sometimes drop sudo and you have to reinstall it. Also, the default user in WSL is root when launched via wsl -d; sudo is unnecessary there.
Docker containers running as root (the default) never need sudo. The “fix” inside a RUN step is almost always to remove the sudo prefix, not to install the package. Only add sudo when the runtime user is non-root and your application code calls sudo itself.
CI runners vary widely. GitHub Actions Ubuntu runners run as the runner user with passwordless sudo already configured. GitLab CI shared runners using docker:* images run as root and have no sudo. Self-hosted runners reflect whatever image their administrator built. Always probe with whoami && which sudo before assuming.
Linux capabilities can replace sudo entirely for narrow use cases. If your script only needs CAP_NET_BIND_SERVICE (to bind port 80 as a non-root process) or CAP_NET_RAW (for ping), use setcap instead of installing sudo. This is the right approach in unprivileged containers where adding sudo is impossible.
When to Use Which Fix
The next sections cover the fixes in detail. The table below maps your situation to the recommended fix.
| Your situation | Recommended fix | Why |
|---|---|---|
Fresh server install, sudo missing | Fix 1: install via apt / dnf / apk | Package is just not installed |
Inside Dockerfile RUN, want sudo | Fix 2: drop the prefix (or install if running as non-root) | RUN is root by default |
| Shell script run from cron | Fix 3: use /usr/bin/sudo or set PATH= | cron PATH is minimal |
No sudo and cannot install | Fix 4: su - or su -c "command" root | Older universal alternative |
| Non-interactive SSH command fails | Fix 5: full path or bash -l -c | SSH non-interactive PATH is minimal |
CI runner does not have sudo | Fix 6: install in before_script or workflow step | Runner image may be minimal |
| Installed but want to verify config | Fix 7: which sudo, groups, visudo -c | Confirm before debugging |
If multiple rows apply, pick the topmost one.
Fix 1: Install sudo
If sudo is simply not installed, install it as root:
Debian / Ubuntu:
# Switch to root first
su -
# Install sudo
apt-get update && apt-get install -y sudo
# Add your user to the sudo group
usermod -aG sudo your-username
# Exit root shell
exit
# Apply the group change (log out and back in, or use newgrp)
newgrp sudoCentOS / RHEL / Fedora:
su -
dnf install -y sudo # RHEL 8+ / Fedora
# or
yum install -y sudo # RHEL 7
usermod -aG wheel your-username
exitOn RHEL/CentOS, the wheel group has sudo access by default (not the sudo group).
Alpine Linux:
# As root
apk add --no-cache sudo
# Add user to wheel group
adduser your-username wheel
# Allow wheel group to use sudo without password (optional)
echo "%wheel ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoersAfter installing and adding your user to the appropriate group, log out and log back in for the group change to take effect. Group membership is read at login; newgrp sudo applies it to the current session without re-logging.
Fix 2: Fix sudo in Docker Containers
Docker containers often run as root, so sudo is unnecessary and not installed. But if your Dockerfile or scripts use sudo, add it:
Dockerfile (Debian / Ubuntu base):
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
sudo \
&& rm -rf /var/lib/apt/lists/*
# Create a non-root user with sudo access
RUN useradd -m -s /bin/bash appuser \
&& echo "appuser ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
USER appuserBetter approach: run as root during build, switch to non-root for runtime:
FROM node:20-slim
# Run build steps as root (no sudo needed)
RUN apt-get update && apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
# Switch to non-root user for the application
RUN useradd -m -u 1001 appuser
USER appuser
WORKDIR /app
COPY --chown=appuser:appuser . .
RUN npm install
CMD ["node", "server.js"]A reflex I have built when reviewing Dockerfiles: any sudo inside a RUN step is suspicious. RUN already executes as root before any USER directive switches user. Adding sudo to a RUN step either does nothing (root running sudo is a no-op) or breaks the build (because sudo is not installed). Add sudo only when your APPLICATION (running as the non-root USER) genuinely needs it at runtime.
Fix 3: Fix sudo in Shell Scripts
When a script is run via su, cron, or a non-login shell, the PATH may not include /usr/bin:
Check where sudo is installed:
which sudo
# /usr/bin/sudo (most systems)
ls -la /usr/bin/sudoUse the full path in scripts:
#!/bin/bash
/usr/bin/sudo apt-get update
/usr/bin/sudo systemctl restart nginxOr fix the PATH at the top of the script:
#!/bin/bash
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
sudo apt-get updateFor cron jobs, always use full paths or set PATH explicitly because cron runs with a minimal environment:
# In crontab (crontab -e):
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 2 * * * /usr/bin/sudo /usr/bin/apt-get update -yFix 4: Use su Instead of sudo on Systems Without sudo
On minimal systems or older distributions where sudo is not installed and you cannot install it, use su to switch to root:
# Switch to root
su -
# Run your commands as root
apt-get update
systemctl restart nginx
# Return to your user
exitThe su - (with the dash) starts a login shell as root, which sets up the correct PATH and environment. su without the dash inherits your current environment, which may have PATH issues.
Run a single command as root with su -c:
su -c "apt-get update && apt-get install -y curl" rootThis is equivalent to sudo apt-get update && sudo apt-get install -y curl.
Fix 5: Fix sudo Not Found in SSH Commands
When running commands over SSH non-interactively, the PATH is often minimal:
Broken: PATH does not include /usr/bin:
ssh user@server "sudo systemctl restart nginx"
# bash: sudo: command not foundFixed: use the full path:
ssh user@server "/usr/bin/sudo /bin/systemctl restart nginx"Fixed: force a login shell:
ssh user@server "bash -l -c 'sudo systemctl restart nginx'"The -l flag starts a login shell which sources .bash_profile and sets up the full PATH.
Fixed: use sudo -i or sudo -s in the remote command:
If the user has passwordless sudo configured, you can also use:
ssh user@server "sudo -n systemctl restart nginx"The -n flag makes sudo non-interactive; it fails immediately if a password would be required instead of hanging.
Fix 6: Fix sudo in CI/CD Pipelines
In GitHub Actions, GitLab CI, and similar platforms, runners often run as root or a restricted user:
GitHub Actions: check the runner user:
- name: Check user
run: whoami && idMost GitHub Actions runners run as a non-root user with sudo available and passwordless. If sudo is not found, install it in your workflow:
- name: Install sudo
run: |
if ! command -v sudo &> /dev/null; then
apt-get update && apt-get install -y sudo
fiFor Docker-based CI runners, add sudo to your base image as shown in Fix 2.
GitLab CI: use before_script to install dependencies:
before_script:
- apt-get update -qq && apt-get install -y -qq sudoFix 7: Verify sudo Is Configured Correctly After Install
After installing sudo, verify it works:
# Check that sudo binary exists
which sudo
# Check your group membership
groups
id
# Test sudo with a safe command
sudo echo "sudo works"
# If prompted for password and you want passwordless sudo:
sudo visudoIn visudo, add this line at the end to allow your user passwordless sudo:
your-username ALL=(ALL) NOPASSWD: ALLOr for the entire sudo group:
%sudo ALL=(ALL) NOPASSWD: ALLWarning: Passwordless sudo is convenient but reduces security. Use it only for automated scripts, CI environments, or development machines; not production servers with sensitive data.
Stranger Causes I Have Tracked Down
Check if sudo is installed but not in PATH:
find / -name "sudo" -type f 2>/dev/nullIf it finds /usr/bin/sudo but which sudo returns nothing, your PATH is missing /usr/bin. Add it:
export PATH="$PATH:/usr/bin"Add this to ~/.bashrc or ~/.bash_profile to make it permanent.
Check the sudoers file for syntax errors:
sudo visudo -c # Check syntax without opening the editorA syntax error in /etc/sudoers can break sudo for all users. If you cannot use sudo at all, boot into single-user mode or use pkexec visudo to fix the sudoers file.
WSL (Windows Subsystem for Linux): If you are on WSL and sudo is not found, your WSL distribution may be in a broken state. Try resetting it or reinstalling the distribution from the Microsoft Store. WSL distributions imported from a tar rootfs sometimes ship without sudo; install it via the appropriate package manager once you have switched to root with wsl -d <distro> -u root.
Check whether the distro replaced sudo with doas or run0. On modern Alpine and on some hardened systems, doas is the default. Try which doas and which run0. If one exists, rewrite the script to use it, or apk add sudo / dnf install sudo if you need cross-distro consistency. systemd 256+ introduced run0 as a sudo replacement on bleeding-edge Fedora and Arch.
Check for missing PAM modules. On some hardened images, sudo is installed but fails at startup because /etc/pam.d/sudo references a module that is not present (commonly pam_systemd.so in non-systemd containers). The error then masquerades as sudo: command not found when invoked via a wrapper that swallows stderr. Run sudo -V and look for missing-module warnings, or check journalctl -u sudo if systemd is available.
Check pam_wheel and group membership requirements. Some distributions (notably Arch, Gentoo, and hardened RHEL derivatives) enable auth required pam_wheel.so use_uid in /etc/pam.d/su. If a script falls back to su instead of sudo, that script silently fails unless the user is in wheel. Add the user with usermod -aG wheel <user> and re-login.
Check the busybox shim. On busybox-based images (Alpine, OpenWrt), sudo may be a thin applet that does almost nothing. Run ls -la $(which sudo) and if it points to /bin/busybox, install the real sudo package; the busybox applet does not honor /etc/sudoers.
What Other Tutorials Get Wrong About This Error
Most Linux tutorials list the same fixes but frame them in ways that produce subtle bugs.
They conflate command not found with permission errors. command not found means the binary is missing or unreachable. Permission denied means the binary exists but cannot be executed by the current user. user is not in the sudoers file means sudo ran and refused. The fixes for each are completely different.
They show sudo apt install sudo as advice. If sudo is not installed, you cannot use sudo to install it. The fix is to become root first (su -) and then run apt install sudo.
They miss the wheel vs sudo group split. On Debian/Ubuntu, the privileged group is sudo. On RHEL/CentOS/Fedora/Rocky, it is wheel. Articles that hardcode usermod -aG sudo $USER on RHEL produce a no-op; the user is added to a group /etc/sudoers does not reference.
They recommend adding sudo to Dockerfile RUN steps. RUN runs as root before any USER switch. Adding sudo either does nothing or breaks the build. The correct pattern is to drop sudo from RUN and reserve it for runtime where the application runs as a non-root user.
They miss the doas / run0 alternatives. Modern Alpine prefers doas. systemd 256+ introduced run0. Articles that assume sudo is the only privilege-escalation tool send readers down dead ends on distros that ship the alternatives.
They show chmod 4755 /usr/bin/sudo style fixes. If sudo exists but the setuid bit is missing, the binary still runs but cannot escalate. The right fix is to reinstall the package (which restores the correct mode). Manual chmod fixes can mask deeper package corruption.
Frequently Asked Questions
Why does sudo apt install sudo not work?
Because the system has no sudo, you cannot use sudo to install it. Switch to root first with su - (you need the root password) or boot into single-user / recovery mode. Then apt-get install sudo runs directly as root.
Why does sudo exist on my Ubuntu desktop but not on the server?
Server installers offer a sudo-installed flag at install time. Some sysadmins prefer to manage privileges via su and root directly and skip the option. Cloud-provider images vary; the official Ubuntu cloud image always includes sudo, but third-party respins and minimal base images may not.
What is the difference between sudo, doas, and run0?
sudo is the long-standing privilege-escalation tool with /etc/sudoers configuration. doas (OpenBSD origin, common on Alpine) is a simpler tool with a shorter config file (/etc/doas.conf). run0 (systemd 256+) is a newer tool that uses systemd’s polkit infrastructure and offers per-command auditing. Functionally similar; sudo remains the most universal choice.
Is chmod u+s /usr/bin/sudo a fix for missing setuid?
In theory yes, in practice reinstall the package. Manual setuid changes can mask underlying issues (missing dependencies, corrupted binary). apt-get install --reinstall sudo (Debian/Ubuntu) or dnf reinstall sudo (RHEL family) restores the binary to a known-good state with correct permissions.
Why does sudo work for one user but not another on the same machine?
Either the second user is not in the privileged group (sudo on Debian / Ubuntu, wheel on RHEL family) or they are not listed individually in /etc/sudoers. Check with groups and sudo -l as that user. The fix is usermod -aG sudo $USERNAME (Debian / Ubuntu) or usermod -aG wheel $USERNAME (RHEL family) followed by a re-login.
Should I use passwordless sudo on a production server?
Generally no. Passwordless sudo is convenient for automation (CI, scripts, ansible) but reduces security: any compromise of a sudo-able account immediately becomes root. Use it for limited scopes (e.g., specific commands in /etc/sudoers) rather than NOPASSWD: ALL. For production interactive logins, keep password prompts.
For permission errors after sudo is working (e.g., sudo: user is not in the sudoers file), the fix is adding the user to the sudo / wheel group as shown in Fix 1. For script permission errors, see Fix: bash: Permission Denied. For Docker socket access without sudo, see Fix: Docker permission denied while trying to connect to the Docker daemon socket. For SSH command execution that loses your interactive PATH, see Fix: SSH Connection Timed Out. For GitHub Actions where sudo suddenly stops being available, see Fix: GitHub Actions Permission Denied.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: EMFILE Too Many Open Files / ulimit Error on Linux
How to fix EMFILE too many open files errors on Linux and Node.js — caused by low ulimit file descriptor limits, file handle leaks, and how to increase limits permanently.
Fix: Cron Job Not Running on Linux
How to fix cron jobs not running on Linux — caused by PATH issues, missing newlines, permission errors, environment variables not set, and cron daemon not running.
Fix: bash: command not found
How to fix bash command not found error caused by missing PATH, uninstalled packages, wrong shell, typos, missing aliases, and broken symlinks on Linux and macOS.
Fix: Valkey Not Working — Redis Client Compatibility, ACL, Cluster Mode, and Migration
How to fix Valkey errors — client connection refused, RESP protocol compatibility, ACL user setup, cluster slot reshard, persistence config (RDB/AOF), TLS, Sentinel mode, and migrating from Redis.