Short answer: “command not found” means the shell looked for a program with that name in every directory listed in $PATH and found none. There are three causes: the program is not installed, it is installed somewhere that is not on your PATH, or the shell is working from a stale or wrong name — a typo, an alias, a remembered old location. The exit status is 127 in each case. The commands below tell you which one it is.
| Shell | Message |
|---|---|
| bash | bash: mytool: command not found |
| bash, login shell (SSH) | -bash: mytool: command not found |
| zsh | zsh: command not found: mytool |
| fish | fish: Unknown command: mytool |
| sh / dash (scripts, cron) | sh: 1: mytool: not found |
| sudo | sudo: mytool: command not found |
Find the cause in one minute
type -a mytool # every match: alias, function, builtin, file
command -v mytool # the path the shell would run, or nothing
echo "$PATH" | tr ':' '\n' # the directories it searches, one per line
# installed somewhere else? look for the file
sudo find / -xdev -type f -name 'mytool' 2>/dev/null
ls -l /usr/local/bin/mytool ~/.local/bin/mytool 2>/dev/nulltypeandcommand -vprint nothing, andfindfinds nothing — the program is not installed. Go to cause 1.findfinds it, in a directory that is not in the PATH list — go to cause 2.typenames an alias or a function, or the file is where the PATH says but the shell still complains — go to cause 3.
Prefer type and command -v to which: they ask the shell itself, so they know about aliases, functions and the shell’s own lookup table. On a Debian or Ubuntu server that answers “not found” with a package suggestion, the command-not-found helper is installed and has already done half the work for you.
Cause 1: the program is not installed
Minimal server images and Docker base images leave out a lot: curl, wget, unzip, ps, even sudo. Install the package with your distribution’s package manager. The package name is often, but not always, the command’s name — the table at the end of this page lists the exceptions.
sudo apt update && sudo apt install -y curl # Debian · Ubuntu
sudo dnf install -y curl # Rocky · Alma · Fedora
apk add curl # Alpine
sudo pacman -S curl # ArchWhen you do not know which package ships a command, ask the package manager:
# Debian · Ubuntu
sudo apt install -y apt-file && sudo apt-file update
apt-file search bin/dig
# Rocky · Alma · Fedora
dnf provides '*/bin/dig'
# Alpine
apk search -v 'cmd:dig'Two programs are never packages of their own: npx comes with npm, and vite lives inside a project’s node_modules. And yum missing on Debian or Ubuntu is not a bug at all — it is the package manager of another distribution family.
Cause 2: it is installed, but not on PATH
Installers that work without root put programs in your home directory, and an application from a vendor’s tarball lands under /opt. Neither is necessarily on the PATH of the shell you are in. The usual places:
| Directory | Who puts programs there |
|---|---|
~/.local/bin | pip install --user, pipx, many install scripts |
~/.cargo/bin | rustup and cargo install |
/usr/local/go/bin, ~/go/bin | Go from the official tarball, go install |
~/.nvm/versions/node/…/bin | Node.js through nvm (its script adds it to the shell) |
~/miniconda3/bin | Miniconda and Anaconda, until conda init has run |
/snap/bin | Snap packages on Ubuntu |
/opt/<app>/bin | Vendor tarballs and some .deb or .rpm packages |
/usr/sbin, /sbin | System tools: useradd, reboot, iptables — missing after su without the dash |
To use the program right now, call it by its full path or extend the PATH of the current shell. That lasts until you close the terminal:
~/.local/bin/mytool --version
export PATH="$HOME/.local/bin:$PATH"Add a directory to PATH permanently
The PATH is set again in every new shell, from the shell’s startup files. Put the line in the file your shell reads, open a new terminal (or log in again), and check with command -v mytool.
bash
A terminal window reads ~/.bashrc; an SSH login reads ~/.bash_profile or, if that does not exist, ~/.profile. On Debian, Ubuntu, Rocky and Alma the login file already loads ~/.bashrc, so one line there covers both:
# add ~/.local/bin once, in front of the system directories
case ":$PATH:" in
*":$HOME/.local/bin:"*) ;;
*) export PATH="$HOME/.local/bin:$PATH" ;;
esacsource ~/.bashrcIf you created ~/.bash_profile yourself, it replaces ~/.profile and may not load ~/.bashrc. Add [ -f ~/.bashrc ] && . ~/.bashrc to it. Debian and Ubuntu’s stock ~/.profile also adds ~/.local/bin on its own — but only if the directory existed when you logged in, so log in again after the first install that creates it.
zsh (and macOS)
typeset -U path # no duplicates
path=("$HOME/.local/bin" $path)~/.zshrc is read by every interactive zsh, which is the default shell on macOS. For scripts that run zsh non-interactively, the place is ~/.zshenv. After installing a program while zsh is open, run rehash or open a new terminal.
fish
fish_add_path ~/.local/binFor every user on the server
export PATH="/opt/mytool/bin:$PATH"Every login shell reads the files in /etc/profile.d/. A PATH= line in /etc/environment also works for every login, but that file takes no variables: write the whole list out.
Cause 3: a stale lookup, an alias or a typo
- The shell remembers the old location. bash and zsh cache where they found a command. After a program moves — you removed a distribution package and installed a newer copy in
/usr/local/bin, say — the cache points at a file that is gone.hash -rin bash,rehashin zsh. - An alias or a function shadows it.
type -a mytoollists them first.unalias mytoolremoves an alias for this shell; then find it in your startup files. - Case and spelling. Linux is case-sensitive, so
Pythonis notpython, andpythonitself may only exist aspython3. - The script, not the command. A script that exists but starts with a wrong
#!line or Windows line endings fails with “bad interpreter: No such file or directory” rather than “not found”. And./script.shwithout the execute bit gives Permission denied.
Works in your terminal, fails in cron, sudo, systemd or Docker
Your terminal’s PATH comes from your startup files. Everything else starts with a PATH of its own, usually a short one:
- sudo replaces PATH with
secure_pathfrom/etc/sudoers, so a program in~/.local/binor an nvm install is “not found” under sudo. Call it by its full path:sudo "$(command -v mytool)". - cron runs jobs with
PATH=/usr/bin:/bin. Set aPATH=line at the top of the crontab, or use absolute paths in the job. - systemd does not read your shell’s startup files. Give
ExecStart=the absolute path of the program, and setEnvironment=PATH=…if the program itself starts others. - Docker images contain only what their Dockerfile installs. Install the package in the image, not in the running container.
Each of these, with the exact lines to change, is in exit code 127 and, for cron, in cron job not running.
Every tool: package and fix
The package to install on Debian and Ubuntu, or what to do instead, and the article that covers each tool on every distribution, in Docker and on macOS where it matters.
| Command | Debian / Ubuntu | Article |
|---|---|---|
curl | curl | curl: command not found |
wget | wget | wget: command not found |
git | git | git: command not found |
sudo | sudo, installed as root | sudo: command not found |
make | build-essential (or make) | make: command not found |
docker | Docker's own repository (docker-ce) | docker: command not found |
docker-compose | docker-compose-plugin, then docker compose | docker-compose: command not found |
systemctl | Nothing to install: the system does not run systemd | systemctl: command not found |
apt-get, apt | Part of every Debian and Ubuntu: missing means another system | apt-get: command not found |
add-apt-repository | software-properties-common | add-apt-repository: command not found |
python | python3, python-is-python3 | python: command not found |
pip | python3-pip, python3-venv | pip: command not found |
conda | Installed already? Run conda init once | conda: command not found |
npx | Comes with npm: Node.js from NodeSource or nvm | npx: command not found |
nvm | A shell function: its lines belong in ~/.bashrc or ~/.zshrc | nvm: command not found |
vite | A project dependency: npm install, then npx vite | vite: command not found |
yarn, pnpm | corepack enable, or npm install -g | yarn or pnpm: command not found |
dig, nslookup, host | bind9-dnsutils | dig, nslookup or host: command not found |
ifconfig, netstat | net-tools — or use ip and ss | ifconfig: command not found |
ps, top, free | procps | ps: command not found |
unzip | unzip (and zip) | unzip: command not found |
yum | Use apt here; yum and dnf belong to Rocky, Alma and RHEL | yum: command not found |
brew | Not needed here, apt is the package manager; on a Mac install Homebrew | brew: command not found |
| Any, in a script or job | Exit status 127 | Exit code 127 |
Frequently asked
What does "command not found" mean in Linux?
The shell searched every directory in your PATH for a program with that name and found none. Either the program is not installed, it is installed in a directory that is not on your PATH, or the name is wrong — a typo, an alias, or a location the shell remembered from before. The exit status is 127.
How do I add a directory to PATH permanently?
Add export PATH="$HOME/.local/bin:$PATH" to ~/.bashrc for bash, path=("$HOME/.local/bin" $path) to ~/.zshrc for zsh, or run fish_add_path ~/.local/bin once in fish, then open a new terminal. For every user on the server, put the export line in a file under /etc/profile.d/.
Why does a command work in my terminal but not in cron or with sudo?
Both start with a PATH of their own. cron runs jobs with /usr/bin:/bin, and sudo replaces your PATH with secure_path from /etc/sudoers. Use the program's full path — command -v shows it — or set PATH at the top of the crontab.
Why does zsh still say command not found after I installed the program?
zsh remembers where it found commands. Run rehash or open a new terminal. If the program went into a directory that is not on your PATH, add that directory in ~/.zshrc.