A server on the internet gets scanned before you have finished reading its welcome email. Most of that traffic is untargeted: bots working through address ranges, trying default logins and known vulnerabilities on anything that answers. You do not need to be an interesting target for this to matter. You just need an IP address.
This guide walks through the tools that make up a reasonable baseline: a firewall, an intrusion prevention tool to deal with repeat offenders, and where heavier network inspection tools fit in. None of it is exotic, and none of it makes a server secure in any permanent sense. It reduces the obvious risks so you can focus on the ones specific to what you are actually running.
Commands below are illustrative and assume a Debian or Ubuntu based system. Check them against your own distribution and current documentation before running them. See our disclaimer for how to treat guide content like this.
Think in layers, not in one big lock
No single tool covers everything, and treating one as sufficient is how gaps get missed. The pattern worth adopting is layered defence: a firewall to control what can reach the server at all, a log-watching tool to deal with the traffic that does get through and starts misbehaving, and for higher-risk workloads, something inspecting traffic content rather than just its source and port. Each layer catches what the one before it missed.
Layer 1: a firewall with a default-deny policy (UFW)
The firewall is the first decision point: what is allowed to reach the server at all. On Ubuntu and Debian, UFW (uncomplicated firewall) is the usual starting point, since it wraps the kernel's own packet filtering in a syntax that does not require memorising iptables rules.
The principle that matters more than any specific command is default-deny: block everything inbound by default, then explicitly allow only the ports you actually use. Not default-allow with a list of things to block, which leaves you exposed to anything you forgot to add.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enable
Confirm your SSH rule is in place and working before enabling the firewall, and keep your existing session open until you have tested a fresh connection. Locking yourself out of a remote server over SSH is one of the more common and avoidable mistakes here.
From there, add rules for whatever else the server needs to expose, and nothing more:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status verbose
If the server runs Docker, it is worth knowing that Docker manipulates iptables directly and can quietly bypass UFW rules for published container ports. That is a common source of "I blocked this port but it is still reachable" confusion, and worth checking specifically rather than assuming UFW's view of the world matches what is actually exposed.
Layer 2: fail2ban, the long-standing default
A firewall controls what can connect. It does not care how many times the same IP address just failed to log in. That is the gap fail2ban fills: it watches log files for patterns of repeated failure, most commonly SSH login attempts, and temporarily bans the offending IP at the firewall level after a threshold is crossed.
It is mature, lightweight, and well understood, which counts for a lot in a security tool. A basic SSH jail can be running in a few minutes:
sudo apt install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Edit jail.local rather than the original jail.conf, since the latter gets overwritten on package updates. The default configuration already covers SSH; jails for other services (a web server, a mail server) can be enabled the same way once you know which log files and failure patterns to watch.
Its limitation is also its design: fail2ban reacts to what it sees in your own logs. It has no idea whether an IP has been hammering a thousand other servers overnight. Every server running it starts from a blank slate.
Layer 2, alternative: CrowdSec
CrowdSec does the same basic job as fail2ban, watching logs and reacting to abusive behaviour, but adds a community threat intelligence layer on top. Detections from participating servers worldwide feed into a shared blocklist, so a server running CrowdSec can start blocking an IP based on behaviour seen elsewhere, not just behaviour aimed at that one machine.
It is also architecturally different: CrowdSec separates detection (the "agent," reading logs and deciding what looks malicious) from enforcement (a separate "bouncer" component that actually blocks traffic, commonly via iptables or nftables). That separation is more flexible, since a bouncer can also work at the reverse proxy or WAF level, but it is more moving parts than fail2ban's single-process model.
Fail2ban vs CrowdSec, in short: fail2ban is simpler, self-contained, and reacts only to what happens on your own server. CrowdSec adds community-sourced threat data and a more flexible architecture, at the cost of an extra component (the bouncer), a dependency on an external service for blocklist data, and a steeper learning curve. Neither is strictly better; they suit different appetites for complexity. Running both on the same jails is usually redundant and worth avoiding.
Worth knowing before adopting CrowdSec: it depends on connectivity to CrowdSec's central API for the shared blocklist to function fully, which is a dependency fail2ban simply does not have. For most small, single-purpose servers, a well-configured fail2ban is entirely adequate. CrowdSec earns its extra complexity on servers exposed to more varied or determined traffic, or where you are already managing several machines and want consistent, shared protection across them.
Layer 3: network-level inspection with Snort or Suricata
Firewalls and fail2ban-style tools work at the level of connections and login attempts. Snort and Suricata operate a level deeper: they inspect the actual content of network traffic against a ruleset, looking for known attack signatures, protocol anomalies, and suspicious patterns that a firewall rule would never see, since the traffic is technically "allowed" by port and source.
Both can run in two modes: intrusion detection (IDS), which logs and alerts but does not block, or intrusion prevention (IPS), which can drop matching traffic in real time. Suricata is the newer of the two, with native multi-threading that generally handles higher traffic volumes more comfortably than Snort on the same hardware, and built-in support for extracting files and TLS metadata from traffic for further analysis.
This is the layer to treat as optional for a typical small VPS running a handful of services. Deep packet inspection is genuinely resource-intensive, generates a meaningful volume of alerts that need someone to actually review them, and is easy to under-tune into either noise or blind spots. It earns its place on servers handling sensitive data, facing sustained targeted interest, or where a compliance requirement calls for it specifically. For everything else, a well-configured firewall and fail2ban or CrowdSec covers the realistic risk far more cheaply.
Tools are half the picture
None of the above helps if the software behind it is out of date. A firewall does not patch a known vulnerability in a web application; it just controls who can reach it. A few habits matter as much as any tool listed above:
- Automatic security updates for the operating system, so known vulnerabilities get patched without depending on someone remembering to run
apt upgrade - Actually looking at fail2ban or CrowdSec logs occasionally, rather than installing them once and assuming they are working
- Keeping SSH access itself hardened, which we cover separately in our guide on setting up SSH securely on a new VPS
- Backups that are tested and kept separate from the server they protect, since none of the tools above defend against a mistake, not just an attacker
Security tooling reduces the chance of a bad outcome. It does not replace the ordinary discipline of patching, reviewing, and backing things up.
Where this fits into ongoing security
A firewall, fail2ban or CrowdSec, and reasonable patching habits will handle the overwhelming majority of what actually hits a typical server: automated scanning and opportunistic abuse, not a determined targeted attacker. Snort or Suricata, and anything beyond, are for a smaller set of situations where that baseline genuinely is not enough.
None of this is a state you configure once and leave. Threats, software, and your own setup all change over time, and treating security as an ongoing process rather than a checklist is what actually keeps the risk down. If you would like a second opinion on what is already running on your server, or help deciding which of these layers are worth the added complexity for your situation, that is exactly the kind of thing we advise on at Techster Consulting.