Bash Startup Files: .bashrc vs .bash_profile vs .profile

Tested on RHEL 10.2 (Coughlan)
Package bash 5.2.26-6.el10
Applies to Linux distributions with Bash installed
Privilege Normal user; no root required for the examples
Scope Login versus non-login shells, interactive versus non-interactive Bash, startup order for .bashrc, .bash_profile, and .profile, BASH_ENV, SSH and cron behavior, distro /etc/skel patterns, and where to place PATH, aliases, functions, and exports. Does not cover full systemd user environment or display-manager session setup.
Related guides Bash shell script tutorial
Shell scripting course hub
Short on time?Beta Get the gist of this 13-minute read in seconds, then ask follow-up questions

This article compares .bashrc, .bash_profile, and .profile using the same verification commands in a terminal, so you can see which startup path Bash took for each shell mode.

Which file Bash reads depends mainly on two things: whether the shell is a login shell and whether it is interactive. Those answers decide whether your login profile chain or your per-interactive-shell rc file runs.


.bashrc vs .bash_profile vs .profile — Quick Answer

File Typical role
~/.bash_profile Bash-specific login startup. Common on RHEL-family /etc/skel templates; often ends with source ~/.bashrc.
~/.profile POSIX login file shared with sh. Default on Debian and Ubuntu skel; many copies source ~/.bashrc when $BASH_VERSION is set.
~/.bashrc Interactive Bash rc file. Bash reads it directly for interactive non-login shells; distro login templates often source it for interactive login sessions.

Bash does not read all three on every invocation. For a login shell it runs /etc/profile, then the first existing file among ~/.bash_profile, ~/.bash_login, and ~/.profile. For an interactive non-login shell it reads ~/.bashrc (unless --norc). Local non-interactive Bash normally skips ~/.bashrc and can use BASH_ENV instead. Non-interactive Bash started by a remote shell daemon such as sshd has a separate startup rule and may read ~/.bashrc.

Shell invocation Files Bash normally reads
Interactive login /etc/profile → first of ~/.bash_profile, ~/.bash_login, ~/.profile
Interactive non-login Upstream Bash: ~/.bashrc; Debian/Ubuntu also use /etc/bash.bashrc; RHEL-family ~/.bashrc commonly sources /etc/bashrc
Local non-interactive BASH_ENV, if set
Non-interactive through sshd Special remote-shell rule; Bash may read ~/.bashrc
Bash invoked as sh Different rules; not covered here

Login vs Non-Login and Interactive vs Non-Interactive Bash

Bash startup behavior depends on two independent properties:

Axis Meaning Quick check
Login Started as a session login (-l, su -, many SSH sessions) shopt -q login_shell exits 0 when true
Interactive Connected to a terminal and accepting keyboard input [[ $- == *i* ]] is true when the i flag is set

Combine them and you get four common modes:

Mode Login? Interactive? Typical example
Interactive login Yes Yes SSH session, console login, bash --login
Interactive non-login No Yes Most terminal emulator tabs, plain bash from an existing prompt
Non-interactive login Yes No bash -l -c '…' when you pass -l explicitly
Non-interactive non-login No No Script with #!/bin/bash, bash -c '…', ssh host command, many cron jobs

The official rules are in Bash Startup Files in the GNU Bash manual.


Interactive Login Shell Startup Order

For an interactive login Bash, the order is:

  1. /etc/profile when present.
  2. Exactly one user login file — the first that exists among ~/.bash_profile, ~/.bash_login, ~/.profile.

Bash does not automatically read ~/.bashrc as part of its login-file sequence. On many distributions, the selected login file explicitly sources ~/.bashrc — Bash source command explains how that differs from running a script in a child process — which is why ~/.bashrc settings also appear in login sessions.

When a login shell exits, Bash also reads ~/.bash_logout if the file exists.

On RHEL 10.2 the skeleton ~/.bash_profile shipped in /etc/skel sources ~/.bashrc so login sessions still pick up aliases and prompt settings:

text
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

