Essential SSH commands

Table of contents

Using SSH is an almost inevitable part of managing remote servers. It's robust security mechanism allows users to safely connect and transfer data between two machines over an insecure network, for example the internet. Encryption and strong authentication ensure that the SSH tunnel's contents remain confidential. But an SSH tunnel has more uses than simply gaining a shell session on a remote machine.

Running commands on a remote server

The most common usage of SSH is to simply start a shell session on a remote server. Our examples will use remote.com as the server's address, you will need to replace this with your own server's domain (or IP address) before running the commands.

ssh root@remote.com

This command will start a session with the default shell configured for the user we logged in as (in this example, root). If you haven't connected to this server before, you will be greeted with a screen like this:

The authenticity of host 'remote.com (172.17.0.3)' can't be established.
ED25519 key fingerprint is SHA256:hAuVbkOMw8vaKPVC5IOGeSb2Jqnj8bAjdysulUSqSaE.
This host key is known by the following other names/addresses:
   ~/.ssh/known_hosts:153: [hashed name]
Are you sure you want to continue connecting (yes/no/[fingerprint])?

That is an intentional security mechanism: Every time you connect to a server with a key you haven't seen before, you will be asked to confirm this. In case one of your server's IPs gets hijacked and pointed at an attacker's server, their keys will mismatch and you will be notified that the server you tried to connect to is unknown, thus alerting you to the attack.

Once you confirm by typing yes, you will see a warning like this:

Warning: Permanently added 'remote.com' (ED25519) to the list of known hosts.

That is also expected and normal behavior. Lastly, the command will ask you for the password for the user we tried to log in as. Typing the correct password will immediately start a shell session as this user on the server. When you are done working on the server, run this to end the remote session:

exit

If you only want to run a single command, you don't need to start a full shell session. Simply add the command you want to run as the last parameter to the ssh command:

ssh root@remote.com "echo hello"

This will simply output hello without starting a session. You can use this in combination with pipes to quickly transfer file contents between the machines:

cat local_data.txt | ssh root@remote.com "cat > remote_data.txt"

We first output the contents of the local file local_data.txt to stdout, and pipe it through the cat command on the server, which then outputs it to the file remote_data.txt on the server. Note the quotes around the cat command on the server: everything in quotes will be run on the server; without these quotes, only the first word would be sent to the server and the rest running locally. This can be used to quickly download remote files:

ssh root@remote.com "cat remote_data.txt" > local_data.txt

This command is the reverse of the previous one, downloading the server's remote_data.txt file contents to the local file local_data.txt.

Passwordless authentication

Typing and remembering the passwords to servers quickly becomes a nuisance when working with multiple servers. Additionally, passwords will usually be less secure than using key authentication directly (as keys are much longer than passwords). SSH allows users to add their public key to the server user's ~/.ssh/authorized_keys file, which will then allow them to directly log in as that user when connecting, without providing a password.

ssh-copy-id root@remote.com

This will as for the user's password (one last time) - afterwards, using ssh commands for that user on that server will not ask for a password again. Note that you will need to do this for every machine you want to log in from, as their public/private key pairs will differ, so they count as separate clients from SSH's perspective.

From a security perspective, it is reasonable to enable password authentication when first installing the server, then enable key-only authentication once you have enabled passwordless authentication for all your client machines. This approach helps to minimize the risk of an attacker brute-forcing SSH passwords.

Forwarding ports

The secure SSH tunnel can also be used to quickly connect services running on one of the machines to the other. Some software running on servers will include sensitive interfaces like dashboards or log file viewers, that shouldn't be exposed over the network directly. These will typically run by binding their listening address to 127.0.0.1 (i.e. they can only be accessed from the machine they are running on, in our case, only the server can access them). To view these interfaces on a client machine, you can forward that address to a local address on the client with the -L flag:

ssh -N -L 3333:127.0.0.1:8000 root@remote.com

The command will have no output (and seem to hang) - this is expected. As long as it stays in that state, the client can now access 127.0.0.1:3333 locally, which will behave as if the server had made a request to 127.0.0.1:8000. When you are done working with the forwarded service, press Ctrl + C on the terminal with the SSH command. Once the command completes, the port forwarding will be stopped and the service is not accessible locally anymore.

This command has a reverse, where you can forward an address from your local machine (maybe a developer workstation) to a remote server as if it were running on the remote using the -R flag:

ssh -N -R 8000:127.0.0.1:3333 root@remote.com

This command will do the opposite as the previous one, forwarding the service running on the local address 127.0.0.1:3333 to the remote server on at 127.0.0.1:8000 as if it were running on the server directly.

