You added an alias or a PATH entry, and it works in one terminal but not another, or not over SSH, or not in cron. The cause is usually which startup file bash reads, and that depends on what kind of shell it is. Here’s the logic, and a setup that just works.
Login vs interactive shells
| Shell type | Example | Reads |
|---|---|---|
| Interactive login | SSH login, console login, su -, sudo -i |
/etc/profile, then the first of ~/.bash_profile, ~/.bash_login, ~/.profile |
| Interactive non-login | New terminal tab in a desktop, typing bash, tmux panes |
/etc/bash.bashrc (Debian/Ubuntu), ~/.bashrc |
| Non-interactive | Scripts, ssh host command, cron |
None (except $BASH_ENV if set) |
Check what you’re in:
shopt -q login_shell && echo login || echo non-login
[[ $- == *i* ]] && echo interactive || echo non-interactive
Note: when you SSH in to run a single command (ssh server 'cmd'), bash on most systems still reads ~/.bashrc, but not the profile files. Distributions differ here.
What goes where
| File | Put here |
|---|---|
~/.profile (or ~/.bash_profile) |
Environment variables (PATH, EDITOR, LANG): things inherited by child processes. Read once at login |
~/.bashrc |
Interactive settings: aliases, functions, prompt, shell options, completion |
/etc/profile, /etc/profile.d/*.sh |
System-wide environment for all users |
/etc/bash.bashrc |
System-wide interactive settings (Debian/Ubuntu) |
The standard pattern
Make login shells also load .bashrc, so you get aliases everywhere. Ubuntu’s default ~/.profile already does this:
# ~/.profile (or ~/.bash_profile)
if [ -n "$BASH_VERSION" ] && [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
export PATH="$HOME/.local/bin:$PATH"
export EDITOR=vim
# ~/.bashrc
case $- in *i*) ;; *) return;; esac # stop here if not interactive
alias ll='ls -alF'
alias k='kubectl'
HISTSIZE=10000
HISTCONTROL=ignoreboth
PS1='\u@\h:\w\$ '
Important: if you create ~/.bash_profile, bash stops reading ~/.profile. Either keep everything in one of them, or make .bash_profile source .profile.
Applying changes
source ~/.bashrc # reload in the current shell (or: . ~/.bashrc)
exec bash -l # start a fresh login shell
sudo, su and root
sudo commandkeeps a restricted environment (secure_pathfrom sudoers), so yourPATHadditions aren’t used. Use full paths, orsudo env "PATH=$PATH" commandwhen you really need to.sudo -iandsu -start a login shell as root, reading root’s files.suwithout-keeps much of your environment, which is confusing. Prefersu -.
See sudo and sudoers.
Cron and systemd don’t read these files
Scheduled jobs and services don’t load .bashrc or .profile. Set what they need explicitly. See cron environment variables and systemd unit files.
Debugging startup
bash -x -l -c exit 2>&1 | head -50 # trace which files run and what they do
A broken .bashrc can stop SSH logins from completing (for example, a command that waits for input). Test changes in a second session before closing the first.
Other shells
zsh uses ~/.zprofile (login) and ~/.zshrc (interactive). fish uses ~/.config/fish/config.fish. See zsh and Oh My Zsh and fish shell.
Common mistakes
- Putting
echooutput or interactive commands in.bashrcbreaksscpandrsync, which expect a silent shell. Guard them with the interactive check at the top of the file. - Exporting
PATHin.bashrcrepeatedly adds duplicates every time a subshell starts. Put PATH changes in the profile file instead.
Quick reference
| Want | File |
|---|---|
| PATH and exported variables | ~/.profile (or ~/.bash_profile) |
| Aliases, prompt, functions | ~/.bashrc |
| Load .bashrc at login | Source it from the profile file |
| System-wide | /etc/profile.d/*.sh |
| Reload | source ~/.bashrc |
Related: environment variables on Linux and Linux prompt colors.