iptables and nftables Firewall Setup on a VPS: A Practical Guide

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
Taskiptablesnftables
List rulesiptables -L -n -vnft list ruleset
Flush everythingiptables -Fflush ruleset
Default policy-P INPUT DROPpolicy drop;
Allow port-A INPUT -p tcp --dport 80 -j ACCEPTtcp 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