Networking and remote
ssh
Log in to a remote machine or run a command on it over an encrypted connection; also forwards ports and jumps through hosts.
ssh [OPTION...] [USER@]HOST [COMMAND [ARG...]]
ssh host opens an interactive shell on a remote machine; ssh host command runs one command
there and returns its output and exit status, as if it were a local program. Standard input,
output and errors are connected through the encrypted channel, so remote commands fit into local
pipelines in both directions.
Authentication is normally by key: ssh-keygen makes a key pair, ssh-copy-id installs
the public half on the server, and from then on no password is needed. Per-host settings
(user, port, key, jump host) belong in ~/.ssh/config, so that ssh web does what a long
command line would otherwise spell out. The same connection machinery carries scp,
rsync and git.
The examples connect to server, which in the sandbox is the same machine reached over
loopback with a key already installed; the commands work identically against a real remote
host.
Examples
Run a single command on a remote host
ssh server hostname
demo
ssh logs in, runs hostname remotely, prints its output and exits. No interactive shell
is started. (The remote here is the sandbox itself, whose host name is demo.)
Run several commands, quoted
ssh server 'cd demo && wc -l todo.txt notes.txt'
5 todo.txt 6 notes.txt 11 total
ssh joins its arguments into one string and hands it to the remote user's shell, so &&,
cd and globs work there. Quote the whole thing to stop your local shell from
interpreting it first.
Which shell expands the variable
name=laptop
ssh server "echo double: $name"
ssh server 'echo single: $name'
double: laptop single:
In double quotes, $name is expanded locally before ssh sends the command, so the remote
side receives echo double: laptop. In single quotes the remote shell expands it, and the
variable does not exist there: the output is empty after single:.
Pipe data into and out of a remote command
grep ' 404 ' access.log | ssh server 'wc -l'
ssh server 'cat demo/data/hosts.txt' | sort -k2 | head -n 2
4 web01 10.0.0.11 web02 10.0.0.12
Local standard input becomes the remote command's input, and the remote output comes back on standard output. The first line counts on the remote side, the second sorts locally.
The remote exit status comes back
ssh server 'grep -q nothing-like-this demo/todo.txt'
echo "exit status: $?"
exit status: 1
grep -q found nothing and exited 1 on the remote host; ssh exits with the same status, so
if ssh host 'test -f /etc/app.conf'; then ... works in scripts. ssh itself uses 255 for
its own errors.
Run a local script on a remote host
ssh server 'bash -s' <<'EOF'
echo "running as $(whoami) on $(hostname)"
ls demo/logs | wc -l
EOF
running as dev on demo 3
bash -s reads the script from standard input, which the here-document supplies, so the
script never has to be copied to the server. Quoting 'EOF' keeps $(...) from being
expanded locally.
Commands get no terminal unless you ask
ssh server tty
ssh -t server tty
not a tty Pseudo-terminal will not be allocated because stdin is not a terminal. not a tty
A remote command runs without a terminal, so tty prints not a tty. -t requests one,
which programs like top, sudo password prompts and vim need; here even -t cannot
help because ssh's own standard input is not a terminal, and it says so.
Give a host a short name in an ssh config
Host web
HostName 127.0.0.1
User dev
Port 22
ssh -F ssh_config web 'echo connected to $(hostname) as $USER'
connected to demo as dev
A Host block maps the alias web to a real address, user and port. Normally this lives
in ~/.ssh/config and needs no -F; the example uses -F to point at a separate file.
See the settings ssh will use for a host
ssh -G server true | grep -E '^(user|hostname|port|connecttimeout) '
user dev hostname server port 22 connecttimeout none
-G prints the final configuration for a host after applying every config file and
default, then exits without connecting (the trailing true is a dummy command, which keeps
ssh from warning that it would have no terminal for a login). Useful when a Host pattern is not doing what you
expect.
Forward a local port to a remote service
ssh -f -N -L 9090:localhost:8080 server
curl -s http://localhost:9090/api/status.json
{"status": "ok", "version": "2.4.1", "uptime_s": 86400} -L 9090:localhost:8080 listens on local port 9090 and carries each connection through
ssh to localhost:8080 as seen from the server. -N runs no command and -f puts ssh in
the background once connected. This is how you reach a database or admin page that only
listens on the server's loopback.
Fail fast in scripts
ssh -o BatchMode=yes -o ConnectTimeout=5 server 'echo reachable'
reachable
BatchMode=yes forbids password and passphrase prompts, so an unattended script fails
instead of hanging, and ConnectTimeout bounds how long a dead host can stall it.
Debug a login with -v
ssh -v server true 2>&1 | grep -E 'Authenticat|accepts key'
debug1: Authenticating to server:22 as 'dev' debug1: Authentications that can continue: publickey debug1: Server accepts key: /home/dev/.ssh/id_ed25519 ED25519 SHA256:pTQrwWxt+t2g4SlardlErNMcCuna6ARNbiOI3+SF7Po Authenticated to server ([127.0.0.1]:22) using "publickey".
-v writes a debug trace to standard error; the grep keeps the authentication lines. They
show which methods the server allows and which key it accepted, the first thing to check
when a key login falls back to a password.
When the connection fails
ssh -p 2222 server true
ssh: connect to host server port 2222: Connection refused
Nothing listens on port 2222, so ssh reports Connection refused and exits with 255, the
status it reserves for its own errors (as opposed to the remote command's).
Common pitfalls
ssh host rm $fileexpands$filelocally and the remote shell splits it again. Quote the whole remote command, and for untrusted values useprintf %qto build it.A remote command that reads standard input (like
ssh host cmdinsidewhile readloops) swallows the loop's input. Usessh -nor redirect< /dev/null.WARNING: REMOTE HOST IDENTIFICATION HAS CHANGEDmeans the server's host key differs from~/.ssh/known_hosts. Find out why before removing the old entry withssh-keygen -R host.ssh refuses keys whose private file is readable by others (
UNPROTECTED PRIVATE KEY FILE).chmod 600 ~/.ssh/id_*andchmod 700 ~/.sshfix it.-psets the port for ssh, butscpuses-P. APortline in~/.ssh/configavoids remembering which.
GNU vs BSD and macOS
The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).
Linux distributions and macOS both ship OpenSSH, so options match; versions differ (macOS often lags), which matters for newer options and key types.
Options worth knowing
A selection, described in our own words. For the full list, see ssh(1) or ssh --help.
| Option | What it does |
|---|---|
-p PORT | Connect to PORT on the remote host. |
-l USER | Log in as USER (same as USER@HOST). |
-i FILE | Use the private key in FILE. |
-F FILE | Read configuration from FILE instead of ~/.ssh/config. |
-o OPTION | Set any config option, for example -o ConnectTimeout=5. |
-v | Print debug messages; -vv and -vvv for more. |
-q | Quiet: suppress most warnings and diagnostics. |
-t | Force a pseudo-terminal, for interactive remote programs. |
-T | Do not allocate a pseudo-terminal. |
-n | Read standard input from /dev/null. |
-N | Do not run a remote command; just forward ports. |
-f | Go to the background after authentication. |
-L [BIND:]PORT:HOST:HOSTPORT | Forward a local port to HOST:HOSTPORT as reached from the server. |
-R [BIND:]PORT:HOST:HOSTPORT | Forward a port on the server back to HOST:HOSTPORT reached from the client. |
-D [BIND:]PORT | Run a local SOCKS proxy that tunnels through the server. |
-J [USER@]HOST[:PORT] | Connect through a jump host (comma-separated for several). |
-A | Forward your ssh agent to the server (only to hosts you trust). |
-X | Enable X11 forwarding. |
-C | Compress the connection. |
-G | Print the effective configuration for the host and exit. |
-4 | Use IPv4 only. |
-6 | Use IPv6 only. |
-M | Act as a connection-sharing master (with ControlPath). |
-S SOCKET | Use SOCKET as the control path for connection sharing. |
-O COMMAND | Send a control command (check, exit, stop) to a master connection. |
-W HOST:PORT | Forward standard input and output to HOST:PORT through the server. |
-V | Print the version and exit. |
Related
scpCopy files between hosts over ssh, with cp-like syntax where a remote path is written HOST:PATH.rsyncCopy and synchronize files and directories, locally or over ssh, transferring only what changed.ssh-keygenCreate, inspect and manage SSH keys, and maintain known_hosts entries.ssh-copy-idInstall your public SSH key in a remote account's authorized_keys so you can log in without a password.curlTransfer data to or from a URL; the standard tool for HTTP requests, API calls and downloads from the shell.- QuotingSingle quotes, double quotes, backslashes and $'...', what each protects, and the unquoted-variable bugs they prevent, each shown failing and then fixed.
- Here-documents and here-stringsFeed multi-line text to a command with <<EOF, stop expansion with a quoted delimiter, indent with <<-, and pass one string with <<<.
- Exit codesWhat exit statuses mean, how $? works, the reserved values 126, 127 and 128+N, PIPESTATUS, and how to choose codes for your own scripts.