Linux ssh Command
last modified October 23, 2026
The ssh (Secure Shell) command opens an encrypted connection
to a remote machine. It is the standard way to log in to Linux servers, run
commands on them, copy data, and tunnel network connections. It replaced
insecure protocols such as telnet and rlogin; scp,
sftp, and rsync use it for file transfers.
The basic syntax is ssh [options] [user@]host [command]. The
examples use the OpenSSH 9.2 client on Debian 12, on a local machine called
box, with an SSH server called server that
listens on ports 22 and 2222 and a host called db that is
reachable only from server. Passwords and passphrases typed at
the prompts are not echoed, so they do not appear in the output.
Connecting to a server
The first connection to a server asks you to confirm its host key.
ssh jano@server
The host key identifies the server. Before answering yes,
compare the fingerprint with the one the administrator gives you through a
trusted channel; the server's fingerprint is printed by
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub. The key is
then stored in ~/.ssh/known_hosts and checked on every later
connection. After the password, you get a shell on the remote machine;
exit or Ctrl+D closes the connection. If the user
name is omitted, ssh uses your local user name.
$ ssh jano@server The authenticity of host 'server (172.18.0.2)' can't be established. ED25519 key fingerprint is SHA256:lr7cZ9OyjmNe4Y9OdsHQDlbeVByPmsrJufBZDBhkcCQ. This key is not known by any other names. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added 'server' (ED25519) to the list of known hosts. jano@server's password: Linux server 6.8.0-138-generic #138~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Fri Aug 7 13:43:15 UTC x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Fri Oct 23 11:50:00 2026 from 172.18.0.3 jano@server:~$ hostname server jano@server:~$ exit logout Connection to server closed.
Using a different port
The -p option selects the port when the server does not listen
on the default port 22.
ssh -p 2222 jano@server hostname
Because the host key was already accepted for server,
ssh does not ask again. Note that scp uses an
uppercase -P for the port, which is a common source of
confusion.
$ ssh -p 2222 jano@server hostname jano@server's password: server
Creating a key pair
Key-based authentication is more secure and more convenient than passwords.
The ssh-keygen command creates a key pair; Ed25519 is the
recommended type.
ssh-keygen -t ed25519 -C "jano@box" ls -l ~/.ssh
Pressing Enter at the first prompt keeps the default file name. The
passphrase encrypts the private key, so a stolen key file is useless
without it. The -C option sets a comment that helps you
recognize the key later. The private key id_ed25519 is
readable only by its owner and never leaves your machine; the public key
id_ed25519.pub is copied to servers. The file
known_hosts.old is a backup that ssh made when it
added keys to known_hosts.
$ ssh-keygen -t ed25519 -C "jano@box" Generating public/private ed25519 key pair. Enter file in which to save the key (/home/jano/.ssh/id_ed25519): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/jano/.ssh/id_ed25519 Your public key has been saved in /home/jano/.ssh/id_ed25519.pub The key fingerprint is: SHA256:h7zi9eHe/5wvbUDVGL6FG5d7zLArbREPVGTox/iyWFo jano@box The key's randomart image is: +--[ED25519 256]--+ | o*=| | ooo+| | .B=o| | . . +#+| | S . .=+=| | o .Eoo| | . o . .=+= | | . o o oooo.+| | . .+ ...==| +----[SHA256]-----+ $ ls -l ~/.ssh total 16 -rw------- 1 jano jano 444 Oct 23 13:51 id_ed25519 -rw-r--r-- 1 jano jano 90 Oct 23 13:51 id_ed25519.pub -rw------- 1 jano jano 978 Oct 23 13:51 known_hosts -rw-r--r-- 1 jano jano 142 Oct 23 13:51 known_hosts.old
Copying the public key to a server
The ssh-copy-id command appends a public key to
~/.ssh/authorized_keys on the server and sets the right
permissions.
ssh-copy-id -i ~/.ssh/id_ed25519.pub jano@server ssh jano@server hostname
The account password is needed one last time. From now on, the server
accepts the key, and ssh asks only for the passphrase of the
local key file. Once all users have keys, password logins can be disabled
on the server with PasswordAuthentication no in
/etc/ssh/sshd_config.
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub jano@server /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/jano/.ssh/id_ed25519.pub" /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed /usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys jano@server's password: Number of key(s) added: 1 Now try logging into the machine, with: "ssh 'jano@server'" and check to make sure that only the key(s) you wanted were added. $ ssh jano@server hostname Enter passphrase for key '/home/jano/.ssh/id_ed25519': server
The SSH agent
The ssh-agent program keeps decrypted keys in memory, so the
passphrase is typed once per session.
eval "$(ssh-agent -s)" ssh-add ssh-add -l ssh jano@server hostname
ssh-agent -s prints shell commands that set the
SSH_AUTH_SOCK variable, and eval runs them.
ssh-add loads the default keys, and ssh-add -l
lists the loaded keys. Desktop environments usually start an agent
automatically. With AddKeysToAgent yes in
~/.ssh/config, a key is added to the agent the first time it
is used.
$ eval "$(ssh-agent -s)" Agent pid 100 $ ssh-add Enter passphrase for /home/jano/.ssh/id_ed25519: Identity added: /home/jano/.ssh/id_ed25519 (jano@box) $ ssh-add -l 256 SHA256:h7zi9eHe/5wvbUDVGL6FG5d7zLArbREPVGTox/iyWFo jano@box (ED25519) $ ssh jano@server hostname server
Running remote commands
A command after the host name is run on the remote machine instead of an interactive shell. The connection closes when the command finishes.
ssh jano@server 'uname -sr; whoami' ssh jano@server echo $HOSTNAME ssh jano@server 'echo $HOSTNAME' ssh jano@server 'cat /etc/os-release' | grep PRETTY
Quoting decides where things are expanded. Without quotes, the local shell
replaces $HOSTNAME before ssh runs, so the remote
command is echo box. In single quotes, the variable reaches
the remote shell. In the last command, cat runs remotely and
grep locally, because the pipe is outside the quotes. For
interactive programs such as top or sudo with a
password prompt, add -t to allocate a terminal.
$ ssh jano@server 'uname -sr; whoami' Linux 6.8.0-138-generic jano $ ssh jano@server echo $HOSTNAME box $ ssh jano@server 'echo $HOSTNAME' server $ ssh jano@server 'cat /etc/os-release' | grep PRETTY PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
The config file
The ~/.ssh/config file stores connection settings per host,
so you do not have to repeat them.
Host web
HostName server
User jano
Port 2222
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 60
Host defines an alias, and the indented lines apply to it.
ServerAliveInterval 60 sends a keepalive message every minute,
which prevents idle connections from being dropped by firewalls and NAT
routers. The file must not be writable by others.
ssh web hostname ssh -G web | grep -E '^(hostname|user|port|identityfile|serveraliveinterval) '
ssh web now connects to port 2222 of server as
jano with the given key. The -G option prints the
effective configuration for a host without connecting, which helps to
check which settings apply; the harmless note about the pseudo-terminal
appears because its standard input is not a terminal. The aliases also work
with scp, sftp, rsync, and
git.
$ ssh web hostname server $ ssh -G web | grep -E '^(hostname|user|port|identityfile|serveraliveinterval) ' Pseudo-terminal will not be allocated because stdin is not a terminal. user jano hostname server port 2222 serveraliveinterval 60 identityfile ~/.ssh/id_ed25519
Debugging a connection
The -v option prints debugging messages; -vv and
-vvv print more.
ssh -V ssh -v web true 2>&1 | grep -E 'Reading configuration|Connecting to|Authenticating to|Server accepts key|Authenticated to'
ssh -V prints the client version. The full verbose output is
long, so the example shows only the key steps: the configuration files
read, the address and port, the user, the accepted key (here, from the
agent), and the authentication method. When a login fails, these messages
show where it stops. Remove addresses and other private details before
sharing such logs.
$ ssh -V OpenSSH_9.2p1 Debian-2+deb12u10, OpenSSL 3.0.22 25 Aug 2026 $ ssh -v web true 2>&1 | grep -E 'Reading configuration|Connecting to|Authenticating to|Server accepts key|Authenticated to' debug1: Reading configuration data /home/jano/.ssh/config debug1: Reading configuration data /etc/ssh/ssh_config debug1: Connecting to server [172.18.0.2] port 2222. debug1: Authenticating to server:2222 as 'jano' debug1: Server accepts key: /home/jano/.ssh/id_ed25519 ED25519 SHA256:h7zi9eHe/5wvbUDVGL6FG5d7zLArbREPVGTox/iyWFo explicit agent Authenticated to server ([172.18.0.2]:2222) using "publickey".
Piping data through ssh
ssh connects the standard input and output of the remote
command to the local ones, so it can be used in pipelines.
tar czf - project | ssh web 'tar xzf - -C /tmp && find /tmp/project' ssh web 'cat /etc/hostname' > remote-hostname.txt cat remote-hostname.txt
The first command packs a directory locally and unpacks it on the server in one stream, without a temporary archive. The second saves remote output in a local file. For everyday copying, scp and rsync are more convenient.
$ tar czf - project | ssh web 'tar xzf - -C /tmp && find /tmp/project' /tmp/project /tmp/project/readme.txt /tmp/project/docs /tmp/project/docs/guide.txt $ ssh web 'cat /etc/hostname' > remote-hostname.txt $ cat remote-hostname.txt server
Local port forwarding
The -L option forwards a local port through the SSH
connection to an address reachable from the server.
curl -sS http://server:8080/ ssh -N -L 9090:localhost:8080 web & sleep 1 curl -sI http://localhost:9090/ | head -n 2 kill $!
A web application on the server listens only on its loopback interface,
port 8080, so it cannot be reached from the network. The tunnel makes it
available on port 9090 of the local machine; localhost in
9090:localhost:8080 is resolved on the server. The
-N option opens no remote shell, only the tunnel. This is a
common way to reach admin interfaces and databases that are not exposed.
$ curl -sS http://server:8080/ curl: (7) Failed to connect to server port 8080 after 0 ms: Couldn't connect to server $ ssh -N -L 9090:localhost:8080 web & $ sleep 1 $ curl -sI http://localhost:9090/ | head -n 2 HTTP/1.0 200 OK Server: SimpleHTTP/0.6 Python/3.14.0 $ kill $!
Remote port forwarding
The -R option works in the opposite direction: it opens a port
on the server that leads to a service on the local machine.
python3 -m http.server 8000 --bind 127.0.0.1 > /dev/null 2>&1 & ssh -N -R 9000:localhost:8000 web & sleep 1 ssh web 'curl -sI http://localhost:9000/ | head -n 1' kill $!
A development web server runs locally on port 8000. On the server, port
9000 now leads to it. By default, the remote port is bound to the server's
loopback interface; making it reachable from other machines requires
GatewayPorts in the server configuration.
$ python3 -m http.server 8000 --bind 127.0.0.1 > /dev/null 2>&1 & $ ssh -N -R 9000:localhost:8000 web & $ sleep 1 $ ssh web 'curl -sI http://localhost:9000/ | head -n 1' HTTP/1.0 200 OK $ kill $!
SOCKS proxy
The -D option starts a SOCKS proxy on a local port. Each
connection made through the proxy is opened from the server.
ssh -N -D 1080 web & sleep 1 curl -sI --socks5-hostname localhost:1080 http://localhost:8080/ | head -n 2 kill $!
With --socks5-hostname, curl lets the proxy
resolve the host name, so localhost means the server, and the
request reaches the web application from the previous example. Browsers can
use the same proxy to route their traffic through the server, for example
on an untrusted network. The traffic is encrypted only between your machine
and the server.
$ ssh -N -D 1080 web & $ sleep 1 $ curl -sI --socks5-hostname localhost:1080 http://localhost:8080/ | head -n 2 HTTP/1.0 200 OK Server: SimpleHTTP/0.6 Python/3.14.0 $ kill $!
Jump hosts
The -J option connects to a host through an intermediate host,
often called a bastion or jump host.
ssh jano@db hostname ssh -J web jano@db hostname
The local machine cannot resolve or reach db. With
-J, ssh first connects to web and
then opens the connection to db through it. The connection is
encrypted end to end, the jump host only forwards it, and the key from the
local agent is used for both hops. In the config file, the same is written
as ProxyJump web in a Host db block.
$ ssh jano@db hostname ssh: Could not resolve hostname db: Temporary failure in name resolution $ ssh -J web jano@db hostname The authenticity of host 'db (<no hostip for proxy command>)' can't be established. ED25519 key fingerprint is SHA256:JukR+oypi/uWL7NTbXTDar1uVY2XnP9ZtbXYm2W54+g. This key is not known by any other names. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added 'db' (ED25519) to the list of known hosts. db
Connection sharing
OpenSSH can reuse one connection for several sessions. The first session becomes the master, and later sessions skip the key exchange and authentication.
time ssh web true ssh -o ControlMaster=auto -o ControlPath=~/.ssh/cm-%C -o ControlPersist=60 web true ssh -o ControlPath=~/.ssh/cm-%C -O check web time ssh -o ControlPath=~/.ssh/cm-%C web true
ControlPersist=60 keeps the master running in the background
for 60 seconds after the last session ends. -O check asks
whether the master is running. A shared session starts in milliseconds
instead of a quarter of a second, which speeds up scripts and tools such as
Ansible that open many connections. The options are usually put in the
config file.
$ time ssh web true real 0m0.248s user 0m0.088s sys 0m0.007s $ ssh -o ControlMaster=auto -o ControlPath=~/.ssh/cm-%C -o ControlPersist=60 web true $ ssh -o ControlPath=~/.ssh/cm-%C -O check web Master running (pid=199) $ time ssh -o ControlPath=~/.ssh/cm-%C web true real 0m0.015s user 0m0.006s sys 0m0.004s
Key file permissions
ssh refuses to use a private key that other users can read.
chmod 644 ~/.ssh/id_ed25519 SSH_AUTH_SOCK= ssh -o BatchMode=yes web true chmod 600 ~/.ssh/id_ed25519
Emptying SSH_AUTH_SOCK hides the agent, so ssh
must read the key file, and BatchMode=yes fails instead of
asking for a password. The key is ignored and authentication fails. Keep
~/.ssh at mode 700 and private keys at mode 600.
$ chmod 644 ~/.ssh/id_ed25519 $ SSH_AUTH_SOCK= ssh -o BatchMode=yes web true @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: UNPROTECTED PRIVATE KEY FILE! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Permissions 0644 for '/home/jano/.ssh/id_ed25519' are too open. It is required that your private key files are NOT accessible by others. This private key will be ignored. Load key "/home/jano/.ssh/id_ed25519": bad permissions jano@server: Permission denied (publickey,password). $ chmod 600 ~/.ssh/id_ed25519
When a host key changes
If the server presents a different host key than the one stored in
known_hosts, ssh refuses to connect. Here, the
administrator regenerated the server's host keys.
ssh web 'sudo rm /etc/ssh/ssh_host_*; sudo ssh-keygen -A' ssh jano@server hostname ssh-keygen -R server ssh jano@server hostname
The warning is loud for a reason: a changed key may mean that someone
intercepts the connection. Legitimate causes are a reinstalled server or
new keys, as here; confirm with the administrator before you continue.
ssh-keygen -R removes the old entries for the host, and the
next connection asks about the new key like a first connection.
$ ssh web 'sudo rm /etc/ssh/ssh_host_*; sudo ssh-keygen -A' ssh-keygen: generating new host keys: RSA ECDSA ED25519 $ ssh jano@server hostname @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)! It is also possible that a host key has just been changed. The fingerprint for the ED25519 key sent by the remote host is SHA256:jS8EQUfoe5K6M1e+NmsgEhHBNImrGwgmqbRnun7RwJQ. Please contact your system administrator. Add correct host key in /home/jano/.ssh/known_hosts to get rid of this message. Offending ECDSA key in /home/jano/.ssh/known_hosts:3 remove with: ssh-keygen -f "/home/jano/.ssh/known_hosts" -R "server" Host key for server has changed and you have requested strict checking. Host key verification failed. $ ssh-keygen -R server # Host server found: line 1 # Host server found: line 2 # Host server found: line 3 /home/jano/.ssh/known_hosts updated. Original contents retained as /home/jano/.ssh/known_hosts.old $ ssh jano@server hostname The authenticity of host 'server (172.18.0.2)' can't be established. ED25519 key fingerprint is SHA256:jS8EQUfoe5K6M1e+NmsgEhHBNImrGwgmqbRnun7RwJQ. This key is not known by any other names. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added 'server' (ED25519) to the list of known hosts. server
Best practices for ssh
- Use keys with passphrases: Create Ed25519 keys, protect them with a passphrase, and use an agent.
- Disable password logins: Once keys are installed, set
PasswordAuthentication noon servers. - Verify host keys: Compare fingerprints on first connection and investigate changed keys.
- Use the config file: Store hosts, users, ports, keys,
and jump hosts in
~/.ssh/config. - Quote remote commands: Use single quotes for anything that should be expanded on the remote side.
- Debug with -v: Verbose output shows where a connection or login fails.
Source
In this article, we have used the ssh command to log in to a
server, set up key authentication and an agent, run remote commands, use a
config file, forward ports, connect through a jump host, share connections,
and handle changed host keys.
Author
List all Linux tutorials.