| name | ssh-ops |
| description | SSH operations for remote server management, tunneling, and proxy-based connections. Use when connecting to remote servers, running remote commands, setting up SSH tunnels, port forwarding, managing jump hosts/bastions, or executing kubectl/k9s on remote clusters. Triggers on SSH, remote, tunnel, bastion, jump host, port forward, or remote kubernetes access. |
SSH Operations
Connection Patterns
Direct Connection
ssh user@host
ssh -i ~/.ssh/id_rsa user@host
ssh -p 2222 user@host
ssh user@host "command"
ssh user@host << 'EOF'
cd /app
ls -la
cat config.yaml
EOF
Through Jump Host / Bastion
ssh -J jumpuser@bastion user@target
ssh -J jump1@bastion1,jump2@bastion2 user@target
ssh -o ProxyCommand="ssh -W %h:%p jumpuser@bastion" user@target
ssh -J jumpuser@bastion user@target "kubectl get pods"
SSH Config for Persistent Setup
Host bastion
HostName bastion.example.com
User jumpuser
IdentityFile ~/.ssh/bastion_key
Host target
HostName 10.0.1.50
User admin
ProxyJump bastion
IdentityFile ~/.ssh/target_key
Host k8s-*
User ubuntu
ProxyJump bastion
IdentityFile ~/.ssh/k8s_key
Host k8s-prod
HostName 10.0.1.100
Host k8s-staging
HostName 10.0.2.100
Then simply: ssh target or ssh k8s-prod "kubectl get pods"
Port Forwarding / Tunneling
Local Port Forward (access remote service locally)
ssh -L 8080:localhost:80 user@host
ssh -L 8080:internal-service:80 user@bastion
ssh -L 6443:kubernetes.default:443 user@bastion
ssh -L 5432:db.internal:5432 user@bastion
ssh -L 8080:web:80 -L 5432:db:5432 -L 6379:redis:6379 user@bastion
Remote Port Forward (expose local service remotely)
ssh -R 8080:localhost:3000 user@host
ssh -R 0.0.0.0:8080:localhost:3000 user@host
Dynamic SOCKS Proxy
ssh -D 1080 user@bastion
curl --socks5 localhost:1080 http://internal-service/api
HTTPS_PROXY=socks5://localhost:1080 kubectl get pods
Tunnel in Background
ssh -f -N -L 8080:service:80 user@host
autossh -M 0 -f -N -L 8080:service:80 user@host \
-o "ServerAliveInterval 30" \
-o "ServerAliveCountMax 3"
pkill -f "ssh.*-L 8080"
ps aux | grep "ssh.*-L" | grep -v grep
kill <pid>
Remote Kubernetes Access
Through SSH Tunnel
ssh -L 6443:kubernetes.default.svc:443 user@bastion -N &
TUNNEL_PID=$!
kubectl config set-cluster tunnel-cluster \
--server=https://localhost:6443 \
--insecure-skip-tls-verify=true
kubectl config set-context tunnel-context \
--cluster=tunnel-cluster \
--user=admin
kubectl config use-context tunnel-context
kubectl get pods -A
kill $TUNNEL_PID
Execute kubectl Remotely
ssh user@k8s-master "kubectl get pods -n production"
ssh user@k8s-master "kubectl --context=prod get pods"
ssh -t user@k8s-master "kubectl get pods -w"
ssh user@k8s-master "kubectl logs -l app=myapp --tail=100"
ssh user@k8s-master << 'EOF'
kubectl get pods -n production
kubectl get events -n production --sort-by='.lastTimestamp' | tail -20
kubectl top pods -n production
EOF
Interactive k9s Remotely
ssh -t user@k8s-master "k9s"
ssh -t user@k8s-master "k9s -n production"
ssh -t user@k8s-master "k9s --readonly"
ssh -t user@k8s-master "k9s --context production"
Remote Script Execution
Run Local Script Remotely
ssh user@host 'bash -s' < local-script.sh
ssh user@host 'bash -s' < local-script.sh arg1 arg2
ssh user@host << 'SCRIPT'
set -euo pipefail
echo "Running on $(hostname)"
kubectl get pods -n production
SCRIPT
Deploy and Execute
scp deploy.sh user@host:/tmp/
ssh user@host "chmod +x /tmp/deploy.sh && /tmp/deploy.sh"
ssh user@host << 'EOF'
cat > /tmp/check.sh << 'INNER'
kubectl get pods -A | grep -v Running
kubectl get events -A --field-selector type=Warning | tail -20
INNER
chmod +x /tmp/check.sh
/tmp/check.sh
EOF
File Transfer
SCP
scp file.yaml user@host:/path/
scp user@host:/path/file.yaml ./
scp -J jumpuser@bastion file.yaml user@target:/path/
scp -r ./manifests user@host:/deploy/
scp -i ~/.ssh/mykey file.yaml user@host:/path/
Rsync over SSH
rsync -avz -e ssh ./local/ user@host:/remote/
rsync -avz -e "ssh -J jumpuser@bastion" ./local/ user@target:/remote/
rsync -avzn -e ssh ./local/ user@host:/remote/
rsync -avz --delete -e ssh ./local/ user@host:/remote/
Connection Management
Keep Connection Alive
ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 user@host
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
TCPKeepAlive yes
Connection Multiplexing (reuse connections)
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
mkdir -p ~/.ssh/sockets
First connection creates socket, subsequent connections reuse it (faster).
Check Connection
ssh -o ConnectTimeout=5 -o BatchMode=yes user@host echo "OK" 2>/dev/null && echo "Connected" || echo "Failed"
ssh -vvv user@host
ssh -J jumpuser@bastion -o ConnectTimeout=10 user@target echo "OK"
Automation Scripts
Tunnel Manager
#!/usr/bin/env bash
ACTION="${1:-status}"
TUNNEL_NAME="${2:-default}"
BASTION="user@bastion.example.com"
PIDFILE="/tmp/ssh-tunnel-${TUNNEL_NAME}.pid"
case "$ACTION" in
start)
if [[ -f "$PIDFILE" ]] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
echo "Tunnel already running (PID: $(cat "$PIDFILE"))"
exit 0
fi
case "$TUNNEL_NAME" in
k8s)
ssh -f -N -L 6443:kubernetes:443 "$BASTION" \
-o ServerAliveInterval=30 \
-o ExitOnForwardFailure=yes
;;
db)
ssh -f -N -L 5432:postgres.internal:5432 "$BASTION" \
-o ServerAliveInterval=30 \
-o ExitOnForwardFailure=yes
;;
*)
echo "Unknown tunnel: $TUNNEL_NAME"
exit 1
;;
esac
pgrep -f "ssh.*-L.*$TUNNEL_NAME" > "$PIDFILE"
echo "Tunnel $TUNNEL_NAME started (PID: $(cat "$PIDFILE"))"
;;
stop)
if [[ -f "$PIDFILE" ]]; then
kill "$(cat "$PIDFILE")" 2>/dev/null
rm "$PIDFILE"
echo "Tunnel $TUNNEL_NAME stopped"
else
pkill -f "ssh.*-L.*$TUNNEL_NAME"
echo "Tunnel $TUNNEL_NAME stopped (by pattern)"
fi
;;
status)
if [[ -f "$PIDFILE" ]] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
echo "Tunnel $TUNNEL_NAME: running (PID: $(cat "$PIDFILE"))"
else
echo "Tunnel $TUNNEL_NAME: not running"
fi
;;
*)
echo "Usage: $0 {start|stop|status} [tunnel-name]"
echo "Tunnels: k8s, db"
;;
esac
Remote Health Check
#!/usr/bin/env bash
HOST="${1:?Usage: $0 <ssh-host> [namespace]}"
NS="${2:-default}"
echo "=== Checking $HOST ($NS namespace) ==="
ssh "$HOST" << EOF
echo "--- Pod Status ---"
kubectl get pods -n $NS -o wide
echo ""
echo "--- Non-Running Pods ---"
kubectl get pods -n $NS | grep -v Running | grep -v Completed | grep -v NAME
echo ""
echo "--- Recent Events ---"
kubectl get events -n $NS --sort-by='.lastTimestamp' | tail -15
echo ""
echo "--- Resource Usage ---"
kubectl top pods -n $NS 2>/dev/null || echo "Metrics unavailable"
EOF
Batch Remote Execution
#!/usr/bin/env bash
HOSTS=("k8s-prod" "k8s-staging" "k8s-dev")
CMD="${1:?Usage: $0 'command'}"
for host in "${HOSTS[@]}"; do
echo "=== $host ==="
ssh -o ConnectTimeout=10 "$host" "$CMD" 2>&1 || echo "FAILED: $host"
echo ""
done
Security Best Practices
Key Management
ssh-keygen -t ed25519 -C "description" -f ~/.ssh/mykey
ssh-copy-id -i ~/.ssh/mykey.pub user@host
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_*
chmod 644 ~/.ssh/*.pub
chmod 600 ~/.ssh/config
Agent Forwarding (careful!)
ssh -A user@bastion
ssh target-server
ssh -J bastion target
Restrict Commands per Key
In remote ~/.ssh/authorized_keys:
command="kubectl get pods",no-port-forwarding,no-X11-forwarding ssh-ed25519 AAAA... readonly-key
Troubleshooting
Connection Issues
ssh -vvv user@host
ssh -v user@host 2>&1 | grep "Offering"
ssh -i ~/.ssh/specific_key -v user@host
ssh -o PreferredAuthentications=publickey user@host
Tunnel Issues
lsof -i :8080
netstat -an | grep 8080
nc -zv localhost 8080
ssh -v -L 8080:target:80 user@bastion
Permission Denied
Common causes:
- Wrong key permissions:
chmod 600 ~/.ssh/id_*
- Wrong .ssh dir permissions:
chmod 700 ~/.ssh
- Key not in agent:
ssh-add ~/.ssh/mykey
- Server doesn't have public key:
ssh-copy-id
- SELinux/firewall blocking