✍️ This post was written with two hands. The story — the first part — is Murilo's, lived and told by the person who was there. The technical manual, at the end, was written with AI. The split is intentional and marked in the text. Nothing hidden about the seam: part is human, part is machine, and the reader sees both.
If you've ever managed or logged into a web server and never set up SSH keys, it's because you don't yet know the real risks of a break-in — and that's okay. Until you find out what can happen.
Logging into a server over SSH with a username and password is like locking the front door of a house that faces the street, with nobody keeping watch. Anyone can try as many combinations as they want, freely.
And setting this up takes almost as much time as typing a username and password — and it makes getting into the server much faster and easier afterwards.
Ignorant of best practices, I managed my servers for a long time by typing:
ssh user@server-ip
password
That nearly cost me dearly, the day I found out my server had been broken into.
After that incident, I realized just how vulnerable a username and password are on SSH.
Today I can't say I sleep soundly — no system is completely break-in proof — but I sleep a lot better (and honestly, I always slept well, until I started managing servers).
Waking up on a fine Sunday morning to do some maintenance on the server, and finding out it was broken into through the front door because you left a combination padlock facing the street — that is not the kind of surprise I'd wish on anyone.
I have a degree in Law. I worked for 15 years in the legal field at a public institution, until I decided to venture into the world of programming.
And where did I end up? Managing systems at the institution I work for, after spending some time building automations in Python.
Managing systems wasn't exactly what I had in mind when I wanted to learn to code and understand the world of programming.
But that opportunity ended up teaching me far more about systems in general, about how the internet works — much more than I imagined back when the urge to work in development first hit me.
Today I can't picture myself doing anything other than this: automating deploys and backups, improving application performance on the server side, monitoring, alerting.
When it comes to SSH, there are bots scanning the internet all day long, trying usernames and passwords on the default port of servers all over the world. A weak, obvious, or reused password falls fast — it's just a matter of the right bot knocking on your door.
A truly strong password holds up much longer: since every attempt has to travel back and forth across the network, brute-forcing it takes far too long to be worth it. But it's still a secret — and any secret can leak, be reused on a service that got compromised, or end up in the wrong place. That kind of slip doesn't usually happen to a good systems administrator. Which, at the time, I was not.
So one way to minimize the risk is to disable password login and allow access only through SSH keys.
Even if you use a tool to fend off repeated attempts, like fail2ban, it's still less effective and less secure than SSH keys, because an attacker can switch IPs or use other tricks to get around it.
With an SSH key pair, the attacker only gets in if they get hold of the key. And for that, your slip-up has to be a bigger one — leaving it exposed somehow, or sharing it with the wrong people.
When you set up the key pair, you put the padlock on the door and keep the key on your computer — so access happens only with the private key. And you can put that padlock on as many servers as you want.
In other words: you create a pair of keys, one private and one public.
The private one is the key; the public one is the padlock.
So you hold on to the key and place as many padlocks as you like on the servers you manage.
First benefit: security.
On top of that, you can set up an alias. Instead of typing ssh name@server-ip and then the password every single time you want to reach the server, you just type ssh alias. It will ask for the passphrase you configured — only on the first access.
For the next ones that use that key — even on other servers — it's just ssh alias.
With that, you get both more security and more speed when accessing your servers.
A padlock with a key is harder for an intruder to open than a combination padlock — wouldn't you agree?
Luckily, when my server was broken into, it was a test machine I had just wiped to install a few applications for testing — I hadn't configured anything yet, nor deployed a single app. Damage contained, but the scare was real.
Want to know how to set up SSH keys in practice? Below is a tutorial put together with the help of AI, and at the end a checklist and a command map — for both your PC and the server — to configure SSH keys, with a full, step-by-step explanation.
If you have any questions, I'm happy to chat. I love talking about technology, infrastructure, DevOps, SRE, and related topics.
🤖 From here on: technical manual, written with AI. I told my story. Now comes the practical step-by-step — the part AI writes well. If you manage any server (a $5 VPS, an EC2 instance, the company machine), you can do this today, in about 20 minutes.
The step-by-step — swapping password for key on your server
This is the hands-on part. I won't just list commands — I'll explain the why behind each one, because understanding the reason is what lets you touch a production server without breaking into a cold sweat.
1. How the key works under the hood
You already saw the idea in the story above: the public key is the padlock, the private key is the key. It's just worth understanding what happens at the moment of login.
When you connect, the server sends a challenge that only the holder of the private key can solve. Your machine solves that challenge locally and sends back only the answer — the private key itself never travels across the network, not even during login.
That's where the difference from a password lives. A password is a secret that leaves your machine every time and can be guessed. The private key never leaves, and can't be guessed: an Ed25519 pair is such an astronomically large number that brute-forcing it stops being an option. You're not using a stronger password — you're switching to a mechanism where guessing simply doesn't exist.
2. Generate your key pair
On your computer (not on the server):
ssh-keygen -t ed25519 -C "your-user@laptop-2026"
-
-t ed25519— the algorithm. Ed25519 is modern, fast, and secure. Forget the old RSA. -
-C— a comment that stays attached to the public key. Use something that identifies the machine (e.g.,your-user@laptop-2026). Later on, this tells you which key is which. - Accept the default path (
~/.ssh/id_ed25519). - Set a strong passphrase. It's the password for the key. It's your second layer: if someone steals the private key file, they still need the passphrase to use it. Don't skip this.
This creates two files:
| File | What it is | Safe to share? |
|---|---|---|
~/.ssh/id_ed25519 |
Private key | Never. |
~/.ssh/id_ed25519.pub |
Public key | Yes, freely. |
The private key must be readable only by you. Check, and fix it if needed:
ls -la ~/.ssh/id_ed25519 # should show permission -rw------- (600)
chmod 600 ~/.ssh/id_ed25519
3. Get the public key onto the server
ssh-copy-id user@server-ip
This command asks for the server password — and that is, literally, the last time you'll type it. It copies your public key into the server's ~/.ssh/authorized_keys file, which is the list of padlocks that server accepts.
Test it:
ssh user@server-ip
An important detail to confirm it worked: if it asks for a passphrase, you're good — that's the passphrase for your key. If it asks for a password, you're still using the server's password. These are different things; pay attention to which word shows up.
4. Give your servers nicknames
Memorizing IPs is a waste of headspace. Create the ~/.ssh/config file on your computer:
Host app-prod
HostName 203.0.113.10
User deploy
Host app-staging
HostName 203.0.113.20
User deploy
# Applies to every host above
Host *
IdentityFile ~/.ssh/id_ed25519
AddKeysToAgent yes
ServerAliveInterval 60
(The aliases and IPs above are fictional — replace them with your own.)
Fix the permission (chmod 600 ~/.ssh/config) and that's it — now you connect like this:
ssh app-prod # instead of ssh deploy@203.0.113.10
ServerAliveInterval 60 sends a little signal every 60s so your connection doesn't drop on its own when you go a while without typing.
5. Close the password door
Working with a key isn't enough — as long as the password is still accepted, brute-forcing is still possible. The final step is to disable password authentication.
⚠️ The golden rule: don't lock yourself out. Keep your current SSH session open throughout this whole step. You'll test the new configuration in a second terminal window, without closing the first. If something goes wrong, the old session is still there for you to undo it. Only close it once the test passes.
Connected to the server, edit the SSH daemon configuration:
sudo nano /etc/ssh/sshd_config
Make sure these four lines look like this (uncomment any that have a # in front):
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
-
PermitRootLogin no— no one logs in directly asroot. You log in as a regular user and usesudowhen needed. -
PasswordAuthentication no— the heart of the change: passwords are no longer accepted. - The other two make sure the key is the way in, and close off an alternative password method.
Before applying it, validate the syntax — a typo here can take SSH down:
sudo sshd -t
If it prints nothing, you're clean. Then restart the service:
sudo systemctl restart ssh
📝 If
sshisn't the service name. On some distributions the service is calledsshd. If the command above complains, usesudo systemctl restart sshd.
Now the test — without closing the current session. Open a new terminal:
ssh app-prod
Got in with the key? Great. Now confirm the password is really dead:
ssh -o PubkeyAuthentication=no user@server-ip
This command tells SSH to ignore the key on purpose. The expected result is Permission denied. If you got a "Permission denied", congratulations — the password door is shut. Now you can finally close the old session.
6. Guard the private key like the treasure it is
Your private key is your identity on those servers. Lose the file = lose access. Leak the file = hand access to someone else. Treat it accordingly.
Do: save the key's contents in a secure note inside a password manager (Bitwarden has a free plan and works well). Copy the contents with cat ~/.ssh/id_ed25519 and paste them into a Secure Note. Keep the passphrase in a separate place.
Don't: save the key on Google Drive, Dropbox, or OneDrive. Those services sync the file automatically across several devices and expose it through the browser. You lose control over where your identity lives.
7. Two extra tweaks worth the effort
Optional, but recommended on servers exposed to the internet:
-
Change the SSH port. Port 22 is the default and takes 100% of the bot noise. Moving to another port isn't real security (it's just hiding), but it drastically cuts the junk in your logs. Add
Port XXXXtosshd_configand the matchingPort XXXXin the host block of your~/.ssh/config. -
Install
fail2ban. It watches the logs and temporarily bans IPs that keep trying to get in. It's your automatic sentry.
Final checklist
- [ ] Ed25519 key created, with a strong passphrase
- [ ] Public key copied to all your servers
- [ ]
~/.ssh/configset up with aliases - [ ]
PasswordAuthentication noandPermitRootLogin noon all of them - [ ] Tested in a new session before closing the old one
- [ ] Confirmed the password is blocked (
Permission deniedon the test) - [ ] Private key in a password manager (never on synced cloud storage)
-
Port changed +
fail2banon public servers
Mini-manual — every command in this post
Every command that appeared above, and what it does:
-
ssh-keygen -t ed25519 -C "comment"— generates the key pair (private + public) using the Ed25519 algorithm. -
ssh-keygen -p -f ~/.ssh/id_ed25519— adds or changes the passphrase of an existing key, without changing the key itself. -
ls -la ~/.ssh/id_ed25519— shows the key file and its current permission. -
chmod 600 ~/.ssh/id_ed25519— restricts read/write of the private key to your user only. -
chmod 600 ~/.ssh/config— does the same for the aliases file. -
ssh-copy-id user@server— copies your public key into the server'sauthorized_keys. -
ssh app-prod(orssh user@ip) — connects to the server. -
sudo nano /etc/ssh/sshd_config— opens the server's SSH daemon configuration for editing. -
sudo sshd -t— validates thesshd_configsyntax before applying it (prints nothing if it's OK). -
sudo systemctl restart ssh— restarts the SSH service to apply the new configuration (sshdon some distros). -
ssh -o PubkeyAuthentication=no user@ip— forces password login on purpose; used to confirm the password is blocked (should givePermission denied). -
cat ~/.ssh/id_ed25519— prints the private key's contents, to copy into your password manager.
Murilo Negrão — backend/DevOps developer. I write about modernizing legacy stacks and the bridge between code and infrastructure.
Top comments (0)