Stock ~/.bashrc on the same system pulls in the system-wide file:

text
# Source global definitions
if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi

Remote text logins are usually interactive login shells. Most Linux terminal emulators start Bash as an interactive non-login shell by default, although terminal settings can be configured to launch a login shell instead. As a result, an SSH login often processes a login profile, while a normal terminal tab typically starts with ~/.bashrc.


Interactive Non-Login Shell Startup

An interactive shell that is not a login shell reads ~/.bashrc (unless you pass --norc). It does not re-run ~/.bash_profile or ~/.profile for that invocation.

Settings such as aliases, prompt configuration, and shopt options belong in ~/.bashrc because they should be available in each new interactive Bash session.

Distribution handling of system-wide interactive configuration differs:

  • Debian and Ubuntu provide /etc/bash.bashrc as their system-wide interactive Bash configuration. The packaged file describes that role explicitly. Stock ~/.bashrc on those systems is a separate user file with its own interactivity guard — it does not normally source /etc/bash.bashrc the way RHEL templates wire /etc/bashrc.
  • RHEL, Fedora, Rocky, and AlmaLinux commonly have the user's ~/.bashrc source /etc/bashrc.

Those paths are distro packaging choices, not names mandated by Bash itself. Compare /etc/skel and the top of ~/.bashrc on the system you deploy.


Non-Interactive Bash and BASH_ENV

A normal local non-interactive Bash, such as a script or bash -c, does not read ~/.bashrc. Instead, if BASH_ENV is set, Bash reads the file named by that variable before executing the script or command. Non-interactive Bash started by sshd follows a separate rule documented in the GNU manual and may read ~/.bashrc even when BASH_ENV is unset.

When BASH_ENV names a readable file, Bash reads that file before executing a non-interactive script or command:

bash
printf 'echo "BASH_ENV loaded"\n' > /tmp/bash_env_test.sh

printf writes the one-line helper script Bash will source:

bash
BASH_ENV=/tmp/bash_env_test.sh bash -c 'echo child_start'
output
BASH_ENV loaded
child_start

A normal local bash -c does not read ~/.bashrc by default.

BASH_ENV is easy to misuse. Prefer explicit source in scripts that need shared functions, or export variables from the parent process when you control the caller. For background on exports and inheritance, see Bash environment variables.


SSH Startup Behavior

Interactive login SSH

An ordinary ssh user@host session starts Bash as an interactive login shell when Bash is your login shell. Expect /etc/profile, then your login file (~/.bash_profile or ~/.profile depending on skel), and — on typical templates — a source ~/.bashrc line inside that login file.

Remote command nuance

ssh user@host command is not the same startup path as an interactive SSH login. OpenSSH normally executes the remote command through the user's configured shell with -c command — not bash -l -c command. That starts a non-interactive shell to run the command; it is not simply an interactive login session without a prompt.

When non-interactive Bash detects that it was started by a remote shell daemon such as sshd, Bash may read ~/.bashrc. The GNU Bash manual documents this exception separately from local non-interactive startup. Some distro-provided ~/.bashrc files, including common Debian/Ubuntu templates, return early when Bash is non-interactive. In that case, aliases and other interactive settings below the guard are skipped even when the file was sourced.

For one-shot SSH commands, do not rely on interactive aliases or on variables defined only in shell startup files. Set required variables explicitly, source a dedicated environment file from the command or script, or use an SSH-supported environment mechanism where appropriate. Do not assume ssh host ./script.sh sees the same aliases as your terminal tab.

SSH from another host walks through normal interactive sessions; keep the interactive-login versus remote-command distinction in mind when debugging "it works in SSH but not in ssh host ./script.sh."


Cron and Scheduled Scripts

Cron normally executes the command portion of a crontab with /bin/sh, unless SHELL is set to another shell in the crontab — see Bash vs sh when a job needs Bash syntax beyond POSIX sh. It does not start an interactive Bash session or automatically read your personal Bash startup files. Cron also sets HOME and LOGNAME; the environment is not whatever your interactive shell had open at the time.

