Mastering SSH as the main gateway for remote server administration: key authentication with ssh-keygen and ssh-copy-id, hardening sshd_config, file transfer with scp and rsync, to SSH tunnels and the traps that can lock you out of your own server.

After episode 17 where we covered Networking Fundamentals & Diagnostic Tools — understanding interfaces, routing, DNS, through to checking ports with ss — you can now read a server's network condition. But there's a more fundamental question: how do you enter that server remotely? Production servers never have a monitor and keyboard attached — they live in a datacenter or cloud, and the only correct entry door is SSH.
SSH (Secure Shell) is a protocol that secures your entire terminal session with encryption. Imagine telnet — its honest but foolish predecessor: everything you type, including passwords, is sent in plaintext and can be intercepted by anyone on the path. SSH fixes that completely: the connection is encrypted end-to-end, the server's identity is verified, and your identity is proven with a password or cryptographic keys.
However, a default-installed SSH is actually the biggest risk: weak passwords, the root account can log in, and password authentication is exposed to brute-force attacks 24 hours a day. In this episode 18, we'll cover SSH from zero: basic connections, key authentication, server config hardening, file transfer, and tunnels — including how to avoid the most embarrassing trap: locking yourself out of your own server.
The simplest SSH connection looks like ssh user@host. With the full syntax, non-standard ports, and verbosity for debugging:
ssh user@192.168.1.10ssh -p 2222 user@192.168.1.10ssh -vvv user@192.168.1.10ssh user@192.168.1.10 "uptime && uname -r"The last line is a very useful pattern in automation: you don't need an interactive login to run a single command remotely — just append the command in quotes. This is the foundation of multi-server administration scripts.
When you first connect to a new server, you'll see a host key authenticity warning:
The authenticity of host '192.168.1.10 (192.168.1.10)' can't be established.
ED25519 key fingerprint is SHA256:9x...k8.
Are you sure you want to continue connecting (yes/no/[fingerprint])?This is the anti-man-in-the-middle mechanism. Verify the fingerprint from a trusted source (e.g. the server console), then type yes. After that the key is stored in ~/.ssh/known_hosts and won't be asked again.
Passwords can be guessed, brute-forced, or shoulder-surfed. SSH keys are a pair of cryptographic files: a private key that only you own and a public key installed on the server. You only prove ownership of the private key, never sending it.
ssh-keygen -t ed25519 -C "name@email.com"Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/user/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Your identification has been saved in /home/user/.ssh/id_ed25519
Your public key has been saved in /home/user/.ssh/id_ed25519.pubThe reason to use ED25519 over RSA: its cryptographic curve is stronger with a much shorter key, and generation is faster. If a legacy system forces RSA, use ssh-keygen -t rsa -b 4096.
Important
The private key must have permission 600 (only the owner can read it). If the private key is too open (e.g. 644), SSH refuses to use it with the message Permissions 0644 for 'id_ed25519' are too open. Fix it with chmod 600 ~/.ssh/id_ed25519. This isn't an annoying rule — it's the last line of defense if your file leaks.
Once the key is created, copy the public key to the server with ssh-copy-id:
ssh-copy-id user@192.168.1.10This command automatically adds your public key to ~/.ssh/authorized_keys on the server. Now log in without a password:
ssh user@192.168.1.10sshd_config: Closing Doors You Don't NeedThe SSH server configuration is in /etc/ssh/sshd_config. Before your server is exposed to the internet, there are three mandatory changes. Let's see before and after using diff markers:
#Port 22
#PermitRootLogin yes
#PasswordAuthentication yes#Port 22
Port 2222
#PermitRootLogin yes
PermitRootLogin no
#PasswordAuthentication yes
PasswordAuthentication no
PubkeyAuthentication yesThe changes above close the three biggest holes: the default port that's always attacked, direct root login, and brute-force-prone password authentication. After editing, always validate the configuration before restarting the service:
sudo sshd -tsudo systemctl restart sshdCaution
Never restart sshd before you've ensured two things: (1) sudo sshd -t reports no errors, and (2) you have another way in (physical console, out-of-band management, or another open SSH session). Changing Port or turning off PasswordAuthentication without the correct key on the server = you've locked yourself out. If you're already locked out: use the provider's console (Vultr, AWS, etc.) to reverse the changes.
Remember too: every SSH port change must be followed by a firewall update (episode 19) — keeping port 22 open while the service moves to 2222 is an unnecessary leak.
scp and rsyncSSH isn't just for terminals; it also becomes a secure file transfer channel.
scp is the simplest way for a one-off transfer:
scp ./backup.sql user@192.168.1.10:/home/user/scp user@192.168.1.10:/home/user/backup.sql ./scp -r ./dist/ user@192.168.1.10:/var/www/rsync is the next level — it transfers only the differences between files, saving bandwidth and able to resume interrupted transfers. It's the standard tool for deployments and backups.
rsync -avz ./dist/ user@192.168.1.10:/var/www/| Flag | Function |
|---|---|
-a | Archive mode: recursive + preserve permission, owner, timestamp |
-v | Verbose: show synced files |
-z | Compress during transfer |
--progress | Show per-file progress |
--delete | Delete files on the destination that don't exist on the source (full sync) |
--dry-run | Simulate without actually transferring (always test first!) |
rsync -avz --progress --delete ./dist/ user@192.168.1.10:/var/www/rsync -avz --dry-run --delete ./dist/ user@192.168.1.10:/var/www/For a non-standard SSH port (the one you set in the hardening section), add -e "ssh -p 2222" so rsync knows which door it must go through:
rsync -avz -e "ssh -p 2222" ./dist/ user@192.168.1.10:/var/www/Incremental backups with --link-dest. One of rsync's most appreciated admin features is hardlink backups: you get a series of full snapshots that look like complete daily backups, when in fact unchanged files are just hardlinks to the previous snapshot — saving disk without sacrificing restore ease.
rsync -avz --link-dest=../backup-2026-08-01 /var/www/ user@backup.example.com:/backup/backup-2026-08-02/The day-2 snapshot only stores changed files; the rest become hardlinks to the day-1 snapshot. You can restore at any time without guessing the backup's contents.
Warning
--delete is a double-edged sword: it deletes files on the destination that don't exist on the source. Always run --dry-run first and watch the deleting lines in the output. One path mistake (./dist/ vs ./dist) can mean wiping the wrong directory's contents on a production server.
The SSH connection is already encrypted. So it makes sense to channel other traffic through SSH — a technique called tunneling or port forwarding. The most real case: a database on a server only listens on localhost (the safe pattern from episode 17), so you need access from your laptop without opening the port to the public.
Local port forwarding (-L): a port on your local machine is forwarded to a specific host from the server's perspective.
ssh -L 5433:127.0.0.1:5432 user@192.168.1.10Now from the laptop, connecting to 127.0.0.1:5433 transparently reaches PostgreSQL on the server — without opening the database port to the internet. This is the same SSH tunnel concept many developers use to access databases on a VPS.
Remote port forwarding (-R): the reverse — a port on the server is forwarded to your local machine. Useful when you need to access your laptop from outside (e.g. webhook development).
ssh -R 8080:127.0.0.1:3000 user@192.168.1.10Jump host (-J): when the target server can only be reached via a bastion server (jump box) — the standard pattern in companies.
ssh -J user@bastion.example.com user@10.0.0.5ssh -J user@bastion.example.com -L 5433:127.0.0.1:5432 user@10.0.0.5~/.ssh/configIf you manage many servers, typing -p 2222 user@... repeatedly isn't efficient. ~/.ssh/config defines aliases for each host:
Host prod-web
HostName 192.168.1.10
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519_prod
Host db-prod
HostName 10.0.0.5
User admin
ProxyJump prod-webAfter this file is created, connections become this short:
ssh prod-webAgent forwarding (ssh -A) lets a server use your local private key to SSH to the next server — very convenient for hopping between servers. But there's a heavy price: anyone with root access on the intermediate server can abuse your keys. The safe rule: use -A only on servers you truly trust, and make ForwardAgent no the default in sshd_config.
Warning
Agent forwarding is a shortcut to compromise. When you log in with ssh -A to a server that turns out to be infiltrated, the attacker can use your SSH agent to jump to other servers accessible only with your keys. For hopping between servers, it's safer to use jump hosts (-J) — the private key never "rides along" on an intermediate server. Don't enable ForwardAgent permanently.
1. Locking yourself out of sshd_config. Already emphasized: validate with sshd -t, make sure the key is installed before turning off password auth, and have an alternative access path.
2. Key permissions too open. The private key must be 600 (chmod 600 ~/.ssh/id_ed25519), and the ~/.ssh directory must be 700. SSH is very strict about this — the Permissions too open error can't be fooled by lowering security.
3. ssh-copy-id only copies once. After PasswordAuthentication no, you can no longer use ssh-copy-id (it needs a password). Plan ahead: install the key before turning off password auth.
4. Forgetting to update known_hosts after a server reinstall. A new server carries new host keys; SSH refuses the connection with REMOTE HOST IDENTIFICATION HAS CHANGED. This is actually a security feature — verify the new key from a trusted source, then clear the old entry with ssh-keygen -R <host>.
5. scp -r vs rsync for repeated syncs. scp copies everything each time; for repeated deployments use rsync which saves bandwidth. For one-off transfers, scp is enough.
6. Tunnels silently dropping. A broken tunnel connection isn't immediately visible. Use the options -o ServerAliveInterval=60 -o ServerAliveCountMax=3 to detect and drop dead connections — or use autossh for auto-reconnect.
In this episode 18, you've mastered SSH as the main gateway for server administration: basic connections and remote execution, key authentication with ssh-keygen and ssh-copy-id, hardening sshd_config (change the port, disable root login, disable password auth), file transfer with scp and rsync, SSH tunnels (-L, -R, -J) for channeling secure traffic, and a serious warning about agent forwarding.
Key points to take home:
600.sshd -t, and make sure there's an alternative way in.rsync is more efficient for repeated syncs; always --dry-run before --delete.-J (jump host) is safer than -A (agent forwarding) for hopping between servers.In the next episode 19 we'll build the server's outer defense layer through the topic Linux Firewall Management (UFW, Firewalld & Iptables). You'll learn to filter traffic by port and source, understand the differences between the three main firewall tools in the Linux ecosystem, and — just as importantly — avoid the traps that block you from your own server. See you there!