| |

LFCS 45 ๐Ÿง Shell Configuration Files โ€” .bashrc, .profile

โฑ๏ธ Estimated read: 12 min | |

When a Bash shell starts, it does not simply present a prompt and wait. It reads one or more configuration files first, and the contents of those files determine which variables are set, which aliases exist, and how the environment is shaped. The confusion that surrounds shell configuration files comes from the fact that Bash reads different files depending on how the shell was started. A login shell over SSH does not read the same files as a terminal window opened inside a graphical session, and that terminal window does not read the same files as a non-interactive script .

The two files that cause the most confusion are ~/.bashrc and ~/.profile. The common description is that .bashrc is for interactive shells and .profile is for login shells, but that simplification hides the actual mechanics. Bash reads ~/.bash_profile, ~/.bash_login, and ~/.profile in that order when it is a login shell, and it reads only the first one that exists . It reads ~/.bashrc when it is an interactive non-login shell . A typical desktop terminal is an interactive non-login shell, so it reads .bashrc but not .profile. An SSH session is an interactive login shell, so it reads .profile (or .bash_profile) but not .bashrc automatically .

For the LFCS exam, “Use shell/environment variables” is the objective, and understanding which file to edit is the practical consequence of that objective . The rule of thumb that works on most systems is: put environment variables and anything that must be inherited by child processes in ~/.profile (or ~/.bash_profile), and put aliases, functions, and prompt customizations in ~/.bashrc. Many distributions also have ~/.profile source ~/.bashrc so that login shells get both sets of settings .

Key point: Bash reads ~/.bash_profile, ~/.bash_login, then ~/.profile for login shells, and ~/.bashrc for interactive non-login shells; on most systems, ~/.bash_profile or ~/.profile should source ~/.bashrc so that login shells inherit both environment variables and interactive settings.


Why the distinction exists

The login vs non-login problem. A login shell is the first shell started after authenticationโ€”an SSH connection, a console login, or su -. A non-login shell is any shell started after that: a terminal emulator window, a subshell, or bash typed at a prompt . Bash needs to know this because some settings should run once per login (environment setup, resource limits) and some should run every time an interactive shell starts (aliases, prompt) .

The ~/.profile fallback chain. Bash does not read all three login files. It reads ~/.bash_profile if it exists; if not, it tries ~/.bash_login; if neither exists, it falls back to ~/.profile . This chain exists for compatibility: .profile is the original Bourne shell file, .bash_profile is Bash-specific, and .bash_login is a C shell convention. You should use only one of them. If ~/.bash_profile exists, ~/.profile is ignored entirely .

The ~/.bashrc gap. A login shell does not read ~/.bashrc automatically. This means an SSH session that reads only ~/.profile will not get the aliases and functions defined in ~/.bashrc unless ~/.profile explicitly sources it. The standard fix is a line in ~/.bash_profile or ~/.profile that checks for ~/.bashrc and sources it if present . Many distributions ship a default ~/.profile that does this, but not all do, and the omission is a common source of “my alias works in the terminal but not over SSH” confusion.

The system-wide layer. Before any user file, Bash reads /etc/profile for login shells . Files in /etc/profile.d/ with a .sh extension are sourced by /etc/profile and apply to all users. This is where system administrators put environment variables that every user should inherit, such as JAVA_HOME or a custom PATH addition . For interactive non-login shells, Bash reads /etc/bash.bashrc on Debian-based systems, though this varies by distribution.

The non-interactive silence. A shell running a script is non-interactive. Bash does not read ~/.bashrc or ~/.profile for non-interactive shells unless the script explicitly sources them or BASH_ENV is set . This is why environment variables set in a terminal do not automatically appear in cron jobs or systemd services. The configuration files are for interactive use; scripts get the environment they are given.


a. The login shell files: ~/.bash_profile, ~/.bash_login, ~/.profile

When Bash starts as a login shell, it reads /etc/profile first, then searches for the first readable file among ~/.bash_profile, ~/.bash_login, and ~/.profile . It does not read all threeโ€”it reads exactly one. The choice of which file to create is a matter of convention and compatibility.

~/.profile is the most portable choice. It is read by Bash, sh, ksh, and other Bourne-compatible shells, so it works even if the user changes shells . It is the right file for environment variables that should be inherited by all processes: PATH, EDITOR, LANG, and similar settings .

