| 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 |
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:
/etc/profilewhen present.- 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:
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fiStock ~/.bashrc on the same system pulls in the system-wide file:
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fiRemote 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.bashrcas their system-wide interactive Bash configuration. The packaged file describes that role explicitly. Stock~/.bashrcon those systems is a separate user file with its own interactivity guard — it does not normally source/etc/bash.bashrcthe way RHEL templates wire/etc/bashrc. - RHEL, Fedora, Rocky, and AlmaLinux commonly have the user's
~/.bashrcsource/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:
printf 'echo "BASH_ENV loaded"\n' > /tmp/bash_env_test.shprintf writes the one-line helper script Bash will source:
BASH_ENV=/tmp/bash_env_test.sh bash -c 'echo child_start'BASH_ENV loaded
child_startA 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:
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
0 2 * * * /home/user/bin/backup.shPut 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:
ls -la /etc/skelOn RHEL 10.2:
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 .bashrcDebian and Ubuntu /etc/skel commonly ship ~/.profile instead of ~/.bash_profile, with a block that sources ~/.bashrc when Bash is the shell:
# if running bash
if [ -n "$BASH_VERSION" ]; then
if [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
fiCheck /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:
[[ $- == *i* ]] && echo interactive=yes || echo interactive=noOn a normal prompt this prints interactive=yes. In bash -c without -i it prints interactive=no.
Login test — exit status 0 means login shell:
shopt -q login_shell && echo login_shell=yes || echo login_shell=noCombine both in one line when debugging SSH or CI wrappers:
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
- GNU Bash manual — Bash Startup Files
- GNU Bash manual — Invoking Bash
- OpenSSH
sshd(8)manual — remote command execution through the user's shell - Linux
crontab(5)manual —SHELL, environment variables, and command execution - Debian Bash package —
/etc/bash.bashrc— system-wide interactive Bash configuration - Debian Bash package —
/etc/skeltemplates — stock.bashrcand.profilefor new users
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.

