A server with a public IP receives its first login attempts within minutes of booting. Most attacks are automated and unsophisticated, which means a short list of standard measures stops nearly all of them: a firewall that exposes only the ports you serve, SSH that accepts keys only, a tool that bans repeat offenders, automatic security updates, and services that run as unprivileged users. This lesson walks through each in the order you would apply them to a fresh machine.
Every listening port, every enabled service and every account is a way in. Start by seeing what you have:
sudo ss -tulpn # listening ports
systemctl list-units --type=service --state=running
awk -F: '$7 !~ /nologin|false/ {print $1}' /etc/passwd # accounts that can log inStop and disable anything the server does not need, and bind internal services (databases, caches, admin dashboards) to 127.0.0.1 so they are unreachable from outside even before the firewall.
ufw is a front end for the kernel's netfilter (nftables/iptables) that ships with Ubuntu and Debian. The strategy is deny inbound by default, allow specific services.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH # or: sudo ufw allow 22/tcp — do this BEFORE enabling
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status numberedStatus: active
To Action From
-- ------ ----
[ 1] OpenSSH ALLOW IN Anywhere
[ 2] 80/tcp ALLOW IN Anywhere
[ 3] 443/tcp ALLOW IN AnywhereMore precise rules:
sudo ufw allow from 203.0.113.0/24 to any port 22 proto tcp # SSH only from the office
sudo ufw allow from 10.0.0.0/8 to any port 5432 # database from the private network
sudo ufw limit OpenSSH # rate-limit: 6 connections per 30 s per IP
sudo ufw deny from 198.51.100.7 # block one address
sudo ufw delete 3 # by number from status numberedAllow SSH before running ufw enable on a remote machine; enabling a default-deny firewall without it locks you out. On RHEL-family systems the equivalent is firewalld:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-allfail2ban watches log files for failed logins and adds temporary firewall rules against the source IP. The SSH jail is the essential one; jails exist for nginx, postfix and many others.
sudo apt install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local # never edit jail.conf directly# /etc/fail2ban/jail.local (relevant parts)
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8 203.0.113.0/24 # never ban yourself
[sshd]
enabled = true
[nginx-http-auth]
enabled = truesudo systemctl enable --now fail2ban
sudo fail2ban-client status # list jails
sudo fail2ban-client status sshd # banned IPs and counts
sudo fail2ban-client set sshd unbanip 198.51.100.7
sudo tail -f /var/log/fail2ban.logOn systemd-only systems where /var/log/auth.log does not exist, set backend = systemd in [DEFAULT] so fail2ban reads the journal.
Unpatched software is the second most common way in after weak credentials. Debian and Ubuntu ship unattended-upgrades; RHEL-family uses dnf-automatic.
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades # answer Yes
cat /etc/apt/apt.conf.d/20auto-upgrades # should show both lines set to "1"
sudo unattended-upgrade --dry-run --debug # testsudo dnf install dnf-automatic
sudo systemctl enable --now dnf-automatic-install.timerKernel updates still need a reboot; needrestart or /var/run/reboot-required tells you when.
sudo, and never log in as root: adduser alice && usermod -aG sudo alice.sshd_config (previous SSH lesson).useradd --system --shell /usr/sbin/nologin api.sudo: getent group sudo and sudo -l./etc/sudoers.d/ that were added for convenience and forgotten.sudo find / -type f -perm -4000 2>/dev/null # setuid binaries; know what is on the list
sudo find / -xdev -type f -perm -o+w 2>/dev/null # world-writable files
sudo chmod 600 /etc/app/env # secrets readable by owner only
sudo systemd-analyze security api # sandboxing score for a unitufw over SSH without allowing port 22 first.jail.conf instead of jail.local, then losing changes on the next package update.ignoreip, banning your own office, and blaming the server.ufw inactive.What must you do before running `sudo ufw enable` on a remote server?
ss -tulpn.ufw default deny incoming, allow SSH (with limit) before enable, then only served ports; firewalld on RHEL.fail2ban with the sshd jail bans brute-force sources; configure in jail.local and set ignoreip.unattended-upgrades or dnf-automatic; reboot for kernel updates.600 secrets and systemd-analyze security complete the baseline.Next lesson: Linux for Web Developers: Deploying a Node.js App with Nginx — put everything together to serve a real application over HTTPS.