~/.bash_profile is Bash-specific. It is the conventional choice on Red Hat-based systems, while Debian-based systems tend to favor ~/.profile . If you use ~/.bash_profile, you should include a snippet to source ~/.bashrc so that aliases and functions are available in login shells:

if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

This snippet is the standard bridge between the two files. Without it, an SSH session will have the environment variables from .bash_profile but not the aliases from .bashrc.

~/.bash_login is rarely used. It exists for compatibility with the C shell’s .login convention and is generally not created on modern systems. If it exists, it takes precedence over .profile.


b. The interactive non-login file: ~/.bashrc

~/.bashrc is read every time an interactive non-login shell starts. This is the shell you get when you open a terminal emulator in a graphical desktop, or when you type bash at an existing prompt . It is also read by some SSH configurations for non-interactive remote commands, depending on how Bash detects the connection .

The content of ~/.bashrc should be settings that are specific to interactive use and do not need to be inherited by child processes. Aliases are the classic example:

alias ll='ls -alF'
alias grep='grep --color=auto'

Functions, shell options (set -o), and prompt customization (PS1) also belong here . These settings do not make sense for a script, and they should not be exported to child processes.

A common mistake is to put export PATH=... in ~/.bashrc. This works for terminal windows, but it does not apply to SSH login sessions unless .bashrc is sourced from .profile. The PATH modification belongs in .profile or .bash_profile if it should apply to all sessions .

On Debian and Ubuntu, the default ~/.bashrc contains a guard at the top that returns early if the shell is not interactive. This prevents the file from running its aliases and prompt settings in non-interactive contexts. The guard looks like this:

case $- in
    *i*) ;;
      *) return;;
esac

This is why sourcing .bashrc from .profile is safe on Debian-based systemsโ€”the guard prevents non-interactive shells from executing the interactive-only parts.


c. System-wide configuration and the /etc/profile.d/ directory

User configuration files apply to one user. System-wide configuration applies to everyone. The primary file for system-wide login settings is /etc/profile, which is read before any user file . On Debian-based systems, /etc/bash.bashrc serves the same role for interactive non-login shells.

The /etc/profile.d/ directory is where system administrators place modular configuration scripts. /etc/profile contains a loop that sources every .sh file in that directory . This is the standard location for software that needs to set environment variables for all usersโ€”Java, Go, Node.js, and similar tools. A file like /etc/profile.d/java.sh might contain:

export JAVA_HOME=/usr/lib/jvm/default-java
export PATH="$JAVA_HOME/bin:$PATH"

The advantage of /etc/profile.d/ over editing /etc/profile directly is modularity and upgrade safety. Package managers can add and remove files in this directory without conflicting with each other or with administrator edits to /etc/profile .

