When you type nginx or node, the shell doesn’t search the whole disk. It looks through the directories listed in the PATH environment variable, in order, and runs the first match. Most “command not found” problems after installing software come down to PATH.
See your PATH
echo "$PATH"
# /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
echo "$PATH" | tr ':' '\n' # one directory per line
Directories are separated by :. Order matters: /usr/local/bin comes before /usr/bin, so a locally installed version wins over the distribution’s.
Find where a command comes from
command -v node # path of the command that will run
type -a python3 # all matches, plus aliases and functions
which -a php
type -a is the most complete. It shows aliases and shell functions too, which can shadow real commands.
The shell caches command locations. After installing or moving a binary in the same session, run hash -r to clear the cache.
Add a directory for this session
export PATH="$HOME/.local/bin:$PATH" # prepend: takes priority
export PATH="$PATH:/opt/tool/bin" # append: used only if nothing earlier matches
This lasts until you close the shell.
Add a directory permanently
Just for your user. Add the line to ~/.bashrc (interactive shells) or ~/.profile (login shells):
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
Use single quotes so $PATH is written literally and expanded at login, not now.
Many distributions’ default ~/.profile already adds ~/bin and ~/.local/bin if they exist. Tools installed with pip install --user, pipx or Cargo go to ~/.local/bin or ~/.cargo/bin.
For all users. Create a file in /etc/profile.d/:
echo 'export PATH="$PATH:/opt/tool/bin"' | sudo tee /etc/profile.d/tool.sh
It applies at the next login. Or simply symlink the binary into a directory that’s already on the PATH:
sudo ln -s /opt/tool/bin/tool /usr/local/bin/tool
That’s often the cleanest option. /usr/local/bin exists for exactly this. See ln.
Avoid editing /etc/environment with shell syntax. It’s read by PAM, not a shell, so it takes plain KEY="value" lines and no $PATH expansion.
PATH and sudo
sudo mytool # sudo: mytool: command not found
sudo uses its own safe PATH (secure_path in /etc/sudoers), not yours. Options:
- Install the tool in
/usr/local/bin(it’s insecure_path). - Use the full path:
sudo /opt/tool/bin/mytoolorsudo "$(command -v mytool)". - Add the directory to
secure_pathwithvisudo, carefully.
PATH in cron and systemd
Cron runs with a minimal PATH, often just /usr/bin:/bin. Commands in /usr/local/bin or ~/.local/bin aren’t found. Set PATH at the top of the crontab:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
or use absolute paths in jobs. See crontab.
systemd services get a default PATH too, and don’t read your .bashrc. Use absolute paths in ExecStart= or set Environment=PATH=….
Version managers such as nvm, pyenv and rbenv change PATH in your shell startup files. Scripts run by cron or systemd don’t see them, so point to the exact binary (/home/app/.nvm/versions/node/v22…/bin/node) or install system-wide. See nvm.
Security notes
- Never put
.(the current directory) in PATH, especially for root. A maliciouslsin a shared directory would run when youcdthere. - Keep writable directories out of root’s PATH.
- Be aware that a directory earlier in PATH can shadow system commands. If
python3suddenly behaves oddly, runtype -a python3.
Fix “command not found”
- Is it installed? Check
dpkg -L package | grep bin/orrpm -ql package. - Where is it? Search with
sudo find / -name toolname -type f 2>/dev/null. - Is that directory in
echo $PATH? If not, add it or symlink the binary. - Opened a new shell? Run
source ~/.bashrcorhash -r. - Under sudo, cron or systemd? See the sections above.
Quick reference
| Task | Command |
|---|---|
| Show PATH | echo "$PATH" | tr ':' '\n' |
| Where is a command | command -v x / type -a x |
| Add for session | export PATH="/dir:$PATH" |
| Add for user | line in ~/.bashrc or ~/.profile |
| Add for everyone | file in /etc/profile.d/ |
| Simplest | ln -s /opt/x/bin/x /usr/local/bin/x |
| Clear lookup cache | hash -r |
Related: environment variables and shebang and running scripts.