When a job works manually but fails from cron, set the shell and PATH explicitly at the top of the crontab:

text
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin

0 2 * * * /home/user/bin/backup.sh

Put any other variables the script needs in the crontab, inside the script, or in a wrapper file you source from the script — do not assume ~/.bashrc ran.


Ubuntu/Debian vs RHEL-Family Defaults

/etc/skel templates

New home directories copy from /etc/skel when useradd creates an account (unless overridden). The skeleton tells you which login file your distro expects:

bash
ls -la /etc/skel

On RHEL 10.2:

output
total 24
drwxr-xr-x.   3 root root   78 Aug  3 11:36 .
drwxr-xr-x. 143 root root 8192 Sep  7 13:56 ..
-rw-r--r--.   1 root root   18 Oct 29  2024 .bash_logout
-rw-r--r--.   1 root root  144 Oct 29  2024 .bash_profile
-rw-r--r--.   1 root root  522 Oct 29  2024 .bashrc

Debian and Ubuntu /etc/skel commonly ship ~/.profile instead of ~/.bash_profile, with a block that sources ~/.bashrc when Bash is the shell:

text
# if running bash
if [ -n "$BASH_VERSION" ]; then
    if [ -f "$HOME/.bashrc" ]; then
        . "$HOME/.bashrc"
    fi
fi

Check /etc/skel on the target distribution because the default startup files differ between distro families.

Hidden dotfiles are easy to miss; show hidden files when you audit a home directory.

System-wide rc differences

Distro family Common system-wide interactive rc Notes
Debian / Ubuntu /etc/bash.bashrc Packaged system-wide interactive Bash configuration
RHEL / Fedora / Rocky / Alma /etc/bashrc Sourced from stock ~/.bashrc on RHEL 10.2

There is no /etc/bash.bashrc on this RHEL host; looking for the wrong filename is a common portability mistake.


Where Should You Put PATH, Aliases, Functions and Exports?

Interactive rc files own prompt and alias settings; login files own session-wide exports. Define reusable logic in a Bash functions library file and source it only when a script or session needs that code.

