Guide Bash-Scripting Advanced

SSH Automation

Running remote commands, copying files, and orchestrating multi-server tasks over SSH from Bash scripts, using keys instead of passwords.

3 min read

Running a Remote Command

ssh [email protected] "systemctl restart myapp"
ssh -i ~/.ssh/deploy_key [email protected] "systemctl status myapp"

# Capture remote command output into a local variable
UPTIME=$(ssh [email protected] "uptime -p")
echo "Remote uptime: $UPTIME"

Running Multiple Commands Remotely

ssh [email protected] << 'EOF'
cd /opt/myapp
git pull
npm install
systemctl restart myapp
EOF

The single-quoted 'EOF' prevents the LOCAL shell from expanding variables inside the heredoc — everything is sent to the remote shell literally, which then does its own expansion there.

Non-Interactive SSH (Keys, Not Passwords)

ssh-keygen -t ed25519 -f ~/.ssh/deploy_key -N ""     # generate a key with no passphrase, for automation
ssh-copy-id -i ~/.ssh/deploy_key.pub [email protected]   # install the public key on the target

ssh -i ~/.ssh/deploy_key -o BatchMode=yes [email protected] "echo connected"

BatchMode=yes makes SSH fail immediately instead of hanging on a password prompt — essential for unattended scripts, since a stray prompt will hang a CI job indefinitely.

Avoiding Host Key Prompts in Automation

ssh -o StrictHostKeyChecking=accept-new [email protected] "hostname"

accept-new trusts a host on first connection but still verifies it hasn’t changed on subsequent connections — safer than fully disabling host key checking (StrictHostKeyChecking=no), which removes protection against man-in-the-middle attacks entirely.

Deploying to Multiple Servers

#!/usr/bin/env bash
set -euo pipefail

SERVERS=("web1.example.com" "web2.example.com" "web3.example.com")
SSH_KEY="$HOME/.ssh/deploy_key"

for server in "${SERVERS[@]}"; do
    echo "Deploying to $server..."
    ssh -i "$SSH_KEY" -o BatchMode=yes "deploy@$server" << 'EOF'
        set -e
        cd /opt/myapp
        git pull
        systemctl restart myapp
        sleep 3
        systemctl is-active --quiet myapp
EOF
    echo "$server: OK"
done
echo "Deployed to all ${#SERVERS[@]} servers"

Copying Files: scp and rsync Over SSH

scp -i ~/.ssh/deploy_key app.tar.gz [email protected]:/opt/myapp/
scp -i ~/.ssh/deploy_key [email protected]:/var/log/app.log ./remote-app.log

rsync -avz -e "ssh -i ~/.ssh/deploy_key" ./dist/ [email protected]:/opt/myapp/

rsync is generally preferred over repeated scp calls for anything beyond a single file — it only transfers differences and is resumable.

SSH ProxyJump (Bastion Hosts)

ssh -J bastion.example.com [email protected] "hostname"

# Or configured persistently in ~/.ssh/config:
# Host internal-server
#     HostName 10.0.1.50
#     ProxyJump bastion.example.com
#     User deploy

Checking Remote Command Success

if ssh -o BatchMode=yes [email protected] "systemctl is-active --quiet myapp"; then
    echo "myapp is running on web1"
else
    echo "myapp is NOT running on web1" >&2
    exit 1
fi

The exit status of the SSH command reflects the remote command’s exit status, not just whether the connection succeeded — this makes if ssh ... a reliable way to check remote conditions.

Production Considerations

  • Always use BatchMode=yes (or -o BatchMode=yes) in automated scripts — without it, an SSH prompt for a password or host key confirmation will silently hang a CI job or cron script.
  • Never disable host key checking entirely (StrictHostKeyChecking=no) in production automation — prefer accept-new, which still protects against a host key changing unexpectedly after the first connection.
  • Use dedicated, narrowly-scoped SSH keys per automation purpose (not a personal key), and rotate/revoke them independently of any human’s credentials.

Quick Interview Answer

“Bash automates SSH by running remote commands directly (ssh host \"command\") or piping a heredoc of commands to a remote shell. For unattended scripts, BatchMode=yes prevents hanging on a password prompt, and StrictHostKeyChecking=accept-new avoids interactive host-key confirmation while still protecting against a host key changing later. Deploying to multiple servers is typically a for loop over an array of hostnames, each with its own SSH call, with per-server success/failure tracked explicitly.”

Common Mistakes

  • Forgetting BatchMode=yes, causing an automated script to hang indefinitely on an unexpected SSH prompt.
  • Disabling host key checking entirely instead of using accept-new, removing meaningful protection against connection hijacking.
  • Using unquoted heredocs (<< EOF instead of << 'EOF') when local variable expansion inside the remote command block wasn’t intended.

Add More Questions to This Guide

Know a question that should be here? Share it and help the community!

Open Google Form