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 command keeps a restricted environment (secure_path from sudoers), so your PATH additions aren’t used. Use full paths, or sudo env "PATH=$PATH" command when you really need to.
  • sudo -i and su - start a login shell as root, reading root’s files.
  • su without - keeps much of your environment, which is confusing. Prefer su -.

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 echo output or interactive commands in .bashrc breaks scp and rsync, which expect a silent shell. Guard them with the interactive check at the top of the file.
  • Exporting PATH in .bashrc repeatedly 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.

  • Linux
  • Bash
  • Shell