You have already used SSH dozens of times in this course: you log in to srv-tramontana from laptop-student, you copy files with scp and you synchronise with rsync. You have used it the way people use a lift, without wondering how it works. This lesson opens the box, because SSH is your server's front door and right now that door accepts passwords, allows root logins and has racked up 47 failed attempts from 203.0.113.44 that nobody has looked at.
By the end you will have ed25519 keys instead of passwords, an sshd_config hardened directive by directive, a ~/.ssh/config that does the heavy lifting for you, tunnels to reach Tramontana's database without exposing it to anybody, and the habit — which here is not optional — of never closing the door you are coming in through.
Contents
- What SSH solves and why Telnet and FTP are dead
- Architecture and the protocol's three phases
- Server authentication: host keys and
known_hosts - Key authentication: generating, installing and protecting
ssh-agent, agent forwarding andProxyJump- The client configuration file
- Hardening
/etc/ssh/sshd_config - The safe procedure for not locking yourself out
- Tunnels: local, remote and SOCKS
- File transfer:
scp,sftpandrsync - Persistent sessions with
tmux - Auditing: the 47 attempts from 203.0.113.44
- What SSH solves and why Telnet and FTP are dead
Before SSH, administering a remote machine meant Telnet: a protocol that sends the username, the password and every command in plain text over the network. Anybody with access to the same segment — or to the router in between — read the whole session. The same goes for FTP, rlogin and rsh.
SSH solves three problems at once, and it is worth telling them apart because each one is attacked differently:
| Problem | What SSH guarantees | How |
|---|---|---|
| Confidentiality | Nobody in the middle reads what you send | Symmetric encryption of the channel |
| Integrity | Nobody alters the data unnoticed | MAC / authenticated encryption |
| Mutual authentication | The server is who it says it is, and so are you | Host keys + user keys |
The third is the most forgotten and the most important: an encrypted channel to the wrong attacker protects absolutely nothing. That is why this lesson devotes a whole section to known_hosts.
- Architecture and the protocol's three phases
srv-tramontana runs the sshd daemon, managed by systemd. In Ubuntu 24.04 there is a detail that surprises a lot of people: the service is socket-activated.
$ systemctl status ssh --no-pager
● ssh.service - OpenBSD Secure Shell server
Active: active (running) since Mon 2026-08-18 09:15:02 CEST
TriggeredBy: ● ssh.socketssh.socket listens on the port and starts ssh.service when a connection arrives. The practical consequence is important and we come back to it in section 7: the Port directive in sshd_config is ignored while socket activation is in place.
An SSH connection goes through three phases in this order:
sequenceDiagram
participant C as Client (laptop-student)
participant S as Server (srv-tramontana)
C->>S: 1. Supported versions and algorithms
S->>C: Host public key + key exchange
Note over C,S: Encrypted channel established (ephemeral Diffie-Hellman)
C->>C: 2. Does the fingerprint match known_hosts?
C->>S: 3. Authentication: signature with the user's private key
S->>C: Verified against authorized_keys → session granted
- Key exchange and the encrypted channel. Client and server negotiate algorithms and derive a session key with ephemeral Diffie-Hellman. From here on everything is encrypted, phase 3 included.
- Server authentication. The server proves it holds the private key matching its host key; the client checks that key against
known_hosts. - Client authentication. Now, and only now, the user identifies themselves — with a key or with a password. It is essential that this comes afterwards: that is why the password never travels in the clear.
- Server authentication: host keys and
known_hosts
known_hostsThe server has its own keys in /etc/ssh/, generated at installation time:
$ ls /etc/ssh/ssh_host_*_key
/etc/ssh/ssh_host_ecdsa_key /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key
$ ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
256 SHA256:9dK3nQpX2vTfL8mRc1sYwZ0aB4eHgJ7iOu5N6xVpQrE root@srv-tramontana (ED25519)The first time you connect you see this:
The authenticity of host '10.0.2.15 (10.0.2.15)' can't be established. ED25519 key fingerprint is SHA256:9dK3nQpX2vTfL8mRc1sYwZ0aB4eHgJ7iOu5N6xVpQrE. Are you sure you want to continue connecting (yes/no/[fingerprint])?
What that warning really means, as opposed to what everybody does with it: SSH is telling you "I have no way of knowing whether this machine is the one you are after". The correct answer is not to type yes blindly, but to compare that fingerprint with the one you obtained through a different channel — the VM console, the cloud provider's panel, a signed email from whoever installed the server. If you accept without checking, you accept the first machine that answers, and that is exactly the gap that the man-in-the-middle attack exploits: somebody places themselves between you and the server, presents you with their host key, you accept, and from then on they decrypt your session, see your password and forward it to the real server. All with the green padlock in place.
Once accepted, the key is saved in ~/.ssh/known_hosts and later connections verify themselves. If it changes one day, you will see a huge warning with REMOTE HOST IDENTIFICATION HAS CHANGED! and the connection is refused. There are three legitimate causes: the server was reinstalled, its host keys were regenerated, or the IP was reused for another machine. The illegitimate cause is a man in the middle. Work out which it is before deleting anything, and once you know, delete only that entry:
Never rm ~/.ssh/known_hosts: that destroys the trust built up with every machine and leaves you accepting new fingerprints blindly for weeks.
- Key authentication: generating, installing and protecting
Asymmetric cryptography uses a pair of mathematically related keys: what the private one signs, only its matching public one verifies, and the private key cannot be derived from the public one in any reasonable time. In SSH, you keep the private key on your laptop and upload the public one to the server. To authenticate, the server throws you a challenge, you sign it with the private key and it verifies the signature with the public key it holds in authorized_keys. The private key never leaves your machine, not even encrypted: that is what makes it radically better than a password, which does travel (even if through an encrypted channel) and which can be guessed by brute force.
$ ssh-keygen -t ed25519 -C "operator@laptop-student-2026-08"
Enter file in which to save the key (/home/student/.ssh/id_ed25519):
Enter passphrase for "/home/student/.ssh/id_ed25519" (empty for no passphrase):
Your identification has been saved in /home/student/.ssh/id_ed25519
The key fingerprint is:
SHA256:tR7xK2mP9wQ4vZ1cN8bY0aL6sJ3fH5gD operator@laptop-student-2026-08Why ed25519 and not RSA: ed25519 offers security equivalent to 3072-bit RSA with 68-character keys, signs and verifies faster, does not depend on a good random number generator for every signature and has no parameters you can choose badly. Use 4096-bit RSA only if you need compatibility with old equipment that does not support ed25519. The -C comment is not decorative: it identifies whose key it is and when it was made, for when you review authorized_keys two years from now.
The passphrase encrypts the private key on disk. It is the second layer: if your laptop is stolen, they have a useless file without it. The cost — typing it every time you use it — is solved by ssh-agent.
The permissions are compulsory, not a recommendation. SSH refuses to use a private key that others can read:
$ chmod 700 ~/.ssh && chmod 600 ~/.ssh/id_ed25519 && chmod 644 ~/.ssh/id_ed25519.pub
$ ssh [email protected]
Permissions 0644 for '/home/student/.ssh/id_ed25519' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.Installing the public key on the server, with the tool or by hand:
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected] # or, by hand:
$ cat ~/.ssh/id_ed25519.pub | ssh [email protected] \
'install -d -m 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'Each line of authorized_keys accepts per-key options in front of the type, and they are a layer of control almost nobody uses:
from="10.0.2.0/24,192.0.2.10" ssh-ed25519 AAAAC3Nza...QrE operator@laptop-student restrict,command="/home/operator/scripts/backup_tramontana.sh --read-only" ssh-ed25519 AAAAC3Nza...tYu backups@nas
| Option | Effect |
|---|---|
from="pattern" |
The key is only valid from those IPs or names |
command="..." |
That is what runs and nothing else, ignoring whatever the client asks for |
restrict |
Disables tunnels, agent, X11 and pty in one go (the safest default) |
no-port-forwarding |
Forbids tunnels with that key |
The restrict,command= combination is the right way to give access to an automated process — a backup, a deployment — without giving it a shell.
ssh-agent, agent forwarding and ProxyJump
ssh-agent, agent forwarding and ProxyJumpssh-agent holds your decrypted key in memory so you do not have to type the passphrase every time:
$ eval "$(ssh-agent -s)"
Agent pid 4821
$ ssh-add -t 4h ~/.ssh/id_ed25519
Enter passphrase for /home/student/.ssh/id_ed25519:
Identity added: /home/student/.ssh/id_ed25519 (operator@laptop-student-2026-08)
Lifetime set to 14400 seconds-t 4h makes the key expire in the agent: if you leave your laptop unlocked in a café, the exposure window is limited. ssh-add -D removes them all at once.
Agent forwarding (ssh -A) lets you use your keys from the server you have logged in to, in order to hop to a third one. Its real risk: while the session is open, anybody with root on that intermediate machine can use your agent — talk to the socket in /tmp/ssh-*/agent.* and sign with your keys to log in anywhere they are valid. It does not steal your key, but it can use it, which in practice is the same thing.
The correct alternative is ProxyJump, which does not expose the agent: the client opens a tunnel through the hop and negotiates end-to-end encryption with the final destination.
$ ssh -J [email protected] [email protected]Practical rule: use ProxyJump always; use -A only if there is no alternative, and then with ssh-add -c (which asks for confirmation on every use).
- The client configuration file
Everything above is written once in ~/.ssh/config (mode 600) and disappears from your muscle memory:
Host *
ServerAliveInterval 30
ServerAliveCountMax 3
HashKnownHosts yes
Host tramontana
HostName 10.0.2.15
User operator
Port 22
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 10m
Host tramontana-luis
HostName 10.0.2.30
User luis
IdentityFile ~/.ssh/id_ed25519
ProxyJump tramontana| Directive | What it is for |
|---|---|
HostName / User / Port |
The real details behind the alias: ssh tramontana and you are in |
IdentityFile |
Which key to use with this destination |
IdentitiesOnly yes |
Offer only that key: without this the agent tries them all and you can exhaust MaxAuthTries |
ServerAliveInterval |
Sends a heartbeat every 30 s: stops NAT from cutting idle sessions |
ControlMaster/ControlPersist |
Multiplexing: the second connection reuses the first one's channel and opens in milliseconds |
ProxyJump |
An intermediate hop without exposing the agent |
Multiplexing is the one you notice most: repeated rsync and scp runs stop renegotiating the encryption every time.
- Hardening
/etc/ssh/sshd_config
/etc/ssh/sshd_configThis is the heart of the lesson. Ubuntu 24.04 reads /etc/ssh/sshd_config and, inside it, an Include /etc/ssh/sshd_config.d/*.conf at the top of the file. Since in SSH the first value found wins, whatever you put in a file under sshd_config.d/ takes priority over the main file. That is the clean way to harden without touching the original or fighting the next package update.
$ sudo cp /etc/ssh/sshd_config /root/sshd_config.bak-$(date +%F)
$ sudo tee /etc/ssh/sshd_config.d/60-tramontana-hardening.conf > /dev/null <<'CONF'
# SSH hardening for srv-tramontana — see /opt/tramontana/HISTORY
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no
AllowGroups sshusers
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowTcpForwarding yes
AllowAgentForwarding no
PrintMotd no
LogLevel VERBOSE
CONF
$ sudo chmod 600 /etc/ssh/sshd_config.d/60-tramontana-hardening.conf| Directive | What it protects | The honest caveat |
|---|---|---|
PermitRootLogin no |
Removes the account every attacker tries first; forces people to log in under their own name and use sudo, which also leaves a trail |
With prohibit-password root can still log in with a key; no is clearer |
PasswordAuthentication no |
The single most effective measure: it kills brute force at the root | Make sure your key works first |
KbdInteractiveAuthentication no |
Closes the alternative route through which PAM could still ask for a password | Without this, the previous line can be bypassed |
AllowGroups sshusers |
An allowlist: even if the account exists, intern does not get in unless they are in the group |
Create the group before reloading |
MaxAuthTries 3 |
Cuts the session after 3 attempts and multiplies the cost of the attack | Careful if the agent offers many keys |
LoginGraceTime 30 |
Fewer half-authenticated connections left open | The default 120 s is too much |
ClientAliveInterval 300 |
Closes dead sessions from forgetful administrators | Complements the client's ServerAliveInterval |
X11Forwarding no |
Surface area a server with no desktop does not need | — |
AllowAgentForwarding no |
Stops a compromise of the server from using the agents of whoever logs in | — |
LogLevel VERBOSE |
Records the fingerprint of the key used on every login | Essential for auditing; without it you do not know which key got in |
And the honest debate about changing the port: moving SSH from 22 to 2222 hugely reduces the noise from automated scans and, with it, the size of your logs. What it does not do is protect you: any ten-second nmap finds the new port, and in exchange you complicate life for your tools and your colleagues. It is log hygiene, not security; never count it as a control. If you do it anyway on Ubuntu 24.04, remember socket activation: Port 2222 in sshd_config has no effect and you have to touch the socket.
The first empty line clears the inherited list; without it, the service would listen on both ports.
- The safe procedure for not locking yourself out
This module's golden rule: never close the door you are coming in through. In SSH that translates into five steps that are never skipped.
# 1. Create what the configuration takes for granted
$ sudo groupadd -f sshusers && sudo usermod -aG sshusers operator
# 2. Validate the syntax WITHOUT applying anything
$ sudo sshd -t && echo "syntax OK"
syntax OK
# 3. See the EFFECTIVE configuration after the Includes
$ sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|allowgroups|maxauthtries)'
permitrootlogin no
passwordauthentication no
allowgroups sshusers
maxauthtries 3
# 4. Reload (do not restart): open sessions survive
$ sudo systemctl reload ssh
# 5. Test from a THIRD session, without closing the previous two
$ ssh -v [email protected] 'echo OK'
debug1: Authentications that can continue: publickey
debug1: Offering public key: /home/student/.ssh/id_ed25519 ED25519
debug1: Server accepts key
OKsshd -t validates the syntax; sshd -T shows you the result after resolving the Includes, which is the only thing that counts. reload re-reads the configuration without touching established sessions: if you have made a mess, your two open sessions stay alive and you can revert. And above all there is the VM console in VirtualBox, which does not go over the network and is your last resort. With socket activation, a port change also needs sudo systemctl restart ssh.socket.
- Tunnels: local, remote and SOCKS
An SSH tunnel carries an arbitrary TCP connection inside the encrypted channel. Three forms, three real use cases at Tramontana:
# LOCAL (-L): connect to srv-tramontana's PostgreSQL from the laptop,
# without the database listening on any public interface.
$ ssh -N -L 15432:127.0.0.1:5432 tramontana
$ psql -h 127.0.0.1 -p 15432 -U operator tramontana
# REMOTE (-R): temporarily expose a service on the laptop to the server,
# for example an artefact repository during a deployment test.
$ ssh -N -R 9000:127.0.0.1:9000 tramontana
# SOCKS (-D): a dynamic proxy for browsing as if you were on the internal network.
$ ssh -N -D 1080 tramontanaRead -L 15432:127.0.0.1:5432 like this: "open port 15432 on my machine; whatever arrives there, take it out of the other end of the tunnel and deliver it to 127.0.0.1:5432 as seen from the server". It is exactly what you need in order to administer the database with a graphical tool without opening 5432 in the firewall. -N means "do not run any command, just the tunnel".
An important warning: by default remote tunnels (-R) listen only on the server's localhost, and that is a good thing. Opening them on all interfaces requires GatewayPorts yes in sshd_config, which is a splendid way of bypassing your own firewall without noticing. Leave it as it is.
- File transfer:
scp, sftp and rsync
scp, sftp and rsync| Tool | Its status today | When to use it |
|---|---|---|
scp |
Discouraged: its protocol had security problems and in OpenSSH 9 it already uses SFTP underneath | Quick one-off copies |
sftp |
The official replacement | Interactive sessions, automation with -b |
rsync -e ssh |
The best one for volume | Synchronising, resuming, filtering, verifying |
$ rsync -avz --partial --progress -e ssh \
/home/operator/data/bookings.csv tramontana:/srv/tramontana/backups/
sending incremental file list
bookings.csv
1,248 100% 1.19MB/s 0:00:00 (xfr#1, to-chk=0/1)--partial keeps what has been transferred if the connection drops, and --progress tells you whether it is worth waiting. Combined with the ControlPersist from section 6, a repeated rsync does not even renegotiate the encryption.
- Persistent sessions with
tmux
tmuxIf your connection drops halfway through an apt upgrade or a deploy.sh, the process receives SIGHUP and dies half-finished. A deployment interrupted between the release rsync and the systemctl restart leaves the application in a state nobody designed.
$ tmux new -s deploy
$ ./deploy.sh --version 3.2.2 # the connection drops...
$ ssh tramontana
$ tmux attach -t deploy # ...and here it still is, runningtmux (or screen, which is older) keeps the session on the server, independent of your connection. Professional rule: every long operation in production is launched inside tmux. It is free and it prevents the kind of incident people talk about for years.
- Auditing: the 47 attempts from 203.0.113.44
Everything sshd does ends up in the journal and in /var/log/auth.log.
$ sudo journalctl -u ssh --since "-24h" | grep -c 'Failed password'
47
$ sudo journalctl -u ssh --since "-24h" | grep 'Failed password' | tail -1
Aug 18 03:14:55 srv-tramontana sshd[2843]: Failed password for invalid user admin from 203.0.113.44 port 51288 ssh2
$ sudo lastb -F | head -2
root ssh:notty 203.0.113.44 Tue Aug 18 03:14:52 2026 - Tue Aug 18 03:14:52 (00:00)
admin ssh:notty 203.0.113.44 Tue Aug 18 03:14:55 2026 - Tue Aug 18 03:14:55 (00:00)lastb reads /var/log/btmp, the record of failed attempts (last reads wtmp, the successful ones). With what you learned in Module 3, the summary by IP and by attempted username comes out in one line:
$ sudo lastb | awk '{print $3}' | sort | uniq -c | sort -rn
47 203.0.113.44
2 10.0.2.77
$ sudo lastb | awk '{print $1}' | sort | uniq -c | sort -rn | head -3
19 root
11 admin
9 postgresInterpretation: 47 attempts from a single IP, trying root, admin, postgres and ubuntu in three minutes. This is not somebody getting their password wrong: it is a brute-force bot, one of the thousands that sweep the Internet. The two attempts from 10.0.2.77 come from the internal network and are probably Luis mistyping.
The good news: with PasswordAuthentication no you have just turned those 47 attempts into harmless noise, because the route they were trying no longer exists. The bad news: the bot keeps calling, it consumes resources, it dirties your logs and it will still be there tomorrow with another technique. Blocking it at the door — and automating the blocking — is exactly the job of the next lesson.
Security and compliance warning: srv-tramontana processes guests' personal data, so remote access is a control subject to the GDPR. In a real environment that means named accounts (never shared ones), a record of who logs in and when with an agreed retention period, periodic review of authorized_keys to withdraw the keys of people who have left, and review of the change by the security officer. And the analysis of access logs, which identifies working people, has legal limits: it is done for security purposes and under the notified policy, not to keep anybody under surveillance.
Common Mistakes and Tips
- Enabling
PasswordAuthentication nowithout having tested the key. The classic that leaves people locked out of production servers. Test withssh -o PreferredAuthentications=publickeyfirst, and only then reload. - Wrong permissions in
~/.ssh. SSH silently (or almost silently) ignores keys andauthorized_keysfiles that are too open.700for the directory,600for private keys andauthorized_keys. If something "does not work for no reason",ssh -vvvtells you in theAuthentications that can continueline. - Deleting the whole
known_hostswhen faced with a changed-fingerprint warning. Investigate the cause and usessh-keygen -R <host>. systemctl restart sshinstead ofreload. With a broken configuration,restartlocks you out;reloadkeeps existing sessions alive.- Copying the private key to the servers "so I can hop from one to the other". The private key does not leave your laptop: that is what
ProxyJumpis for and, with reservations, the agent. - Forgetting that the
AllowGroupsgroup has to exist. If you writeAllowGroups sshusersand the group is empty, nobody gets in. Create it and add the people before reloading. - Tip: document every key in
authorized_keyswith its-Ccomment (person, machine, date) and review it every quarter. An orphaned key is an open door with a forgotten name on it.
Exercises
- Restricted access for backups. Tramontana's NAS (10.0.2.90) must run
/home/operator/scripts/backup_tramontana.sh --read-onlyon the server, and nothing else. Write the exactauthorized_keysline and justify each option. - A tunnel to the database. Marta wants Luis to query PostgreSQL from
laptop-luiswith a graphical tool, without 5432 being exposed. Give the command, explain each parameter and say what is configured in the tool. - Recovering from a disastrous change. You apply
AllowGroups admins(a group that does not exist) and reload. You have one SSH session open and the VM console. Detail the diagnosis and the repair, and which step of the procedure would have saved you the scare.
Solutions
1. A single line, with a double lock — who and from where — plus confinement of the command:
from="10.0.2.90",restrict,command="/home/operator/scripts/backup_tramontana.sh --read-only" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...tYu nas-backups@tramontana-2026-08
from="10.0.2.90"— even if the NAS's key is stolen, it only works from that IP. It is an extra layer that costs nothing.restrict— disables tunnels, agent forwarding, X11 and pty allocation in one go. It is the safe default: instead of forbidding things one by one, everything is forbidden and only what is needed is enabled.command="..."— that command runs whatever happens; if the NAS asks forbash, it gets the script all the same. The original stays inSSH_ORIGINAL_COMMAND, so the script could inspect it, but never run it blindly.- The final comment identifies the key for the quarterly review.
With those three options, a compromise of the NAS does not give you a shell on srv-tramontana: at most it gives a read-only backup run from one specific IP.
2. A local tunnel from Luis's laptop:
$ ssh -N -f -L 15432:127.0.0.1:5432 [email protected]-L 15432:127.0.0.1:5432— opens 15432 onlaptop-luis; whatever arrives there goes out through the tunnel and is delivered to127.0.0.1:5432from the server's point of view. The database still listens only onlocalhost.-N— do not run any remote command: we only want the tunnel.-f— go into the background once authenticated, so you get your terminal back.
In the graphical tool, Luis configures host 127.0.0.1, port 15432, his username and his database. What this protects: PostgreSQL is not exposed to the network, authentication is controlled by SSH (with a key, not a password) and the traffic is encrypted. What it does not protect: if Luis's laptop is compromised, the attacker has the same access; and any local user of that laptop can use 15432, so the strict form would be -L 127.0.0.1:15432:127.0.0.1:5432.
3. Diagnosis and repair from the session that is still open (or from the VM console):
$ sudo sshd -T | grep allowgroups
allowgroups admins
$ getent group admins || echo "the group does NOT exist"
the group does NOT exist
$ sudo journalctl -u ssh -n 5 --no-pager
sshd[3102]: User operator from 10.0.2.50 not allowed because none of user's groups are listed in AllowGroups
Two possible repairs, depending on what you meant to do:
$ sudo sed -i 's/^AllowGroups admins/AllowGroups sshusers/' /etc/ssh/sshd_config.d/60-tramontana-hardening.conf
$ sudo sshd -t && sudo systemctl reload ssh # alternative: groupadd -f admins && usermod -aG admins operator
$ ssh [email protected] 'echo OK'
OKThe step that would have saved you the scare is step 3 of the procedure: sudo sshd -T shows the effective configuration, and checking getent group admins before reloading would have revealed in two seconds that the group did not exist. sshd -t does not catch it, because the syntax is perfectly valid: the file is well written and says exactly what you did not want. That is the difference between validating the syntax and validating the intent — and the reason the second open session is not negotiable.
Conclusion
srv-tramontana's front door now has a lock. You have understood the protocol's three phases and why the password never travels in the clear; you know what the fingerprint you are asked to accept the first time really means and how to react when it changes; you have an ed25519 pair with a passphrase, an agent that expires it after four hours and a ~/.ssh/config that multiplexes connections and hops with ProxyJump instead of exposing the agent. On the server, a file in sshd_config.d/ forbids root, removes password authentication, restricts access to the sshusers group and records in VERBOSE mode the fingerprint of every key that logs in — all applied with sshd -t, sshd -T, reload and three open sessions, because you never close the door you are coming in through. And you reach the database through a -L tunnel without exposing a single extra port.
You have also put a name to the noise: 47 attempts from 203.0.113.44 trying root, admin, postgres and ubuntu in three minutes. Against that particular IP you have won — there are no passwords left to guess — but the bot keeps ringing the bell, and tomorrow it will try 8080, which is still wide open to anybody who reaches the machine, just as PostgreSQL would be if it ever listened outside localhost. SSH was one service; what is missing is a policy about what may even speak to this server. That is lesson 06-03: Firewalls and Perimeter Security, where you will get to know netfilter and nftables from the inside, bring up ufw in the right order so as not to lose your session, write a complete ruleset for Tramontana, tune the network sysctl settings with an impact on security and set fail2ban to block 203.0.113.44 automatically, along with everyone who comes after it.
Linux Course: From Beginner to System Administrator
Module 1: Introduction to Linux
- What Is Linux?
- History of Linux
- Linux Distributions
- Installing Linux
- First Contact with the System
- The Linux File System Structure
Module 2: Basic Linux Commands
- Introduction to the Command Line
- Getting Help and System Documentation
- Navigating the File System
- File and Directory Operations
- Viewing and Editing Files
- Hard and Symbolic Links
- File Permissions and Ownership
Module 3: Advanced Command-Line Skills
- The Shell Environment: Variables, Aliases and History
- Using Wildcards and Regular Expressions
- Searching Files and Content: find, locate and grep
- Pipes and Redirection
- Text Processing: cut, sort, uniq, sed and awk
- Process Management
- Scheduling Tasks with Cron
- Networking Commands
Module 4: Shell Scripting
- Introduction to Shell Scripting
- Variables and Data Types
- Script Input, Output and Arguments
- Control Structures
- Functions and Libraries
- Debugging and Error Handling
- Production Scripts: Best Practices
Module 5: System Administration
- User and Group Management
- sudo and Special Permissions
- Package Management
- Disk Management
- systemd and Service Management
- System Logs: journald and syslog
- System Monitoring and Performance Tuning
- Backup and Restore
Module 6: Networking and Security
- Network Configuration
- SSH and Remote Access
- Firewalls and Perimeter Security
- Intrusion Detection Systems
- Secrets Management and TLS Certificates
- Securing Linux Systems
Module 7: Advanced Topics
- The Boot Process and System Recovery
- Advanced Diagnostics: strace, perf and eBPF
- Linux Kernel Tuning
- Virtualization with Linux
- Linux Containers and Docker
- Automation with Ansible
- High Availability and Load Balancing