For the LFCS exam, knowing that /etc/profile.d/*.sh is sourced by /etc/profile is the key fact. The exam objective “Manage system-wide environment profiles” points directly at this mechanism .


Complete Example Session

The following session demonstrates the difference between login and non-login shells and the correct placement of configuration.

# ============================================
# PART 1: CHECK CURRENT SHELL TYPE
# ============================================
echo $0
# Output: -bash (the leading dash indicates a login shell)

# ============================================
# PART 2: CREATE A TEST VARIABLE IN .profile
# ============================================
echo 'export TEST_PROFILE="from profile"' >> ~/.profile

# ============================================
# PART 3: CREATE A TEST ALIAS IN .bashrc
# ============================================
echo 'alias testalias="echo from bashrc"' >> ~/.bashrc

# ============================================
# PART 4: OPEN A NEW TERMINAL (non-login shell)
# ============================================
# In a terminal emulator:
echo $TEST_PROFILE
# Output: (empty) โ€” .profile not read

testalias
# Output: from bashrc โ€” .bashrc read

# ============================================
# PART 5: START A LOGIN SHELL
# ============================================
bash --login
echo $TEST_PROFILE
# Output: from profile โ€” .profile read

testalias
# Output: (command not found) โ€” .bashrc not read
# (unless .profile sources .bashrc)

# ============================================
# PART 6: ADD THE BRIDGE TO .profile
# ============================================
# Add to ~/.profile:
# if [ -f ~/.bashrc ]; then . ~/.bashrc; fi

# ============================================
# PART 7: VERIFY AFTER BRIDGE
# ============================================
bash --login
testalias
# Output: from bashrc โ€” now .bashrc is sourced

# ============================================
# PART 8: SYSTEM-WIDE VARIABLE
# ============================================
sudo tee /etc/profile.d/test.sh << 'EOF'
export SYSTEM_WIDE="available to all users"
EOF

# ============================================
# PART 9: VERIFY SYSTEM-WIDE (new login shell)
# ============================================
bash --login
echo $SYSTEM_WIDE
# Output: available to all users

# ============================================
# PART 10: CLEANUP
# ============================================
# Remove test lines from ~/.profile and ~/.bashrc
# sudo rm /etc/profile.d/test.sh

The ten parts cover checking the shell type, adding a variable to .profile, adding an alias to .bashrc, testing in a non-login shell, testing in a login shell, adding the bridge, verifying the bridge, creating a system-wide variable, verifying system-wide access, and cleanup. What the session demonstrates is that the file that runs depends entirely on how the shell was started, and that the bridge from login files to .bashrc is what makes settings consistent across session types.


Quick Reference

Which File Runs When

Shell TypeFiles Read (in order)
Login (SSH, console)/etc/profile, then first of ~/.bash_profile, ~/.bash_login, ~/.profile
Interactive non-login (terminal)/etc/bash.bashrc (Debian), ~/.bashrc
Non-interactive (script)None (unless BASH_ENV set)

Login File Precedence

FileRead If
~/.bash_profileExists and readable (first priority)
~/.bash_login.bash_profile does not exist
~/.profileNeither of the above exists

What Goes Where

SettingFile
PATH, EDITOR, LANG~/.profile or ~/.bash_profile
Aliases, functions~/.bashrc
Prompt (PS1)~/.bashrc
System-wide variables/etc/profile.d/*.sh

Common Commands

CommandEffect
bash --loginStart a login shell
bashStart a non-login interactive shell
source ~/.bashrcApply .bashrc changes immediately
echo $0Show current shell (leading - means login)

Best Practices

โœ… Do This:

# ~/.profile
export PATH="$HOME/bin:$PATH"                              # โœ… inherited variable
if [ -f ~/.bashrc ]; then . ~/.bashrc; fi                  # โœ… bridge to .bashrc
# ~/.bashrc
alias ll='ls -alF'                                         # โœ… interactive only
sudo tee /etc/profile.d/app.sh <<< 'export APP_HOME=/opt/app' # โœ… system-wide

โŒ Don’t Do This:

# ~/.bashrc
export PATH="$HOME/bin:$PATH"                              # โŒ not read by SSH login
# ~/.profile
alias ll='ls -alF'                                         # โŒ not interactive
# create both ~/.bash_profile and ~/.profile                 # โŒ .profile ignored

Common Pitfalls

PitfallWhy It HappensFix
Alias works in terminal, not SSH.bashrc not sourced by login shellAdd bridge to .profile
Variable missing in terminalSet in .profile, not .bashrcMove to .bashrc or source it
.profile ignored.bash_profile existsUse only one login file
System-wide variable not appliedFile not in /etc/profile.d/Place .sh file there
Changes not appliedShell not reloadedsource file or new shell

Real-World Examples

1. Add PATH in .profile

export PATH="$HOME/bin:$PATH"

2. Bridge to .bashrc

if [ -f ~/.bashrc ]; then . ~/.bashrc; fi

3. Alias in .bashrc

alias ll='ls -alF'

4. System-wide Java home

sudo tee /etc/profile.d/java.sh <<< 'export JAVA_HOME=/usr/lib/jvm/default'

5. Check shell type

echo $0  # -bash = login, bash = non-login

6. Reload .bashrc

source ~/.bashrc

7. Force login shell

bash --login

8. Force non-login shell

bash

9. Skip config files

bash --noprofile --norc

10. View /etc/profile.d/

ls /etc/profile.d/

Visual

Login vs Non-Login Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  SSH LOGIN (login shell)                     โ”‚
โ”‚         โ”‚                                    โ”‚
โ”‚         โ–ผ                                    โ”‚
โ”‚  /etc/profile                                โ”‚
โ”‚         โ”‚                                    โ”‚
โ”‚         โ–ผ                                    โ”‚
โ”‚  ~/.bash_profile OR ~/.bash_login OR ~/.profileโ”‚
โ”‚         โ”‚                                    โ”‚
โ”‚         โ–ผ                                    โ”‚
โ”‚  (does NOT read ~/.bashrc unless sourced)    โ”‚
โ”‚                                              โ”‚
โ”‚  TERMINAL (interactive non-login)            โ”‚
โ”‚         โ”‚                                    โ”‚
โ”‚         โ–ผ                                    โ”‚
โ”‚  /etc/bash.bashrc (Debian)                   โ”‚
โ”‚         โ”‚                                    โ”‚
โ”‚         โ–ผ                                    โ”‚
โ”‚  ~/.bashrc                                   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Bridge

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ~/.profile                                  โ”‚
โ”‚  export PATH=...      โ† environment          โ”‚
โ”‚  if [ -f ~/.bashrc ]; then . ~/.bashrc; fi   โ”‚
โ”‚         โ”‚                                    โ”‚
โ”‚         โ–ผ                                    โ”‚
โ”‚  ~/.bashrc                                   โ”‚
โ”‚  alias ll='ls -alF'   โ† interactive          โ”‚
โ”‚  PS1=...                                     โ”‚
โ”‚                                              โ”‚
โ”‚  Result: SSH gets both environment and aliasesโ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

File Precedence

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Login shell file search:                    โ”‚
โ”‚                                              โ”‚
โ”‚  1. ~/.bash_profile  โ† if exists, stop       โ”‚
โ”‚  2. ~/.bash_login    โ† if exists, stop       โ”‚
โ”‚  3. ~/.profile       โ† fallback              โ”‚
โ”‚                                              โ”‚
โ”‚  Only ONE of these is read.                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

System-Wide Layer

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  /etc/profile                                โ”‚
โ”‚  sources /etc/profile.d/*.sh                 โ”‚
โ”‚         โ”‚                                    โ”‚
โ”‚         โ–ผ                                    โ”‚
โ”‚  /etc/profile.d/java.sh                      โ”‚
โ”‚  /etc/profile.d/node.sh                      โ”‚
โ”‚  /etc/profile.d/custom.sh                    โ”‚
โ”‚         โ”‚                                    โ”‚
โ”‚         โ–ผ                                    โ”‚
โ”‚  All users get these variables               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Login shell files~/.bash_profile, ~/.bash_login, ~/.profile (first found)
Interactive non-login file~/.bashrc
System-wide login/etc/profile
System-wide drop-in/etc/profile.d/*.sh
Bridge snippetif [ -f ~/.bashrc ]; then . ~/.bashrc; fi
~/.profile purposeEnvironment variables, inherited settings
~/.bashrc purposeAliases, functions, prompt
Shell type checkecho $0 (leading - = login)

Key takeaways:

  • Bash reads different files depending on shell type. Login shells read ~/.bash_profile/~/.bash_login/~/.profile; interactive non-login shells read ~/.bashrc .
  • Only the first login file found is read. If ~/.bash_profile exists, ~/.profile is ignored entirely .
  • .profile is for environment variables; .bashrc is for interactive settings. PATH and exported variables go in .profile; aliases and functions go in .bashrc .
  • The bridge makes login shells read .bashrc. Add if [ -f ~/.bashrc ]; then . ~/.bashrc; fi to .profile or .bash_profile .
  • /etc/profile.d/ is for system-wide variables. Files there are sourced by /etc/profile for all users .
  • Non-interactive shells read nothing. Scripts and cron jobs do not read these files; they inherit only exported environment variables.

Remember: The shell configuration file that runs depends on how the shell was started. A login shell reads /etc/profile and then one of ~/.bash_profile, ~/.bash_login, or ~/.profile. An interactive non-login shell reads ~/.bashrc. The common practice is to put environment variables in .profile and interactive settings in .bashrc, then source .bashrc from .profile so that login shells get both. System-wide settings go in /etc/profile.d/. For the LFCS exam, know the order, know the precedence, and know the bridgeโ€”that is the practical difference between a configuration that works everywhere and one that works only in the terminal where you tested it.


Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!