Modern Linux distributions filter packets with nftables in the kernel. You can write nftables rules directly, or use a front end: UFW on Ubuntu/Debian, firewalld on AlmaLinux/Rocky. All three end up as kernel rules — pick one and don’t mix them.

The safety net

Before changing firewall rules on a remote server:

  1. Know how to reach the VNC console in your control panel.

  2. Schedule an automatic rollback while you test:

    (sleep 180 && nft flush ruleset) &     # remove after confirming access: kill %1

Option 1: nftables directly

A complete server ruleset in /etc/nftables.conf:

#!/usr/sbin/nft -f
flush ruleset

table inet filter {
  set admin_ips {
    type ipv4_addr
    elements = { 203.0.113.50 }
  }

  chain input {
    type filter hook input priority 0; policy drop;

    ct state established,related accept
    ct state invalid drop
    iif "lo" accept

    # ICMP (ping, path MTU discovery)
    ip protocol icmp accept
    ip6 nexthdr icmpv6 accept

    # SSH: admins only, with rate limit
    tcp dport 22 ip saddr @admin_ips ct state new limit rate 10/minute accept

    # Web
    tcp dport { 80, 443 } accept
    udp dport 443 accept

    # Log the rest (rate-limited), then drop by policy
    limit rate 5/minute log prefix "nft-drop: "
  }

  chain forward {
    type filter hook forward priority 0; policy drop;
  }

  chain output {
    type filter hook output priority 0; policy accept;
  }
}

Apply and enable at boot:

nft -c -f /etc/nftables.conf      # check syntax
nft -f /etc/nftables.conf
systemctl enable nftables
nft list ruleset

Add an admin IP without reloading: nft add element inet filter admin_ips { 198.51.100.7 }.

Option 2: UFW (Ubuntu, Debian)

UFW is the simplest way to express the same policy:

apt install -y ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow from 203.0.113.50 to any port 22 proto tcp
ufw allow 80,443/tcp
ufw allow 443/udp
ufw enable
ufw status verbose

More UFW patterns are in custom firewall rules for your VPS.

Option 3: firewalld (AlmaLinux, Rocky)

firewalld uses zones and services:

systemctl enable --now firewalld
firewall-cmd --permanent --add-service={http,https}
firewall-cmd --permanent --add-port=443/udp
# SSH only from an admin IP:
firewall-cmd --permanent --remove-service=ssh
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.50" service name="ssh" accept'
firewall-cmd --reload
firewall-cmd --list-all

--permanent writes the config; --reload applies it. Without --permanent, changes are lost on reload or reboot.

What about iptables?

iptables commands still work on most systems through a compatibility layer (iptables-nft) that writes nftables rules. Older guides are fine to follow, but new rulesets are clearer in native nftables syntax.

Common mistakes

  • Running two front ends (UFW and firewalld, or UFW plus a hand-written nftables.conf) — rules conflict and get overwritten.
  • Forgetting IPv6 — inet tables in nftables cover both; UFW handles both when IPV6=yes in /etc/default/ufw.
  • Docker bypassing rules — Docker manages its own chains; bind published ports to 127.0.0.1 or filter in DOCKER-USER.
  • Blocking all ICMP — breaks path MTU discovery and makes troubleshooting harder. Allow at least the essential types.

Test from outside

nmap -Pn -p 1-1024 YOUR_SERVER_IP

Only intended ports should be open. See protecting your VPS from port scans for more.

All our KVM VPS plans give you full control of the kernel firewall, plus console access if a rule goes wrong.

  • Firewall
  • nftables
  • Linux
  • Security