If you want to be able to access the forwarded port from outside the remote server, you will need to specify a different bind address:

# bind to all ipv4 interfaces
ssh -N -R 0.0.0.0:8000:127.0.0.1:3333 root@remote.com

# bind to all ipv6 interfaces
ssh -N -R "[::]:8000:127.0.0.1:3333" root@remote.com

# bind to all interfaces
ssh -N -R \*:8000:127.0.0.1:3333 root@remote.com

If the remote SSH server is using openssh-server, the you must also set the option GatewayPorts yes in /etc/ssh/sshd_config, otherwise forwarded ports will always be bound to the loopback interface (localhost).

Copying files over SSH

The scp command, short for secure copy, is a common choice when copying data between machines over SSH. The syntax is similar to that of cp, in the format scp source destination, where source is the file or directory to copy, and destination is the address to store the copy. An SSH address can be used as either source or destination, with an optional path at the end to change the output path or file:

scp local.txt root@remote.com:/storage/data.txt

This example would copy the local file local.txt to the file /storage/data.txt on the server. Reversing the position of the argument let's us download the file:

scp root@remote.com:/storage/data.txt local.txt

This time, the file /storage/data.txt is copied from the server to the local file local.txt.

Entire directories can also be copied by adding the -r flag, just like for cp:

scp -r config/ root@remote.com:/app

This would copy the local directory config/ (and all directories/files within) to the server's /app directory.

Syncing directory and file contents

For more advanced copying needs, the rsync command provides more flexibility when transferring data between two hosts over SSH. While rsync can do the same as scp, it has many more options, for example to preserve hard links, maintain file permissions and timestamps, or resume partial copies. While scp will blindly copy the entire file each time, rsync intelligently checks what parts of the file on local and remote differ, and only transfer the missing or changed parts of a file or directory instead of copying everything. This makes it ideal for recurring copies such as scheduled backups, or for copying large amounts of data, as a network interrupt can be resumed where it left off.

rsync -av --progress root@remote.com:/backups backups/remote.com/

The flags used are mainly for convenience: -a will enable archive mode, keeping file permissions and timestamps as they were in the source. The other two are mainly for human readability, where -v will print more details about which file is currently processed, and --progress shows an indicator of how far the transaction has progressed. The output may look like this the first time:

receiving incremental file list
created directory backups
backups/
backups/d
             0 100%   0.00kB/s   0:00:00 (xfr#1, to-chk=5/7)
backups/e
             0 100%   0.00kB/s   0:00:00 (xfr#2, to-chk=4/7)
backups/x
             0 100%   0.00kB/s   0:00:00 (xfr#3, to-chk=3/7)
backups/a/
backups/b/
backups/c/

sent 101 bytes received 305 bytes 812.00 bytes/sec
total size is 0 speedup is 0.00

Running the command again, the output changes, indicating that no files were transferred:

receiving incremental file list

sent 28 bytes received 176 bytes 408.00 bytes/sec
total size is 0 speedup is 0.00

This is synchronization at work: the files on the server and client were the same, thus nothing had to be changed (they are already in sync). Changing a file's contents or adding new ones will make the same rsync command transfer the changes (but only the changes, nothing more):

receiving incremental file list
backups/x
             9 100%   8.79kB/s   0:00:00 (xfr#1, to-chk=3/7)

sent 47 bytes received 236 bytes 566.00 bytes/sec
total size is 9 speedup is 0.03

The command correctly picked up on the changes that were made to the file /backups/x and transferred only that to the client, leaving everything else untouched.

Note that by default, rsync will not perform a destructive sync: it will add new files and change the ones that differ, but if you delete a file from the source, it won't automatically be deleted from the copy as well (to prevent accidental data loss). If you want to enforce deleted files also get deleted from the copy, you need to add the --delete flag to the command:

rsync -av --progress --delete root@remote.com:/backups backups/remote.com/

Now if we delete a file from the source, the command will also delete it from the destination:

receiving incremental file list
deleting backups/x

sent 28 bytes received 162 bytes 380.00 bytes/sec
total size is 0 speedup is 0.00

As shown in the output, the file /backups/x was deleted on the server, and using the --delete flag forced rsync to also delete it from the local copy.

More articles

How software compression works

Making files smaller without losing information

Checking disk health in Linux

Identify bad drives before they fail

Handling file downloads with PHP

Meeting user expectations with every download

Why boring software is a smart choice

Not everything is about excitement

Common pitfalls running docker in production

Avoiding the mistakes many make when embracing containerized deployments

Modern linux networking basics

Getting started with systemd-networkd, NetworkManager and the iproute2 suite