Innovate | Connect | Transform
ContactService statusUSD $

GICT knowledge base

Environment Variables in Shell: What $PATH Controls and How to Set It Properly

Published

Linux Administration
Environment Variables in Shell: What $PATH Controls and How to Set It Properly

Every time you type python3 or nginx without specifying the full path, the shell has to find that executable somewhere. That somewhere is PATH. Understanding how PATH works is the difference between a script that runs everywhere and one that fails with “command not found” on every server except yours.

What PATH actually is

PATH is an environment variable. It holds a list of directory paths separated by colons. When you type a command, the shell searches these directories in order until it finds an executable with that name.

echo $PATH

On a typical Ubuntu VPS, this looks something like:

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

The shell checks each directory from left to right. It stops at the first match. If it reaches the end without finding the command, you get “command not found” and exit code 127.

How the shell resolves a command step by step

When you type nginx, the shell goes through this sequence:

  1. Check if nginx is a shell builtin (like cd or export). If yes, run it directly.
  2. Check if nginx is a function defined in the current shell session.
  3. Search each directory in PATH, in order, for an executable file named nginx.
  4. First match wins. If no match is found, print “command not found”.

This matters when you have two programs with the same name in different PATH directories. The one in the earlier directory wins, regardless of which is “better” or more recent.

To see which version of a command the shell would run:

which python3
type python3
command -v python3

All three tell you the same thing — the full path the shell resolves for that command. Use these when debugging why your script runs a different version than you expect.

Viewing and modifying PATH

View the current PATH:

echo $PATH

Add a directory to the end of PATH:

export PATH="$PATH:/opt/myapp/bin"

Add it to the beginning to give it priority:

export PATH=/opt/myapp/bin:$PATH

The order matters. If /opt/myapp/bin/nginx and /usr/sbin/nginx both exist, putting the new directory first means your version runs instead of the system one.

These changes are temporary — they disappear when you close the terminal. To make them permanent, you need to know where to put them, which brings you to the next section.

Where PATH changes do not apply

This is the part most tutorials skip. Changes made in your shell profile (.bashrc, .profile) do not apply to systemd services, cron jobs, or any process that does not start from your interactive shell session.

Here is why. When you log in or open a terminal, the shell reads your profile scripts and sets environment variables including PATH. But when systemd starts a service, it does not read .bashrc. It starts the process directly with the environment it was given, which comes from the service file or the system-wide environment.

The same problem affects cron. Cron jobs run in a minimal environment that does not include your user is profile. PATH in cron defaults to whatever cron is configured to use, which is usually just /usr/bin:/bin.

Three ways to handle this:

  • For systemd services — set the full path in ExecStart, or set Environment=PATH=... in the service file.
  • For cron jobs — set PATH at the top of your crontab:
PATH=/usr/local/bin:/usr/bin:/bin:/opt/myapp/bin
0 * * * * /opt/myapp/bin/backup.sh
  • For scripts run by other programs — always use absolute paths in ExecStart or source a file that sets the environment before running the script.

Environment variables vs shell variables

There are two kinds of variables in bash.

A shell variable exists only in the current shell process:

MYVAR="hello"
echo $MYVAR    # prints hello
bash -c 'echo $MYVAR'   # prints nothing -- child shell does not see it

An environment variable is inherited by every child process:

export MYVAR="hello"
bash -c 'echo $MYVAR'   # prints hello -- child shell inherits it

PATH is an environment variable because every command the shell runs is a child process that needs to find executables. Shell variables like myvar=hello are local to that shell session.

View all environment variables:

env

View all variables including shell-local ones:

set | head

Where to set PATH permanently

There are several places to put a PATH change, and the right one depends on who needs to see it.

/etc/environment — system-wide, applies to every user at login. Use this for paths that should be available to all users and services. Edit it directly:

sudo nano /etc/environment

Add a line like:

PATH="/opt/myapp/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

~/.profile — user-specific, read at login for non-interactive shells. Good for things that need to be available to scripts running outside an interactive session.

~/.bashrc — user-specific, read for interactive bash sessions. This is where you put aliases, functions, and PATH additions for your own terminal work. It is not read by login shells or systemd services.

The practical rule: put system-wide paths in /etc/environment, put user-specific interactive settings in ~/.bashrc, and always use absolute paths in systemd service files.

Debugging “command not found” errors

When a script fails with “command not found”, the fix is almost always one of three things:

  1. The command is not in PATH. Use which or type to check if the shell can find it. If not, either add its directory to PATH or use the full path.
  2. PATH differs between your terminal and the script is execution environment. The script might run under a different user or through cron, both of which may have different PATH values. Use absolute paths to eliminate this ambiguity.
  3. The executable is not marked as executable. Check with ls -l /full/path/to/command. If it lacks execute permission, run chmod +x /full/path/to/command.

To see exactly what PATH a running process sees:

cat /proc/$(pgrep -f myservice)/environ | tr '\0' '\n' | grep PATH

Replace myservice with the name of your process. This shows the environment as it actually exists inside that process.

A practical workflow for adding a command to PATH

You have installed a tool in /opt/mytool/bin/ and want it available everywhere:

# 1. Verify the command works with the full path
/opt/mytool/bin/mytool --version

# 2. Add it to /etc/environment for system-wide access
sudo nano /etc/environment
# Add /opt/mytool/bin to the front of PATH

# 3. Reload the environment
source /etc/environment

# 4. Verify it is now found without the full path
which mytool

# 5. For systemd services, also set the path in the service file
# Environment=PATH=/opt/mytool/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
The shebang line matters too. If a script runs a command without a full path and fails with “command not found”, the problem might be the shebang. If the script is called directly (not passed to bash), the kernel uses the shebang line to decide which interpreter to run — and that interpreter also needs the command in its PATH. Always use absolute paths inside scripts for anything that is not a shell builtin.

Related guides

Account services are unavailable until the production backend is configured.