This is sequential tutorial. You can kinda skip parts you already know about, but at that point you probably already know about everything discussed here.
I try to entirely cover the basic use cases and a few common usages. Extra research is always encouraged.
Let’s get started then.
SSH Basics
Install ssh (search how to if you don’t know).
To log in to a remote computer, let’s say its IP is 192.168.0.18
, you’d run the following command: ssh root@192.168.0.18
. This tries to log in as root on a PC with ip 192.168.0.1.
This can’t be much simpler, we’ll see how to make it a tiny bit simpler later.
You don’t actually need to write an IP. If your remote host has a DNS name you can use it instead, and SSH will figure the IP for you. For example: if the hostname feynman.xyz
is associated to the IP 192.168.0.18
then these two are equivalent commands:
ssh root@192.168.0.18
ssh root@feynman.xyz
Hostnames are generally easier to remember (duh).
Private and public keys
Using the default settings for ssh means we will try to authenticate using username and password. In short: this is not a good way to do auth.
Using passwords is not as secure as things can be, and in most places (such as GitHub, for interacting with repositories) authentication by password is not even allowed.
At a high level, the way you log in to remote hosts won’t change. You’ll be prompted for a password, you’ll fill it and hopefully you’ll be in.
SSH Keys are divided into two: private and public.
Like the name suggests: your public key is the one you share, the one you’ll put into the remote host. The private one, on the other hand, is yours and yours only.
When you ssh
into the remote host you are going to tell the host you want to use a certain key. It will then try to find a corresponding public key. If the corresponding public key is present in the server the authentication can proceed.
It might make more sense to think of the public key as a lock, and the private key as an actual key.
When you create a key pair you give it a password to use it. It’s an extra security measure so in case your private key is compromised, the malicious actor still has to figure out your password, which will hopefully give you enough time to remove the corresponding public key from wherever you deployed it.
You can already see how this is a more efficient and secure workflow too. If one person messes up the others don’t need to necessarily lose access to the remote host.
Not only that, but managing access control is easier. If you want to remove someone’s access you can simply remove their public key from the remote host. If this was password based you’d need to change the password and tell everyone else the new one. Specially as teams grow this gets out of hand.
There’s also the fact that if you use password you’ll generally want a very strong one, which will be hard to remember to begin with. By using keys you can get away with using a weaker one, because there’s an extra layer of security: you’ll actually hide your file.
In any case, you can search for more “why"s on SSH keys. Let’s see how to use them.
Creating and using keys
As discussed before, a better way to handle logging in is to use public/private ssh key pairs.
To generate a ssh key pair do the following:
ssh-keygen -t ed25519
You’ll answer a form for the creation of the keys, and they’ll be created. The “file in which to save the key” expects a full file path, and it’s generally a good idea to keep them in your ~/.ssh/
directory.
The ed25519
is the key type. If you do not know what that is or what changes with each type just pick ed25519
, it’s your best bet at the time of writing this.
You can search specifically how to increase your security when using SSH. Stuff like opsec, best encryption algorithms and other stuff.
For instance: there is a -a
option, go take a look at what it does!
It’s generally a good idea to set informative file names and comments for your ssh keys. You can decide how to do this yourself. Also, take a look at the -C
option in the man ssh-keygen
. My personal preference is:
<some_name>_<key_type>(.pub)
For instance, the following are a few keys that I have right now:
vms_ed25519 # For my local VMs
git_ed25519 # For github & gitlab
id_rsa # I don't actually remember what this is for (It's the default name and algo)
Now copy the contents of your new public key to the remote user’s ~/.ssh/authorized_keys
file. This file stores the public keys of people allowed to access this user remotely.
Now we can do the following:
ssh -i ~/.ssh/mynewkey_ed25519 root@feynman.xyz
You’ll be prompted for the key password, type it.
And it works!
More SSH options
I only go through some common options I’ve seen people need.
For a more complete and detailed explanation of available options check out man ssh_config
!
Sometimes just setting the user and host is not enough. Below is a list of the most common options you’ll have to interact with:
- User: It’s the thing before the
@
. For instance: to log in to themv
user in thefeynman.xyz
host you’ll typessh mv@feynman.xyz
. - Port: Normally SSH will expect connections on port 22. If for whatever reason it isn’t, you can specify the port with the
-P
option:ssh mv@feynman.xyz -P <YOUR_PORT>
. - IdentityFile: Want to tell SSH to use a specific key? Add the
-i
flag:ssh mv@feynman.xyz -P <YOUR_PORT> -i <path_to_your_key_file>
- AddressFamily: You might want to use only ipv4 or only ipv6, you can specify that using the
-4
and-6
flags respectively.
The ~/.ssh/config
file
What we saw until now was pretty great. But it’s annoying to keep typing long options simply to ssh
into a machine, specially if the arguments are necessary anyways. Take a look at what might be a command someone will type just to log in to a remote host:
ssh -i ~/.ssh/vms_ed25519 -P 2222 mv@feynman.xyz
Thankfully, there’s a very nice way to set up these options automatically in a file called ~/.ssh/config
.
In this section you will learn how to make ssh <whatever>
do the exact same as ssh <any_user>@<any_ip/host> -P <any_port> [...any options]
.
For example: we will turn that command we just saw into ssh feynman
.
The basics
The most common setup is the following:
Host <aliases for the host>
Hostname <actual hostname or ip>
User <user>
Port <port>
IdentityFile <path_to_the_private_ssh_key>
For example, to configure ssh -i ~/.ssh/vms_ed25519 -P 2222 mv@feynman.xyz
you’d do:
Host feynman
Hostname feynman.xyz
User mv
Port 2222
IdentityFile ~/.ssh/vms_ed25519
Cool, we can now just ssh feynman
! But note: if we ssh feynman.xyz
it won’t pick that config, for that you have to add another alias, such as:
Host feynman feynman.xyz
# ...
The aliases have nothing to do with the actual hostname, SSH basically replaces them with the actual Hostname you set in the ~/.ssh/config
. You can set an alias batatinha
for a hostname some.other.completely.unrelated.domain
.
ProxyJumping
This is a special use case. I’m not even going to bother doing it directly in the command line.
Let’s say you need to accesss a machine B
, but you can’t directly do it, because it’s inside a private network.
Instead, you might need to first access A
, and then from A
access B
. That’s what a ProxyJump
is for!
By using the following in the ~/.ssh/config
:
Host A
Hostname A.xyz
User user_a
IdentityFile ~/.ssh/a_ed25519
Host B
Hostname B.xyz
User user_b
IdentityFile ~/.ssh/b_ed25519
ProxyJump A # yes, you can use aliases here
Then again, you can access the B
host via ssh B
!
Cool! But isn’t writing passwords all the time annoying? I had to first unlock a_ed25519
, then b_ed25519
. And then when I tried to ssh B
I had to write the passwords again.
SSH Agent
If you’re trying to follow along you might be annoyed that you have to type your password to unlock ~/.ssh/mykey_ed25519
for the Nth time.
There’s a very neat piece of software that helps with this: SSH agent.
To start it: run eval $(ssh-agent)
. Now your current terminal session can use the SSH agent.
To add a key to it, just run ssh-add ~/.ssh/mykey_ed25519
. It will ask you to unlock it, and then will store it temporarily in memory.
Now you can ssh host
and if you already ran ssh-add
for the appropriate key, it will not prompt you for your password!
You can also see the keys in the agent by running ssh-add -l
. There is also -x
and -X
, which are pretty neat.
You can add keys to the agent automatically by setting the AddKeysToAgent
config for the appropriate host.
Host A
Hostname A.xyz
User user_a
IdentityFile ~/.ssh/a_ed25519
AddKeysToAgent yes
Since this is something you might want to do for every host anyways, you can write it only once by using echo 'AddKeysToAgent yes' >> ~/.ssh/config
to set it for every hsot at the end of your config file.
I generally go for AddKeysToAgent 12h
. It’s safer, and it’s intuitive why.
Always find a balance between security and convenience. And do not underestimate security’s importance.
Now you only need to run eval $(ssh-agent)
, because whenever you ssh host
you’ll be prompted for your key’s password and it will be automatically added to the agent.
Doing it automagically
We already did the following:
- Learn the basics of ssh
- Learn some options for ssh
- Learn how to manage ssh key
- Configure the ssh options for the hosts we want to access
- Found a way to not keep re-typing the password for our keys after running
eval $(ssh-agent)
andssh-add
.
There is only one thing to make this setup more convenient: Not needing to keep runningg eval $(ssh-aggent)
on every new terminal we want to run ssh
from.
So… how do we make it persistent?
There are obviously a number of ways (this is Linux, there is always a number of ways), but the one I find interesting is by using a systemd
user unit.
I won’t bother you with the details. At this point if you need a very detailed setup for SSH with an agent and stuff it’s better to research on your own and get familiar with what you’re probably running on a daily basis anyways.
Place the following in your ~/.config/systemd/user/ssh-agent.service
file (create it):
[Unit]
Description=SSH Agent
[Service]
Type=simple
Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket
ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK
[Install]
WantedBy=default.target
Also, add export SSH_AUTH_SOCK="${XDG_RUNTIME_DIR}/ssh-agent.socket"
to your ~/.bashrc
or whatever file you use to set environment variables.
After that just run systemd --user enable --now ssh-agent
so it automatically starts on your login and.
Now every new terminal shares the same SSH agent, which is automagically started!
This means that, not only you can ssh host
for every configured host, you also only need to type the password to unlock the private key once (until it expires and you have to unlock it again)!
Going beyond this guide
I left out a lot of things (duh), but I’d recommend you take a look at a few:
- Different SSH key types (“what"s and “why"s).
- The
~/.ssh/known_hosts
file. - SSH Agent forwarding (and the security around it).
- Many others that I didn’t even consider for this blog.
Non-extensive list at all btw.
Tips and Tricks
Below are a few use cases you might be interested in.
SSH Tunnels
Let’s say an application is running on port 8080
on your remote host, but there is a firewall blockin any ports other than 22
(default SSH one), how to access what is running there?
This does not play well with SSL/TLS.
Do a bit a research about it, but basically: https will not work.
You can create a tunnel with the -L
flag! It’s usage is a bit funky (check out man ssh
), but the most basic setup is:
ssh remotehost -L LOCAL_PORT:REMOTE_ADDRESS:REMOTE_PORT
This is a bit confusing, and I find it better to learn simply by actually testing. For instance:
ssh remotehost -l 4444:google.com:80
Will allow you to access localhost:4444
in your browser, and it will actually access whatever gooogle.com:80
is for the remote host. Think of it like: “when I access localhost:4444, the remote host will access google.com:80 and return the results to me” (kind of).
For the case of accessing something inside the remote host:
ssh remotehost -l 4444:localhost:8080
This allows you to access localhost:4444
in your browser as if you were accessing localhost:8080
in the remote host’s browser (kind of).
There is a lot to learn about tunneling alone. Check out the -N
flag and the implications of tunneling like this.
SOCKS Proxy
Remember ProxyJump
ing? We have a similar situation here.
Let’s say you want to access stuff that is inside a private network.
One usage for this is accessing papers in IEEExplore using your university’s network, which might actually allow you to access stuff there if there is a valid license.
For that the -D
flag can be used! For example, by running ssh -D 12345 remotehost
, and then configuring the SOCKS proxy in LibreWolf (Firefox) like the following image:
You’ll have access to stuff in the network that remotehost
is in!