How to Permanently Set $PATH on Linux and Unix

You install a program to a custom directory like /opt/myapp/bin, add it to your PATH for the current session, and everything works. Then you log out, log back in, and the path is gone. How do you make it stick?
TL;DR
To permanently set PATH, add an export statement to your shell's configuration file. For Bash, add export PATH="$PATH:/your/custom/path" to ~/.bashrc (Linux) or ~/.bash_profile (macOS). For system-wide changes affecting all users, edit /etc/environment or /etc/profile. After editing, run source ~/.bashrc or log out and back in for changes to take effect.
The PATH variable tells your shell where to find executable programs. When you type a command, the shell searches each directory in PATH until it finds a matching executable.
Let's see what your current PATH looks like:
echo $PATH
You'll see output like:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
These directories are separated by colons. When you run ls, the shell searches these directories in order until it finds /bin/ls.
Understanding User vs System PATH
There are two levels of PATH configuration: user-specific and system-wide.
User-specific paths affect only your account and are stored in files in your home directory:
User Level (affects only you)
├── ~/.bashrc # Bash on most Linux distros
├── ~/.bash_profile # Bash on macOS, login shells on Linux
├── ~/.profile # Generic shell configuration
└── ~/.zshrc # Zsh shell
System-wide paths affect all users and are in system configuration files:
System Level (affects everyone)
├── /etc/environment # System-wide environment variables
├── /etc/profile # System-wide shell configuration
└── /etc/profile.d/ # Drop-in directory for custom scripts
For personal use, stick with user-level configuration. Only modify system-level files when you need a program available to all users.
Adding to PATH for Bash (Most Linux Distributions)
On most Linux distributions, Bash reads ~/.bashrc for interactive non-login shells. This is the file you'll edit most often.
Open the file in your editor:
nano ~/.bashrc
Add this line at the end:
# Add custom program directory to PATH
export PATH="$PATH:/opt/myapp/bin"
The $PATH at the beginning preserves existing directories, and :/opt/myapp/bin adds your new directory to the end.
Save the file and apply the changes without logging out:
source ~/.bashrc
Now verify the change:
echo $PATH
You should see your new directory at the end.
Adding to PATH for Bash on macOS
macOS uses ~/.bash_profile for login shells (Terminal app opens login shells by default). If you're using the default Bash on macOS:
nano ~/.bash_profile
Add the export line:
export PATH="$PATH:/usr/local/myapp/bin"
Apply the changes:
source ~/.bash_profile
If you have both ~/.bash_profile and ~/.bashrc, you might want ~/.bash_profile to source ~/.bashrc:
# In ~/.bash_profile
if [ -f ~/.bashrc ]; then
source ~/.bashrc
fi
This way, you can keep all your PATH modifications in ~/.bashrc.
Adding to PATH for Zsh
If you use Zsh (the default shell on newer macOS versions), edit ~/.zshrc:
nano ~/.zshrc
Add your PATH modification:
export PATH="$PATH:/usr/local/myapp/bin"
Apply the changes:
source ~/.zshrc
Adding Multiple Directories at Once
You can add several directories in one statement:
export PATH="$PATH:/opt/myapp/bin:/home/user/scripts:/home/user/.local/bin"
Or make it more readable by adding them separately:
# Custom application binaries
export PATH="$PATH:/opt/myapp/bin"
# Personal scripts
export PATH="$PATH:$HOME/scripts"
# Python user binaries
export PATH="$PATH:$HOME/.local/bin"
Using $HOME instead of hardcoding /home/username makes your configuration more portable.
Prepending vs Appending to PATH
When you add a directory to PATH, the order matters. The shell searches directories from left to right and uses the first match it finds.
Appending (adding to the end):
export PATH="$PATH:/new/directory"
Prepending (adding to the beginning):
export PATH="/new/directory:$PATH"
If you want your custom programs to take precedence over system programs, prepend:
# Custom node takes precedence over system node
export PATH="/home/user/node/bin:$PATH"
This is useful when you have a newer version of a tool installed in a custom location.
System-Wide PATH for All Users
If you're an administrator and want to add a directory for all users, edit /etc/environment:
sudo nano /etc/environment
This file uses a different format - no export keyword:
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/company-tools/bin"
Changes take effect at the next login for all users.
Alternatively, create a file in /etc/profile.d/:
sudo nano /etc/profile.d/custom-path.sh
Add your export statement:
export PATH="$PATH:/opt/company-tools/bin"
The file needs the .sh extension and must be readable. It does not need execute permission, because login shells read it with . (source) and do not run it as a program.
This approach is cleaner because it keeps your customization separate from system files.
Checking Which Configuration File is Active
If you're not sure which file your shell is reading, add a test line to the file you're editing:
echo "Loading custom PATH from ~/.bashrc"
Then open a new terminal. If you see the message, that's the right file.
To see which files are being sourced, temporarily add this to the top of your shell config:
set -x # Enable debug mode
And this at the end:
set +x # Disable debug mode
Open a new terminal, and you'll see each command being executed, including which files are sourced.
Removing a Directory from PATH
If you need to remove a directory from PATH, you can't just delete the export line and source the file - you need to start a new shell session, or manually reconstruct PATH.
The safe approach:
- Edit the configuration file and remove the line
- Log out and log back in, or open a new terminal
For an immediate fix in the current session:
# This is tedious - you need to reconstruct PATH manually
export PATH="/usr/local/bin:/usr/bin:/bin"
Troubleshooting PATH Issues
If your PATH isn't working after editing a configuration file:
Check for syntax errors in your shell config:
bash -n ~/.bashrc
This checks for syntax errors without executing the file. No output means no errors.
Make sure you're editing the right file. Check which shell you're using:
echo $SHELL
If it says /bin/bash, you're using Bash. If it says /bin/zsh, you're using Zsh.
Verify the file was sourced:
# For Bash
source ~/.bashrc
# For Zsh
source ~/.zshrc
Check if something is overwriting your PATH later in the configuration file:
grep -n "PATH" ~/.bashrc
This shows all lines containing PATH with line numbers.
Per-Project PATH with Direnv
For development work where different projects need different tools, consider using direnv instead of modifying your global PATH.
Install direnv:
# Ubuntu/Debian
sudo apt install direnv
# macOS
brew install direnv
Add the hook to your shell config:
# For Bash, add to ~/.bashrc
eval "$(direnv hook bash)"
# For Zsh, add to ~/.zshrc
eval "$(direnv hook zsh)"
Create a .envrc file in your project directory:
cd ~/projects/myapp
nano .envrc
Add your PATH modification:
export PATH="$PWD/bin:$PATH"
Allow direnv to load this file:
direnv allow
Now, whenever you cd into this directory, the PATH is automatically modified, and it's restored when you leave.
Setting PATH for Cron Jobs
Cron jobs run with a minimal PATH, which often causes scripts to fail because they can't find commands.
Set PATH at the top of your crontab:
crontab -e
Add a PATH line:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/user/scripts
# Your cron jobs
0 2 * * * /home/user/scripts/backup.sh
Or set PATH inside the script itself:
#!/bin/bash
export PATH="/usr/local/bin:/usr/bin:/bin"
# Rest of your script
How Do I Reload .bashrc Without Logging Out?
Run source ~/.bashrc, or the short POSIX form . ~/.bashrc. The shell reads the file again in the current session, so new aliases, functions and PATH entries are available at once:
source ~/.bashrc
Only the terminal where you run it changes. Other open terminals keep their old settings until you reload them too or open new ones.
Sourcing runs the file again. It does not undo anything, so keep three effects in mind:
An alias, function or variable that you deleted from the file still exists in the session. Remove it with
unalias name,unset -f nameorunset NAME.A line such as
export PATH="$PATH:$HOME/bin"adds the directory again each time you source the file. Guard the line so it adds the directory only once:case ":$PATH:" in *":$HOME/bin:"*) ;; # already in PATH *) export PATH="$PATH:$HOME/bin" ;; esacSettings in
~/.profileor~/.bash_profiledo not change, becausesource ~/.bashrcreads only that one file.
For a clean start, replace the shell with a new one:
exec bash # a new interactive shell that reads ~/.bashrc
exec bash -l # a login shell that also reads ~/.bash_profile or ~/.profile
exec writes the command history of the session to ~/.bash_history first, so you keep it. You lose the variables you did not export, the functions and the aliases you defined by hand in the old shell.
If sourcing prints an error, check the file without running it:
bash -n ~/.bashrc # no output means no syntax errors
What Is the Difference Between a Variable With and Without export?
A variable without export is a shell variable: only the current shell can read it. export marks it as an environment variable, and the shell then passes a copy to every program it starts:
LOCAL_VAR="only here"
export SHARED_VAR="also in child processes"
bash -c 'echo "LOCAL_VAR=[$LOCAL_VAR] SHARED_VAR=[$SHARED_VAR]"'
# LOCAL_VAR=[] SHARED_VAR=[also in child processes]
Export what other programs must read, such as PATH, EDITOR, DATABASE_URL or AWS_PROFILE. Keep loop counters and temporary values unexported. Rules that help with exports:
VAR=value commandsets the variable only for that one command, without changing the shell:TZ=UTC date.declare -p VARshowsdeclare -x VAR=...for an exported variable anddeclare -- VAR=...for a shell variable.printenv VARprints only exported variables.- A child process gets a copy. It can change or export variables, but the change never reaches the parent shell. That is why
./set-env.shhas no effect on your terminal, andsource ./set-env.shdoes. - Bash cannot export arrays.
export serverson an array sets the attribute, but child processes do not get the array. Pass a delimited string instead. export -f myfuncexports a function to child Bash processes.
To load a .env file and export every variable in it, turn on allexport while you source it:
set -a # export every variable that is assigned from now on
source .env
set +a
This runs the file as shell code, so use it only for files you trust.
How Do I Delete an Exported Environment Variable?
Use unset. It removes the variable from the shell, and programs started after that do not get it:
export API_KEY="abc123"
unset API_KEY
printenv API_KEY || echo "API_KEY is gone"
unset takes several names at once (unset API_KEY DB_URL TOKEN), and it succeeds even when the variable does not exist, so you do not need to check first. Other cases:
export -n API_KEY # keep the value as a shell variable, stop exporting it
env -u API_KEY ./run-tests.sh # run one command without the variable
(unset API_KEY; ./run-tests.sh) # the same, in a subshell
env -i bash --norc # a shell that starts with an empty environment
With env -i, Bash gets no variables at all. It sets a built-in default PATH, which can include . (the current directory), so use this only for tests.
unset changes only the current shell and the programs it starts later. Programs that already run keep their copy. If the variable comes back in each new terminal, a startup file sets it: find the line with grep -n API_KEY ~/.bashrc ~/.profile ~/.bash_profile /etc/environment /etc/profile.d/*.sh, remove it, and open a new terminal.
Variable names can contain only letters, digits and underscores, and cannot start with a digit. export "MY-API-KEY=value" fails with not a valid identifier, and unset cannot remove such a name either. A program can still start Bash with an environment entry that has a hyphen, and Bash passes it on to child processes. Use env -u MY-API-KEY command to leave it out.
How Do I Create a Bash Alias That Takes Parameters?
You cannot: an alias is text replacement, and Bash adds the arguments at the end of the replaced text. Write a function instead. It takes parameters as $1, $2 and "$@":
# in ~/.bashrc
mkcd() {
mkdir -p -- "$1" && cd -- "$1"
}
search() {
grep -rn --exclude-dir=.git -- "$1" "${2:-.}"
}
Then mkcd ~/projects/new-app creates the directory and changes into it, and search TODO src searches src for TODO.
An alias with $1 in it does not do what it seems to do. Bash expands $1 when you use the alias, to the first positional parameter of your shell, which is usually empty. Your argument still goes at the end:
alias search='grep -r $1 .'
search pattern
# runs: grep -r . pattern ($1 is empty, "pattern" is added at the end)
That is also why an alias still works for arguments at the end, such as alias ll='ls -lh' and ll /var/log. Use a function as soon as the argument has to go anywhere else.
If an alias and a function have the same name, the alias wins and the function definition fails with syntax error near unexpected token '('. Run unalias name before you define the function, or rename one of them. type name shows what a name is.
Put aliases and functions in ~/.bashrc. On Debian and Ubuntu, the default ~/.bashrc also loads ~/.bash_aliases if it exists, which keeps them in a separate file. Reload with source ~/.bashrc after you change them, as described above.
Best Practices
Keep these guidelines in mind when modifying PATH:
- Always include
$PATHin your export to preserve existing directories - Use
$HOMEinstead of hardcoded paths for portability - Add comments explaining what each PATH addition is for
- Test changes in a new terminal before assuming they work
- For system-wide changes, use
/etc/profile.d/scripts instead of editing/etc/profile - Keep your PATH short - too many directories slow down command lookups
- Put frequently-used directories earlier in the PATH for faster lookups
Permanently setting PATH is straightforward once you know which file to edit for your shell and system. Whether you're adding personal script directories, custom application binaries, or system-wide tools, the key is understanding the difference between user-level and system-level configuration, and knowing how to test your changes.
Related Resources
- What is Bash?: which startup files Bash reads for login and non-login shells
- Introduction to Linux: User Management: login files and the user environment
- How to get the current date and time in Linux: aliases and functions for date formats
- Learn Linux interactive tutorial and Linux fundamentals flashcards
Try it hands-on
Run the commands from this article in the browser. Nothing to install.
Learn Linux - Interactive Tutorial
Master essential Linux commands through interactive lessons. Practice pwd, ls, cd, cat, grep, chmod, pipes, and more in a simulated terminal environment with 6 progressive lessons.
Fork Bomb Simulator
Visualize how the infamous :(){ :|:& };: fork bomb works. Watch processes multiply exponentially, exhaust system resources, and learn how to protect against it with ulimit, cgroups, and systemd.
We earn commissions when you shop through the links below.
Bitrise
Managed runners for GitHub Actions
Bitrise Build Hub runs your GitHub Actions jobs on M4 Macs and AMD Linux machines. Change runs-on and keep your workflows, secrets and logs on GitHub.
Atomsized
AWS platform engineering and GitOps
Design and automation for reliable AWS and Kubernetes platforms, safer delivery workflows, and preview and UAT environments your engineers can understand and own.
DigitalOcean
Cloud infrastructure for developers
Simple, reliable cloud computing designed for developers
DevDojo
Developer community & tools
Join a community of developers sharing knowledge and tools
SMTPfast
Developer-first email API
Send transactional and marketing email through a clean REST API. Detailed logs, webhooks, and embeddable signup forms in one dashboard.
QuizAPI
Developer-first quiz platform
Build, generate, and embed quizzes with a powerful REST API. AI-powered question generation and live multiplayer.
Want to support DevOps Daily and reach thousands of developers?
Become a SponsorTags
Found an issue?
Related Posts
Also worth your time on this topic
Bash Scripting Basics
What is the shebang line and how do you write a basic bash script?
junior
What Does "2>&1" Mean in Linux and Bash?
Learn what the 2>&1 redirection operator means, how stdout and stderr work, and how to send all output to a file, to the screen and a file with tee, to a protected file with sudo, or only to stderr.
Creating and Connecting to Your First Linux Server
Spin up an Ubuntu 24.04 Linux server in the cloud, configure SSH key authentication, and learn to navigate your new server. This exercise uses DigitalOcean but the steps work with any cloud provider.
30 minutes