One-off remote commands and pipes over SSH are the backbone of scripts, backups and quick checks.
Give ssh a command after the host and it runs that command instead of an interactive shell, then exits with the command's exit status:
ssh web1 'df -h /' prints the disk usage and returns.
Quoting matters. The command string is sent to the remote shell, so quote it to stop your local shell from expanding things: ssh web1 'echo $HOME' prints the remote home, while ssh web1 "echo $HOME" prints your local one.
Standard input and output are connected through the tunnel, which makes ssh a pipe:
ssh db 'pg_dump shop' > shop.sqlstreams a dump to your machine.tar czf - project | ssh backup 'cat > project.tgz'sends an archive the other way.ssh web1 'journalctl -u nginx -n 100' | grep 500filters remote logs locally.
When you pass a command, ssh does not allocate a terminal, which is right for pipes. Interactive programs (top, sudo asking for a password, vim, tmux) need one: add -t.
Exit status propagates, so scripts can use it: ssh web1 'systemctl is-active nginx' || alert. For running the same command on many hosts, a simple loop works; tools like Ansible build on exactly this.
Example
ssh web1 'uptime' # runs remotely, prints, exits
ssh web1 'echo $HOME' # /home/alice (remote)
ssh web1 "echo $HOME" # expanded locally first!
ssh -t web1 'sudo systemctl restart nginx' # needs a TTY for sudo prompt
ssh db 'pg_dump shop | gzip' > shop.sql.gz # stream remote -> local
tar czf - src | ssh backup 'cat > src.tgz' # stream local -> remote
for h in web1 web2; do ssh "$h" 'hostname; uptime'; done
ssh web1 'test -f /etc/maintenance' && echo "in maintenance"Remember
- ssh host 'cmd' returns the command's output and exit status
- Single-quote the command so the remote shell expands it
- Add -t for interactive programs; leave it off for pipes
Watch out for
- Using double quotes and getting local variable expansion instead of remote.