13.3. Remote Execution with SSH#
13.3.1. Common Pitfalls#
1. Hardcoding passwords or credentials
# Bad: Password in script (security risk)
sshpass -p 'password' ssh user@remote 'ls'
# Good: Use SSH keys
ssh -i ~/.ssh/key user@remote 'ls'
2. Not handling remote command failures
# Bad: Continues even if remote command fails
ssh user@remote 'systemctl restart nginx'
echo "Restart complete"
# Good: Check exit code
if ssh user@remote 'systemctl restart nginx'; then
echo "✓ Restart successful"
else
echo "✗ Restart failed" >&2
exit 1
fi
3. Local command expansion breaking remote execution
# Bad: Variables expanded locally, not remotely
ssh user@remote 'echo $HOSTNAME' # Expands $HOSTNAME locally
# Good: Escape or use single quotes
ssh user@remote 'echo $HOSTNAME' # Expands remotely
ssh user@remote "echo \$HOSTNAME" # Escapes variable
4. Timeout issues with slow operations
# Bad: SSH times out during long operation
ssh user@remote 'long_running_task'
# Better: Use timeout and nohup
ssh -o ConnectTimeout=10 user@remote 'nohup long_task > /tmp/output.log 2>&1 &'
# Check result later:
ssh user@remote 'cat /tmp/output.log'
5. Not using SSH multiplexing for efficiency
# Bad: Each command creates new SSH connection
for i in {1..10}; do
ssh user@remote 'echo "task $i"'
done
# Creates 10 connections
# Good: Reuse connection
cat > ~/.ssh/config << 'EOF'
Host *
ControlMaster auto
ControlPath ~/.ssh/control-%C
ControlPersist 600
EOF
# Now all commands use same connection
13.3.2. SSH Tunneling and Advanced Features#
13.3.2.1. Port Forwarding and Tunneling#
#!/bin/bash
# Local port forward: access remote service locally
ssh -L 3306:localhost:3306 user@remote.com -N
# Local port 3306 → remote localhost:3306
# -N: don't execute command (just tunnel)
# Background tunnel
ssh -L 5432:db-server:5432 user@gateway.com -N &
PID=$!
# Use local connection
psql -h localhost -U postgres
# Later: kill $PID
# Remote port forward: expose local service remotely
ssh -R 8080:localhost:8080 user@remote.com -N
# Remote can access http://remote.com:8080 → local:8080
# SOCKS proxy (dynamic port forward)
ssh -D 1080 user@jump-host.com -N
# Use remote as SOCKS proxy:
curl --socks5 localhost:1080 https://internal.example.com
# Reverse tunnel (for home system behind NAT)
ssh -R 2222:localhost:22 user@public-server.com -N
# From public-server: ssh -p 2222 localhost connects to home system
13.3.2.2. Agent Forwarding and Jump Hosts#
#!/bin/bash
# SSH agent forwarding (use local keys on remote)
ssh -A user@jump-host.com
# Now can ssh from jump-host using your local keys
# Jump host (bastion) configuration
ssh -J user@bastion.com user@internal-server.com
# Multiple hops
ssh -J user1@bastion1.com,user2@internal-bastion.com user@target-server.com
# In SSH config
cat > ~/.ssh/config << 'EOF'
Host internal-server
ProxyJump bastion.com
User deploy
HostName 10.0.1.50
EOF
# Usage:
ssh internal-server 'uptime'
13.3.3. SSH Keys and Passwordless Authentication#
SSH keys enable automated, secure authentication without passwords.
13.3.3.1. Generate and Configure SSH Keys#
#!/bin/bash
# Generate SSH key pair
ssh-keygen -t rsa -b 4096 -f ~/.ssh/backup_key -N ""
# -t rsa: key type
# -b 4096: key size
# -f: output file
# -N "": no passphrase
# Generate with passphrase (more secure)
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -C "my@email.com"
# Copy public key to remote host
ssh-copy-id -i ~/.ssh/backup_key.pub user@remote.com
# Manual key installation
cat ~/.ssh/backup_key.pub | ssh user@remote.com 'mkdir -p .ssh && cat >> .ssh/authorized_keys'
# Restrict key permissions (required for security)
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_rsa
chmod 600 ~/.ssh/authorized_keys
13.3.3.2. SSH Configuration for Automation#
#!/bin/bash
# Create SSH config file for easier connections
cat > ~/.ssh/config << 'EOF'
Host backup-server
HostName backup.example.com
User backup
IdentityFile ~/.ssh/backup_key
Port 2222
ConnectTimeout 5
Host prod-*
HostName %h.example.com
User deploy
IdentityFile ~/.ssh/deploy_key
StrictHostKeyChecking no
UserKnownHostsFile=/dev/null
EOF
# Usage:
ssh backup-server 'df -h'
ssh prod-web1 'systemctl status nginx'
# Automated deployment with keys
deploy_to_server() {
local server=$1
local app_path=$2
ssh "$server" "cd $app_path && git pull && npm install && pm2 restart app"
}
deploy_to_server prod-api /home/deploy/api
13.3.4. Running Commands on Remote Hosts#
SSH allows executing commands on remote systems securely, essential for administration and automation.
13.3.4.1. Basic SSH Command Execution#
#!/bin/bash
# Execute single command
ssh user@remote.com 'ls -la /home'
# Execute command with arguments
ssh user@remote.com 'df -h'
# Multiple commands
ssh user@remote.com 'pwd; whoami; date'
# Command with pipes (use quotes to prevent local interpretation)
ssh user@remote.com 'cat /var/log/syslog | grep error | wc -l'
# Use specific port
ssh -p 2222 user@remote.com 'systemctl status nginx'
# Use specific SSH key
ssh -i ~/.ssh/id_rsa_backup user@remote.com 'uptime'
# Verbose output for debugging
ssh -v user@remote.com 'echo "test"'
# Suppress warnings
ssh -q user@remote.com 'whoami'
13.3.4.2. SSH with Input/Output#
#!/bin/bash
# Capture remote output
remote_uptime=$(ssh user@remote.com 'uptime')
echo "Remote system uptime: $remote_uptime"
# Pipe local data to remote command
cat local_file.txt | ssh user@remote.com 'cat > remote_file.txt'
# Remote to local piping
ssh user@remote.com 'cat /var/log/syslog' | grep 'error' | wc -l
# Execute script stored locally on remote
ssh user@remote.com 'bash -s' < local_script.sh
# Pass arguments to remote script
ssh user@remote.com 'bash -s arg1 arg2' < local_script.sh
# Interactive shell (for interactive programs)
ssh -t user@remote.com 'sudo bash'
13.3.4.3. Practical Command Execution#
#!/bin/bash
# Check multiple servers' disk usage
for server in server1 server2 server3; do
echo "=== $server ==="
ssh user@$server 'df -h | grep -v loop'
done
# Execute with error handling
remote_command() {
local host=$1
local cmd=$2
if ssh -o ConnectTimeout=5 "user@$host" "$cmd"; then
echo "✓ Success on $host"
else
echo "✗ Failed on $host" >&2
return 1
fi
}
remote_command "webserver1" 'systemctl status nginx'
# Parallel execution on multiple hosts
hosts=("server1" "server2" "server3")
for host in "${hosts[@]}"; do
ssh "user@$host" 'sudo systemctl restart app' &
done
wait
echo "Restart complete on all servers"