Getting More Out Of SSH
If you’ve been working with Linux servers for any length of time then you’ve used SSH to connect to a server. Though the way you use SSH and the features you use may vary, SSH is incredibly powerful and has a lot of features the average user may not be aware of, or use. I don’t want to say this is a post about power user features, or a list of hidden tips and tricks; the more you use something, the more you understand it, and more efficiently use it. This post is just a collection of features I use that others I’ve seen don’t, and may prove useful to others.
Getting Started Fundamentals
The first few sections below outline how to generate a key pair and give a high level overview of what public key cryptography means in this context.
Using Keys
I’d imagine you probably already use keys rather than plain password authentication. If you don’t, you should. And here’s how.
First, you need to generate a new key pair. A key pair consists of a public key and a private key. The private key stays on your client machine. The public key is added to any server that you wish to access. A passphrase is used to secure the key pair, but successful authentication also depends upon using that password together with your local private key and the presence of your public key on the target server.
To generate your key pair, you can run
ssh-keygen -t rsa
This will create two files, by default, in your ~/.ssh/
directory. You can change the location by adding a -f <pathname>
to the ssh-keygen
command above. This will create ~/.ssh/id_rsa
and ~/.ssh/id_rsa.pub
as your private
and public keys respectively.
Now, you need to append the contents of ~/.ssh/id_rsa.pub
to ~/.ssh/authorized_keys
on the remote server.
cat /.ssh/id_rsa.pub | ssh user@remotehost 'cat >> /.ssh/authorized_keys'
Now, when you try to connect to the remote host, you won’t be prompted for your password. You may be prompted for the passphrase to unlock the key, but you should only need to do that once (or e.g. whenever you restart your computer).
SSH Agent
Most Operating Systems allow you to use an agent program with SSH. On Linux systems, this is typically ssh-agent
. On
windows, you can use Pageant with Putty. An SSH agent securely stored unlocked private keys in memory for the duration
of a session, to avoid the need to always specify commonly used keys when using SSH. Note that SSH will attempt each key
stored in the agent. If all of the keys are exhausted unsuccessfully the SSH server may return an authentication failure
to avoid brute force attacks with many different keys.
Private Keys Are Private
By way of an analogy, imagine you are Alice, and Bob wants to allow you access to his server. Key based authentication is like opening a door that’s padlocked. Bob could send Alice a key to unlock the padlock and gain access (a private key), but that means the private key is out in the open while it’s being sent, for any nefarious party to grab. Instead, imagine the door can be unlocked by Alice giving Bob her padlock (the public key); and that anybody with a key to their own padlock can unlock the door. In this way, you can give padlocks (public keys) to anybody who wants to give you access to their server, but you keep the key (private key) to yourself.
For this reason, the private key should be kept very secure. For all intents and purposes, think of it as your password. You don’t just hand that out over email, right!
Diagnosing Connection Issues
If you’re having trouble connecting to a remote machine, and you’re not sure why, then you can increase the verbosity of
the SSH command line. By default, adding -v
to your SSH command will make things a bit more verbose. You can get
ever more verbose output with -vv
or -vvv
. The verbose output can often quickly point you in the right direction.
Every Day Usage
Connect to remotehost
as the same user as your local user, optionally using non-standard port port
.
ssh remotehost [-p port]
Connect to remotehost
as user user
, optionally using non-standard port port
.
ssh user@remotehost [-p port]
Connect to remotehost
as the same user as your local user, using the private key /path/to/private-key
, optionally
using non-standard port port
.
ssh -i /path/to/private-key remotehost [-p port]
Connect to ‘remotest’ as the user user
, using the private key /path/to/private-key
, optionally using the
non-standard port port
.
ssh -i /path/to/private-key user@remotehost [-p port]
Note that if you don’t specify a private key, prior to attempting password authentication, an attempt will be made to
use key-based authentication using any keys in your ssh-agent
. This will typically contain any default keys such as
id_rsa
or id_dsa
. You may need to unlock the key the first time you use it in a given session.
Tunnelling
SSH can be used for so much more than just connecting to a shell session on a remote server. If you have access to a remote server, you can do all manner of useful things involving port forwarding and tunnelling.
Local Port Forwarding
If you need access to a port on a remote server that isn’t publicly exposed, for example a remote MySQL server that’s only listening to localhost, you can forward a local port to a remote port.
ssh -f -N -L 4406:127.0.0.1:3306 user@remotehost
This command will forward the local port 4406 to the remote port 3306. The -f
flag puts the SSH command into the
background; the -N
option makes sure that no remote commands are executed; the -L
flag is used to forward a local
port to a port on a remote host, rather than forwarding traffic from a remote port to the local host.
Note that you can also use other options, as described earlier, such as specifying a private key with -i
or a
non-standard port with -p
.
You could use the same local and remote port, provided you don’t have a local MySQL server that’s already listening on that port.
Note that you don’t have to use localhost. In fact, you can forward any service that the remote machine you have access
to, can see. If the MySQL database was hosted on a completely private machine that remotehost
can route to and access,
called db-remotehost
, you could do the following.
ssh -f -N -L 4406:db-remotehost:3306 user@remotehost
Remote Port Forwarding
Remote forwarding is useful in situations where you and another party both have access to a common machine, and you would like to use that machine to offer access to the other party, to a service running on your local machine.
In this example, you might have a local web server listening on port 8080, and you’d like to allow a third part to access that local web server. You’d like the third party to be able to browse to the IP address of the common shared server, on port 9090, and have them access your local web server on port 8080. To do so, you’d use a command like the following.
ssh -f -N -R 9090:localhost:8080 user@remotehost
Note that in this example, we use the -R
flag for remote forwarding, rather than the -L
flag we used earlier. Also
note that the order of the ports is reversed when compared to the earlier example: the remote port comes first, and the
local port comes second.
In order for this to work, you also need to enable the GatewayPorts
option on the SSH daemon on the remote host.
You’ll also need to ensure that any firewall running on the remote host permits access to port 9090.
SSH Config
All of the examples given thus far should prove useful, but may become a pain to use regularly, especially if you are
administering a large number of Linux servers. To ease this pain, you can use an SSH config file to define common host
aliases and sets of options. On a per-user basis, the config file is typically located at ~/.ssh/config
.
The file is arranged in terms of a series of Host
blocks. Each definition continues until the next Host
block.
Wildcards can also be used to broaden the scope of a match. The configuration file is parsed from top to bottom, looking
for hosts that match the host used in the SSH command you run. The matching doesn’t immediately stop at the first match.
Any later matches in the file can add to options previously defined, but only for options that have not yet been defined
earlier.
If we want to connect as user bob
to remotehost1.example.com
on port 2222 with a private key named bob-key
, we
could use a command line such as the following:
ssh -i /path/to/bob-key -p 2222 bob@remotehost1.example.com
We could also use the SSH config option names rather than the SSH command line options:
ssh -o "IdentityFile /path/to/bob-key" -o "Port 2222" -o "User bob" -o "HostName remotehost1.example.com"
But this would get pretty tiresome to type all the time. It would be nice if we could instead just type ssh remotehost1
. We can achieve this by adding the following block to ~/.ssh/config
.
Host remotehost1
IdentityFile /path/to/bob-key
Port 2222
User bob
HostName remotehost1.example.com
Because of the ordering described earlier, you can construct your file with less specific, shared options, toward the end of the file. You should think of such entries as “catch all” rather than “default” and always ensure they come last. For example, I use the following sensible options that will apply to all connections, unless override with more specific options earlier in the file.
Host *
identitiesonly yes
cipher arcfour
compression yes
serveraliveinterval 60
gssapiauthentication no
Proxy Commands
You may require access to a large number of private servers, all of which can be accessed via an intermediary bastion
host. However, you might not want to send your agent everywhere. In this case, you can use a ProxyCommand
option in
your SSH config file. In the example below, we use the netcat in a ProxyCommand
to effectively tunnel onward to the
target private host.
With this in place, we can connect to e.g. db.private.example.com
or www2.private.example.com
and SSH will
transparently connect firstly to the bastion host, and then set up a netcat tunnel to pass the SSH connection through to
the destination host.
Host bastion.example.com
ProxyCommand none
Host *.private.example.com
ProxyCommand ssh bastion.example.com nc -q0 %h %p
Miscellaneous
Removing A Bad Host
If you try to connect to a host, and the server signature differs to that recorded in the known hosts file, you may need to remove the offending signature from the known hosts file. You should do this with caution, and only when you know the server you are connecting to is legitimate: for example if you’ve relaunched an AWS EC2 instance with the same hostname.
You can either use ssh-keygen
:
ssh-keygen -R hostname
Or you can use sed
, since the error message will give the line number of the offending known hosts entry (line 25 in
this example):
sed -i '25d' ~/.ssh/known_hosts
Forcing Password Login
If you are working with a host that you know only accepts password authentication, and you want to quickly avoid the overhead of trying keys, you can use a command line such as the following.
ssh -o "PreferredAuthentications password" -o "PubkeyAuthentication no" user@remotehost
Skipping Known Hosts Checking
If you work with e.g. Amazon Web Services, or otherwise interact with a lot of short-lived and disposable or ephemeral servers, you may want to skip checking the known hosts file, since you expect server signatures to change for a given host quite frequently. This should be used with caution, and you wouldn’t want to globally take this step for security reasons.
ssh -o "StrictHostKeyChecking no" -o "UserKnownHostsFile /dev/null" user@remotehost
This command will automatically add the host to the known hosts file, but also use /dev/null as that file.
Check If Host Key Exists
You can check for the presence of a given host key in your known hosts file with the following:
ssh-keygen -F hostname-or-ip
Diff Local And Remote
You can quickly diff a remote file with your local copy with either of the following two examples.
ssh user@remotehost "cat remote_file.txt" | diff - remote_file.txt
diff local_file.txt <$(ssh user@remotehost 'cat remote_file.txt')
Pseudo TTY Allocation
You’ll find some commands, such as sudo
, will give you an error when you try to run the command on a remote server via
SSH. You may see an error about being unable to allocate a TTY. To overcome this issue, you use the -t
flag to force
allocation of a pseudo-tty.
ssh -t user@remotehost sudo cat /etc/passwd
Skipping Lastlog
Similarly to the above example, you can disable allocation of a pseudo-tty with the -T
flag. This will mean your
session doesn’t appear in the output of who
or lastlog
.
ssh -T user@remotehost
SSH Escape Sequences
You’ve no doubt used telnet before, to diagnose network connectivity issues; to do some basic interaction with a web
serve or an MTA etc. If you have, you’ll probably be aware of the ^]
escape sequence to hang up the session and drop
you to the telnet shell. From there you can ^d
to get back to your regular shell. SSH comes with similar functionality
via the ~
escape sequence. If you’re connected to a machine with SSH, you can type ~?<enter>
to see a list of
supported options. You’ll most often use ~.
to kill a hung SSH session.
Supported escape sequences:
~. - terminate connection (and any multiplexed sessions)
~B - send a BREAK to the remote system
~C - open a command line
~R - request rekey
~V/v - decrease/increase verbosity (LogLevel)
~^Z - suspend ssh
~# - list forwarded connections
~& - background ssh (when waiting for connections to terminate)
~? - this message
~~ - send the escape character by typing it twice