Many developers need to customize their SSH login experience by setting environment variables or displaying welcome messages in their .bashrc. However, when these scripts output any text, it breaks SFTP connections (used by clients like FileZilla) because SFTP expects a clean protocol communication channel.
# Typical problematic .bashrc content
echo "Welcome to server $(hostname)"
export MY_VAR="development"
SFTP operates over the same SSH connection but expects a clean binary protocol. Any extraneous output from startup scripts interferes with this protocol handshake, causing:
- Connection hanging indefinitely
- "Connection closed by server with exit code 128" errors
- File transfer failures in GUI clients
Here are several tested approaches to maintain both login customization and SFTP functionality:
Method 1: Check Session Type
The most reliable method checks if the session is interactive:
# In your .bashrc
if [[ $- == *i* ]]; then
# Interactive shell code here
echo "Welcome $(whoami)"
export PATH="$PATH:/custom/path"
# Your custom prompt
PS1='[\u@\h \W]\$ '
fi
Method 2: Detect SFTP Specifically
For more precise control when using FileZilla:
# Check for SFTP environment variables
if [ -z "$SFTP_CONNECTION" ] && [ -z "$SSH_TTY" ]; then
# Regular SSH session - safe to output
echo "Loading development environment..."
source ~/env_vars.sh
fi
Method 3: Separate Configuration Files
A clean architecture approach:
# In .bashrc
[ -f ~/.bash_interactive ] && . ~/.bash_interactive
# In .bash_interactive
# Put all your interactive-only configurations here
function welcome() {
echo "=== System Information ==="
uptime
echo "=========================="
}
welcome
Verify your changes work with both SSH and SFTP:
# Test SSH connection
ssh user@server
# Test SFTP connection
sftp user@server
# Alternative test using SSH directly
ssh user@server -s sftp
For sophisticated environments where you need different behavior for different clients:
# Detect FileZilla specifically
if [ -n "$SSH_CLIENT" ] &&
! pgrep -f "FileZilla" >/dev/null 2>&1 &&
[ "$TERM" != "dumb" ]; then
# Your interactive customizations
neofetch # Example system info tool
fi
- Never put
echostatements outside conditional blocks - Avoid commands that always produce output (like
fortune) - Remember that
.bash_profilemay also be read in some configurations - Environment variables may affect behavior - test with
env -i bash --noprofile --norc
For most use cases, the interactive shell check ([[ $- == *i* ]]) provides the best balance of reliability and functionality. Combine this with proper sourcing of additional configuration files for maintainability.
When working with remote servers, we often encounter this dilemma: We want rich interactive shell experiences through SSH (custom prompts, environment variables, welcome messages) but simultaneously need clean SFTP file transfers. The fundamental issue stems from how OpenSSH handles different connection types:
SSH (interactive shell) → invokes .bashrc/.bash_profile
SFTP (file transfer) → expects pure binary protocol without shell initialization output
Common workarounds like TERM/dumb checks don't reliably work because:
- Some SFTP clients still set terminal types
- Modern shells may bypass these checks
- Environment variables can interfere
The Filezilla-specific error "Connection closed by server with exit code 128" typically indicates shell initialization output corrupted the SFTP protocol handshake.
Here's the bulletproof method I've used in production systems:
# In ~/.bashrc
if [[ $- == *i* ]] && [[ -z "$SSH_CLIENT" ]] || [[ -n "$SSH_TTY" ]]; then
# Interactive SSH session detected
echo "Welcome to $(hostname)"
export MY_VAR="custom_value"
# Other interactive-only configurations
fi
For complex setups, maintain separate configuration files:
# ~/.bashrc
if [ -n "$PS1" ]; then
# Interactive shell
if [ -n "$SSH_CONNECTION" ]; then
source ~/.bashrc_interactive_ssh
else
source ~/.bashrc_interactive_local
fi
fi
Verify your setup works with these commands:
# Test SSH interactivity
ssh user@host "echo \$MY_VAR"
# Test SFTP connectivity
sftp user@host <
For Filezilla users experiencing timeout issues:
- Go to Edit → Settings
- Select "Transfer Settings"
- Increase timeout to 60 seconds
- Check "Limit number of simultaneous connections" (set to 2)
For advanced users with server access:
# In /etc/ssh/sshd_config
Match Group sftponly
ForceCommand internal-sftp
ChrootDirectory /home/%u
PermitTunnel no
AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no