SSH Automation
Running remote commands, copying files, and orchestrating multi-server tasks over SSH from Bash scripts, using keys instead of passwords.
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 — preferaccept-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=yesprevents hanging on a password prompt, andStrictHostKeyChecking=accept-newavoids interactive host-key confirmation while still protecting against a host key changing later. Deploying to multiple servers is typically aforloop 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 (
<< EOFinstead 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