Why ~/.bashrc Isn’t Sourced in SSH Non-Interactive Shells and How to Force Load It


1 views

Many developers assume ~/.bashrc is always sourced, but SSH command execution presents a special case. When you run:

ssh user@host 'command'

The shell operates in non-interactive mode, and most distributions configure bash to skip .bashrc loading in this scenario.

To test whether your .bashrc is being sourced during SSH commands:

# Add this to your .bashrc
echo "Bashrc loaded" > /tmp/bashrc_test

# Then test with:
ssh user@host 'cat /tmp/bashrc_test'
# No output means .bashrc wasn't sourced

In Ubuntu/Debian systems, the default .bashrc starts with this guard clause:

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

This explains why your touch command never executed.

Option 1: Force .bashrc loading by modifying your SSH command:

ssh -t user@host 'bash --rcfile ~/.bashrc -ci "your_command"'

Option 2: Create a dedicated environment file (recommended):

# Create ~/.bashenv
cp ~/.bashrc ~/.bashenv
# Remove the interactive guard clause from .bashenv

# Then use:
ssh user@host 'source ~/.bashenv && your_command'

Option 3: For system-wide changes (use with caution):

# Edit /etc/ssh/sshd_config
AcceptEnv BASH_ENV
# Then set BASH_ENV in your local SSH config
  • Keep environment setup separate from interactive configurations
  • Use absolute paths in your automation scripts
  • Test with -vvv SSH flag for debugging
  • Consider using ~/.ssh/environment for simple variable needs

Here's how to properly structure a deployment command:

ssh deploy@production '
    source ~/.deployenv && \
    cd /var/www/app && \
    git pull && \
    npm install && \
    systemctl restart appservice
'

Where ~/.deployenv contains only the necessary environment setup.


When you execute commands via SSH like ssh user@host 'command', the remote shell behaves differently from interactive sessions. Here's what actually happens:

# This command creates a non-interactive shell
ssh user@example.com 'ls -l /tmp'

# Check if .bashrc was sourced (it typically isn't)
ssh user@example.com 'echo $BASH_ENV'

Bash has specific rules for which startup files it reads:

  • Interactive login shells: Reads /etc/profile, ~/.bash_profile, ~/.bash_login, ~/.profile
  • Interactive non-login shells: Reads ~/.bashrc
  • Non-interactive shells: Only reads file specified in $BASH_ENV

Here are three reliable approaches:

Option 1: Force .bashrc Loading via BASH_ENV

# In your remote ~/.bash_profile or ~/.profile:
if [ -n "$BASH_VERSION" ]; then
    export BASH_ENV=~/.bashrc
fi

Option 2: Use SSH ForcedCommand

# In ~/.ssh/authorized_keys:
command=". ~/.bashrc; $SSH_ORIGINAL_COMMAND" ssh-rsa AAAAB3Nza...

Option 3: Explicitly Source .bashrc

# Modify your SSH command:
ssh user@example.com '. ~/.bashrc; your_command'

To verify your solution works:

# Test script
ssh user@example.com 'set -x; . ~/.bashrc; env > /tmp/ssh_env_test'
ssh user@example.com 'cat /tmp/ssh_env_test'

The behavior might vary slightly between distributions:

# For Ubuntu/Debian systems checking:
if [ -f /etc/debian_version ]; then
    # Additional Debian-specific logic
fi

For production environments, I recommend creating a separate environment setup file and using BASH_ENV:

# Create ~/.bash_env
echo 'export PATH=$PATH:/custom/path' > ~/.bash_env
chmod 644 ~/.bash_env

# In ~/.bash_profile
export BASH_ENV=~/.bash_env