Overview

Theory Areas

User Management

The idea. A new Contabo VM comes with just one account, root, which is allowed to do anything. Every attacker on the internet knows it exists, so it’s the first name they try. To close that obvious attack path I:

  • made an everyday account of my own,

  • let it borrow root’s powers only when asked, with sudo,

  • made it log in with an SSH Key instead of a password, and

  • blocked root from logging in over SSH at all.

That leaves exactly one account that can get in, and only with its key.

Where it fits. This account is the front door. Everything after it (the firewall, web server, wiki, git and backups) is done while logged in as it. The programs we add later each run as a user of their own too, so the ideas here come up again and again. If the front door ever fails, the Contabo Control Panel is the back way in, which is why that account should have MFA turned on.

Where it lives in the OS. The kernel doesn’t know names; to it a user is just a number, the UID (root is 0, my account is 1000). Every running Process carries the UID of whoever started it, and the kernel checks that number before letting the process touch a file. The names are only kept in plain text files: /etc/passwd (accounts), /etc/shadow (password hashes) and /etc/group (each Group, including the sudo group). SSH’s own settings live in /etc/ssh/sshd_config.d/, and my allowed keys in authorized_keys.

What I did.

  1. Logged in as root and made my account: adduser <you>, then added it to the sudo group with usermod -aG sudo <you>. The password I set here is the one sudo asks for.

  2. Went back to my own machine (WSL) and copied my existing key to the new account with ssh-copy-id. One key works for every account, so there’s no need to make a new one.

  3. In a new window, logged in as myself and ran sudo whoami. It printed root, so sudo works.

  4. Created /etc/ssh/sshd_config.d/10-hardening.conf containing PermitRootLogin no and PasswordAuthentication no.

  5. Checked the file for mistakes with sudo sshd -t, restarted SSH with sudo systemctl restart ssh, then tested from a third window: ssh <you>@<server-ip> gets in, ssh root@<server-ip> is refused.

If you are locked out. Remember your VM password, protect your Contabo password, and turn VNC off after use. To get back in:

  1. Sign in to the Contabo control panel.

  2. Turn on the VNC Console and copy its address, port and password.

  3. Connect with a VNC program (TigerVNC, RealVNC or UltraVNC).

  4. Log in as your own user with your VM password (VNC isn’t SSH, so the key doesn’t apply) and fix whatever locked you out.

  5. Turn VNC off again.

Lessons learned.

  • Keep the old window logged in while changing SSH settings. If the new settings are wrong, that open session is your way back in.

  • Test the way back in now, while nothing is broken. A rescue route you’ve never tried isn’t one you can count on.

  • Put your own settings in a Drop-in File under sshd_config.d/ instead of editing the main config.

  • If this goes from a practice system to a live one, add more Emergency Admin accounts so one lost login can’t lock everyone out.

Good sources.

Firewall & Security

The idea. A server on the internet gets knocked on all day by strangers. A Firewall is the fence that decides which of those knocks are even allowed to reach the server. Everything is shut by default, and only the doors we actually need are opened on purpose. The one door we can never shut is SSH, because that’s how we get in ourselves, so the SSH door gets its own guard: Fail2ban.

Layers of protection on our server.

  • The fence: UFW blocks every incoming Port except 22 (SSH). When the web server is added later, ports 80 (HTTP) and 443 (HTTPS) get opened here too.

  • The guard at the door: Fail2ban reads the login logs and bans any address that keeps failing to log in.

  • The lock: key-only login (from User Management) means that even a guest who is let through can’t guess their way in with a password.

Where it lives in the OS. The filtering itself happens inside the kernel, done by netfilter. UFW does no filtering of its own; it’s a friendly tool that writes netfilter’s rules for us. Fail2ban is a Service that runs in the background. When it spots an attacker, it asks UFW to add a block rule. Their settings live in /etc/ufw/ and /etc/fail2ban/.

What I did.

  1. Installed UFW (sudo apt install ufw).

  2. Set a Default Deny policy: block all incoming, allow all outgoing, then allow SSH with sudo ufw allow 22/tcp.

  3. Turned it on (sudo ufw enable) and checked the rules with sudo ufw status verbose.

  4. Opened a new terminal window to prove I could still log in.

  5. Installed Fail2ban (sudo apt install fail2ban).

  6. Wrote my settings in /etc/fail2ban/jail.local, turning on the sshd Jail with banaction = ufw, maxretry = 5 and bantime = 1h.

  7. Started it with sudo systemctl enable --now fail2ban and confirmed the jail was running with sudo fail2ban-client status sshd.

Lessons learned.

  • Order matters. If you turn the firewall on before allowing SSH, you lock yourself out of your own server. After that, the only way back in is the VNC Console in the Contabo panel. Keep a second window logged in until you’ve proven a fresh login still works.

  • Never edit jail.conf. A package update overwrites it. Your own settings go in jail.local, which is read on top of it and left alone by updates.

  • A firewall can’t protect a door it has to leave open. That’s why SSH needs extra defenses (Fail2ban and key-only login) behind the firewall.

Good sources.