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 Error
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.
Why This Happens
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.
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"]Pro Tip: In Docker, you do not need
sudoinRUNcommands — they run as root by default (before anyUSERinstruction). Addsudoonly if your application code or scripts explicitly call it at runtime as a non-root user.
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.
Still Not Working?
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.
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.