Every service you expose on a VPS is a candidate for abuse, and the first line of defense is a host firewall. On modern Debian and Ubuntu systems that means nftables, the successor to iptables, though both still run in production — and both are configured from the command line with no GUI in sight. This guide covers a practical, stateful ruleset in nftables, the equivalent iptables syntax, and how to make the rules survive a reboot.
Before writing any rules, find out what is actually listening on the network. Most breaches on small VPSes start with an unknown service exposed on 0.0.0.0. Run ss -tulpn and note every port, then decide which ones the public internet genuinely needs. Hardware matters too — a firewall is only part of the setup, so compare VPS plans on our comparison table and make sure the provider you choose gives you full root access to the host firewall rather than a locked-down panel.
nftables: The Modern Default
nftables replaces the old iptables toolchain with a single framework and a cleaner syntax. Check what your distribution ships with: nft --version; on Debian 12 and Ubuntu 22.04+ it is the kernel’s native firewall front end. The classic mistake is mixing iptables rules with nftables rules and wondering why the policy seems to ignore half of them — pick one backend and stick with it.
# /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
iif "lo" accept
ct state invalid drop
tcp dport 22 accept # SSH
tcp dport 80 accept # HTTP
tcp dport 443 accept # HTTPS
icmp type echo-request accept
}
chain forward { type filter hook forward priority filter; policy drop; }
chain output { type filter hook output priority filter; policy accept; }
}
The policy drop on the input chain is the key decision: everything not explicitly allowed is rejected at the kernel level. Stateful tracking via ct state established,related accept lets responses to your outbound connections back in without opening a wide port range, and ct state invalid drop disposes of malformed packets. Activate it with systemctl enable --now nftables and check the result with nft list ruleset.
The Same Ruleset in Legacy iptables
If you are stuck on an older distribution or a provider image that still wires iptables into its boot scripts, the equivalent rules look like this — note the order matters: allow established traffic first, then open specific ports, then set the default policy to DROP as the last command:
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -P INPUT DROP
| Task | iptables | nftables |
|---|---|---|
| List rules | iptables -L -n -v | nft list ruleset |
| Flush everything | iptables -F | flush ruleset |
| Default policy | -P INPUT DROP | policy drop; |
| Allow port | -A INPUT -p tcp --dport 80 -j ACCEPT | tcp dport 80 accept |
Once your ruleset is stable and your SSH session is still alive, the practical payoff is immediate: port scans drop, brute-force connections time out instead of getting responses, and your mail or web services keep working untouched. If you are moving a busy application to a VPS with this kind of protection in mind, Cloudways managed VPS hosting includes a preconfigured firewall layer so you can start with sane defaults and layer your own rules on top.
Persisting Rules and Docker Gotchas
Rules typed at the shell die with the reboot unless you persist them. For nftables, save the active ruleset to the config file and enable the service:
nft list ruleset > /etc/nftables.conf
systemctl enable nftables
For iptables, install iptables-persistent and run netfilter-persistent save. Two gotchas bite everyone eventually. First, Docker manipulates iptables directly and inserts its own chains (DOCKER, DOCKER-USER), so a blanket INPUT DROP will not block published container ports — filter in the DOCKER-USER chain instead. Second, always keep an out-of-band path: when testing a new ruleset from a remote SSH session, schedule a nft flush ruleset a minute out with at or systemd-run so a typo cannot lock you out permanently.
Test from the outside with nmap -Pn your-server-ip or an online port checker: closed ports should show as filtered, not open, and your SSH port should still respond. Firewalls are also the enforcement point for tools like fail2ban, which injects temporary bans into nftables or iptables when it detects brute-force attempts — pairing the two gives you an automated response on top of a solid baseline. For the hardware that runs this stack reliably, see the full specs and pricing on our comparison table and pick a plan with enough headroom for connection tracking.

Leave a Reply
You must be logged in to post a comment.