What you are setting Put it here Why
Environment variables initialized at login (PATH, LANG, tool *_HOME) ~/.bash_profile or ~/.profile (whichever your skel uses), or /etc/profile.d/*.sh for machine policy Login startup files run when a login shell starts; exported values are inherited by child processes
alias, PS1, bind, shopt, completion hooks ~/.bashrc, guarded for interactive shells Every new interactive tab reads rc, not the login file
Shared shell functions for scripts A file you source explicitly, or BASH_ENV for controlled non-interactive use Do not assume every script reads ~/.bashrc
Commands that should run once for each shell login Login file Should not rerun for every terminal tab

Put session-wide exported variables in the login file — Bash variables covers assignment and ${name} expansion before you export — and make sure your distro's login file sources ~/.bashrc if you keep shared Bash-specific environment setup there.

If you create ~/.bash_profile on a Debian system that previously relied on ~/.profile, Bash stops reading ~/.profile automatically. Either move your exports into ~/.bash_profile or add source ~/.profile yourself.

Avoid repeatedly appending the same directory to PATH from files that source each other; first understand whether ~/.bash_profile sources ~/.bashrc on your system.

GUI applications may not inherit variables from Bash startup files, so shell startup files are not a universal way to configure a desktop-session environment.


Troubleshooting: “My Variable/Alias Is Missing”

Symptom Likely cause Fix
Alias works in a terminal tab but not in ssh host cmd Remote command runs non-interactively; ~/.bashrc may be read but may return early for non-interactive shells Use the real command instead of an alias. For required environment variables, set them explicitly or source a dedicated environment file from the remote command or script
Export missing in a script Local non-interactive Bash does not read ~/.bashrc by default Move export to login file, parent shell, or source a shared file inside the script
~/.profile changes ignored after adding ~/.bash_profile First-match login rule prefers .bash_profile Merge settings or source ~/.profile from .bash_profile
Cron job cannot find a command Cron jobs often have a different PATH from your interactive shell; startup files did not run Set SHELL= and PATH= in the crontab or script
Nothing from ~/.bashrc in a local script Expected for local non-interactive Bash Use BASH_ENV deliberately or source explicitly
.bashrc edits not visible in the current shell The file runs only when a new interactive Bash starts Run source ~/.bashrc or . ~/.bashrc in the current session, or open a new terminal tab

Verification Commands

Use these checks when you are unsure which mode a shell is in.

Interactive test — true when the shell accepts interactive input:

bash
[[ $- == *i* ]] && echo interactive=yes || echo interactive=no

On a normal prompt this prints interactive=yes. In bash -c without -i it prints interactive=no.

Login test — exit status 0 means login shell:

bash
shopt -q login_shell && echo login_shell=yes || echo login_shell=no

Combine both in one line when debugging SSH or CI wrappers:

bash
echo "flags=$- login=$(shopt -q login_shell && echo yes || echo no) interactive=$([[ $- == *i* ]] && echo yes || echo no)"

Run that from your actual session (SSH, cron wrapper, or local tab) and compare the result to the tables above.


Summary

Bash classifies each invocation as login or non-login and interactive or non-interactive before it touches your dotfiles. Interactive login shells read /etc/profile, then the first existing file among ~/.bash_profile, ~/.bash_login, and ~/.profile; interactive non-login shells read ~/.bashrc. Local non-interactive Bash normally skips ~/.bashrc and can use BASH_ENV instead. When a login shell exits, Bash reads ~/.bash_logout if present. Prompt-time history -a hooks belong in ~/.bashrc for interactive shells—if commands still vanish across terminals, see fix Bash history not saving commands.

ssh user@host is usually an interactive login session. ssh user@host command runs a non-interactive shell through the user's configured shell with -c; when Bash is that shell and detects sshd, it may read ~/.bashrc under a separate GNU rule, but distro ~/.bashrc guards often skip aliases anyway. Cron runs jobs with /bin/sh by default unless the crontab sets SHELL — it does not replay your personal Bash startup chain.

On RHEL-family systems, /etc/skel commonly ships ~/.bash_profile that sources ~/.bashrc, which in turn sources /etc/bashrc. Debian and Ubuntu commonly ship ~/.profile instead of ~/.bash_profile and package /etc/bash.bashrc as system-wide interactive configuration. Put environment variables that should be inherited by the session in the appropriate login startup file, keep interactive settings such as aliases and prompt configuration in ~/.bashrc, and verify mode with shopt -q login_shell and [[ $- == *i* ]] when something is missing.


References


Frequently Asked Questions

Does .bash_profile always source .bashrc?

No. That is a common convention on RHEL-family /etc/skel templates, not a Bash rule. Stock Debian and Ubuntu ~/.profile sources ~/.bashrc for Bash logins instead. If you create a minimal ~/.bash_profile by hand without a source ~/.bashrc line, login shells can miss everything you keep only in ~/.bashrc.

Why is .profile ignored after I create .bash_profile?

Login Bash reads only the first existing file among ~/.bash_profile, ~/.bash_login, and ~/.profile. Adding ~/.bash_profile moves ~/.profile out of the chain unless you source it yourself from the new file.

Does cron read .bashrc?

Not in the way an interactive terminal does. Cron normally runs the command with /bin/sh unless the crontab sets SHELL to another shell. It does not start an interactive Bash session or automatically read your personal startup files. Set PATH and other variables in the crontab or inside the script.

Does SSH read .bashrc?

An interactive SSH login normally reads a login profile; that profile may source ~/.bashrc. For ssh host command, Bash runs non-interactively and has a special remote-shell rule that may read ~/.bashrc. However, some stock ~/.bashrc files may return early for non-interactive shells, so aliases may still be unavailable.

How do I reload .bashrc after editing it?

Run source ~/.bashrc or . ~/.bashrc to apply changes to the current Bash session. Opening a new interactive non-login Bash also reads the file again.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)