SSH automation is the backbone of fleet management. Whether deploying to 3 servers or 300, the same patterns apply: key-based auth, non-interactive flags, and parallel execution via background jobs. Master these and you can drive any number of remote servers from a single script.
1
SSH key setup for automation
BASH
# ── Generate key pair for automation ─────────────────────
ssh-keygen -t ed25519 -C "deploy-bot@prod" \
-f ~/.ssh/deploy_key -N "" # no passphrase
# ── Copy public key to remote server ─────────────────────
ssh-copy-id -i ~/.ssh/deploy_key.pub vriddh@prod-01
ssh-copy-id -i ~/.ssh/deploy_key.pub -p 2222 vriddh@prod-02
# ── ~/.ssh/config — per-host settings ────────────────────
# Host prod-*
# User vriddh
# IdentityFile ~/.ssh/deploy_key
# StrictHostKeyChecking no
# ConnectTimeout 10
# ServerAliveInterval 60
#
# Host prod-01
# HostName 10.0.1.10
#
# Host prod-db-01
# HostName 10.0.1.20
# ── SSH multiplexing — reuse connection ───────────────────
# Host *
# ControlMaster auto
# ControlPath ~/.ssh/mux/%r@%h:%p
# ControlPersist 60s
# Subsequent SSH connections to same host reuse the socket
2
Remote command execution
BASH
# ── Single command ────────────────────────────────────────
ssh vriddh@prod-01 "df -h"
ssh vriddh@prod-01 "uptime && free -h"
# ── Non-interactive (for scripts) ────────────────────────
ssh -o BatchMode=yes \
-o ConnectTimeout=10 \
-o StrictHostKeyChecking=no \
vriddh@prod-01 "systemctl status nginx"
# ── Run local script on remote ────────────────────────────
ssh vriddh@prod-01 "bash -s" < local_script.sh
# ── Multi-line commands via here-doc ─────────────────────
ssh vriddh@prod-01 << 'REMOTE'
set -e
echo "Host: $(hostname)"
cd /opt/myapp
git pull origin main
systemctl restart myapp
echo "Deploy complete"
REMOTE
# ── Capture remote output ─────────────────────────────────
disk=$(ssh vriddh@prod-01 "df -h / | awk 'NR==2{print \$5}'")
echo "prod-01 disk: ${disk}"
3
Parallel multi-server deployment
BASH
#!/usr/bin/env bash
# deploy_all.sh — Deploy to all servers in parallel
set -euo pipefail
SERVERS=(web-01 web-02 web-03 web-04)
DEPLOY_SCRIPT=/opt/myapp/scripts/deploy.sh
deploy_one() {
local host="${1}"
local log="/tmp/deploy_${host}.log"
echo " [${host}] Starting..."
if ssh -o BatchMode=yes -o ConnectTimeout=10 \
"vriddh@${host}" "bash -s" \
< "${DEPLOY_SCRIPT}" >> "${log}" 2>&1; then
echo " [${host}] ✔ Success"
else
echo " [${host}] ✘ Failed — see ${log}" >&2
return 1
fi
}
echo "Deploying to ${#SERVERS[@]} servers..."
pids=()
for srv in "${SERVERS[@]}"; do
deploy_one "${srv}" &
pids+=($!)
done
failed=0
for pid in "${pids[@]}"; do
wait "${pid}" || (( failed++ ))
done
(( failed > 0 )) && { echo "FAILED: ${failed} server(s)" >&2; exit 1; }
echo "All servers deployed successfully"
4
File transfer — scp and rsync over SSH
BASH
# ── scp — simple transfers ────────────────────────────────
scp config.env vriddh@prod-01:/etc/myapp/
scp vriddh@prod-01:/var/log/myapp/app.log ./logs/
scp -r ./dist/ vriddh@prod-01:/opt/myapp/
# ── rsync — preferred for large/repeated transfers ────────
rsync -avz -e ssh ./app/ vriddh@prod-01:/opt/myapp/
rsync -avz --delete ./app/ vriddh@prod-01:/opt/myapp/
# ── Collect logs from all servers in parallel ─────────────
LOG_DIR=/tmp/server_logs
mkdir -p "${LOG_DIR}"
for srv in web-01 web-02 web-03; do
rsync -az "vriddh@${srv}:/var/log/myapp/app.log" \
"${LOG_DIR}/${srv}_app.log" &
done
wait
echo "Logs collected to ${LOG_DIR}"
vriddh@bastion:~/scripts$./deploy_all.sh
Deploying to 4 servers...
[web-01] Starting...
[web-02] Starting...
[web-03] Starting...
[web-04] Starting...
[web-01] ✔ Success
[web-02] ✔ Success
[web-03] ✘ Failed — see /tmp/deploy_web-03.log
[web-04] ✔ Success
FAILED: 1 server(s)
█
✔ SSH automation rules — Always use key-based auth — never passwords in scripts. Always use
-o BatchMode=yes to prevent interactive prompts hanging scripts. Always set -o ConnectTimeout=10 to fail fast. Use rsync over scp for large or repeated transfers. Enable SSH multiplexing (ControlMaster auto) when running many SSH commands to the same host.