iptables basics
iptables is the classic Linux firewall tool: it decides which packets to let through and which to drop by running them through chains of rules. In this article you'll get familiar with chains and policies, learn to list the current rules, allow SSH and HTTP, lock everything else down with a default DROP policy, and make the ruleset survive a server reboot.
What you'll need
- An ForestsNet Cloud VPS running Ubuntu 22.04/24.04, Debian 11/12, or AlmaLinux/Rocky Linux 8/9.
- SSH access as
rootor a user withsudo.
Step 1. Check that iptables is installed
On Ubuntu and Debian, the iptables command is usually already there out of the box (as a wrapper over nftables — iptables-nft — but with the familiar syntax). Check the version:
iptables -VIf the command isn't found, install the package:
Ubuntu / Debian
sudo apt update
sudo apt install iptablesAlmaLinux / Rocky Linux
sudo dnf install iptables⚠️ Important: AlmaLinux and Rocky Linux use firewalld (on top of nftables) by default, not iptables — check whether it's active with systemctl status firewalld. firewalld and iptables can't run at the same time — they're two conflicting managers for the same netfilter subsystem. As of version 9, the iptables utilities themselves are officially deprecated on AlmaLinux/Rocky. The concepts and command syntax in this article (steps 2–5) hold for iptables on any distro, but as a way to actually configure the firewall on a production server, this article is written for Ubuntu/Debian; on AlmaLinux/Rocky, use firewalld instead.
Step 2. Understand chains and policies
iptables filters traffic through tables, and inside a table, through chains of rules. For a firewall you need the filter table (it's the default, so you don't need to specify it separately), which has three built-in chains:
- INPUT — packets addressed to the server itself: incoming connections over SSH, HTTP, and so on;
- OUTPUT — packets the server itself generates: outgoing requests;
- FORWARD — packets the server forwards on elsewhere. On a regular VPS (not a router), nothing goes through this chain.
Each built-in chain has a default policy — what to do with a packet if no rule in the chain matched it. The policy can only be ACCEPT (let it through) or DROP (silently discard it) — iptables won't accept REJECT as a chain policy, only as the target of an individual rule.
Until you've configured any rules, the policy on all three chains is ACCEPT — meaning everything is allowed. Check the current policies:
sudo iptables -L -nThe first line of each chain in the output looks like Chain INPUT (policy ACCEPT) — that's the current policy.
Step 3. List the current rules
For more detail — with packet/byte counters and interface names:
sudo iptables -L -v -n-L lists the rules (with no argument, all chains in the filter table), -v adds counters and interfaces, -n skips resolving addresses and ports into names (without it, the output can be noticeably slower).
To see line numbers — useful before deleting a specific rule:
sudo iptables -L -v -n --line-numbersA rule is deleted by its line number from that output and the chain name: sudo iptables -D <chain> <number> — for example, sudo iptables -D INPUT 3 deletes the third rule in the INPUT chain.
And to get the rules as ready-to-run iptables commands (handy for copying or comparing against what you're about to apply):
sudo iptables -SStep 4. Allow SSH and HTTP
Before turning on the DROP policy (step 5), add the rules that allow the traffic you need — otherwise, once DROP is on, the server becomes unreachable on absolutely everything, including SSH.
Allow traffic on the loopback interface (lo) — many local services rely on it:
sudo iptables -A INPUT -i lo -j ACCEPTAllow already-established connections and traffic related to them — otherwise responses to the server's own outgoing requests (to DNS or a package repository, for example) get cut off:
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPTNow allow SSH itself and HTTP:
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPTℹ️ Tip: if you've already changed the default SSH port (see the "SSH hardening" article), use your own port instead of 22. If the server also serves HTTPS, add port 443 the same way: sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT.
ℹ️ Tip: ufw does the same thing more briefly — ufw allow ssh and ufw allow 80/tcp. If the iptables syntax feels like overkill, check out the "Setting up the UFW firewall" article: it's a friendlier front-end over the same iptables/nftables engine, with simpler commands and ready-made application profiles.
Check that the rules were added:
sudo iptables -L -v -nStep 5. Turn on the default DROP policy
⚠️ Important: this is the riskiest step in the article — a mistake here can cut off your own SSH access. Don't close your current SSH session — open a separate, second connection to test with, and keep the VNC console in VMmanager 6 handy: it lets you get onto the server and fix the rules even if SSH is unreachable.
ℹ️ Tip: there's one more safety net — until you save the rules (step 6), rebooting the server will usually reset the policies back to ACCEPT. So even a completely broken configuration at this step can be fixed with a reboot from VMmanager 6, no OS reinstall required.
Once the rules from step 4 are in place, turn on default DROP for incoming traffic and forwarding:
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROPWe deliberately leave the OUTPUT policy at ACCEPT — filtering outgoing traffic (egress filtering) is useful, but it's easy to get wrong and break your own DNS, package updates, and the like; that's a separate, more advanced topic outside this article.
Because the ESTABLISHED,RELATED rule is already in place, your current SSH session won't drop — the DROP policy only applies to new connections that don't match any rule. Still, from that second, new session, check that SSH still works:
ssh [email protected]And check the resulting state:
sudo iptables -L -v -nThe output should show Chain INPUT (policy DROP) and Chain FORWARD (policy DROP), with the ACCEPT rules for loopback, ESTABLISHED/RELATED, SSH, and HTTP listed above them.
✅ Done: a new SSH connection opens, the site on port 80 responds, and everything else inbound gets dropped by default.
Step 6. Persist the rules: iptables-persistent / netfilter-persistent
The rules and policies you've configured live only in kernel memory — after a server reboot they're gone, and the default ACCEPT policies take over again. To make the rules survive a reboot, Ubuntu/Debian uses the iptables-persistent package, which installs the netfilter-persistent system service:
sudo apt update
sudo apt install iptables-persistentDuring installation, the package asks twice whether to save the current rules — once for IPv4, once for IPv6. Say yes both times: the IPv4 rules are exactly what you configured in steps 4–5, and an empty IPv6 ruleset won't cause any harm even if you don't use IPv6.
The rules are saved to /etc/iptables/rules.v4 (and /etc/iptables/rules.v6 for IPv6). Make sure the service that loads them at boot is enabled:
sudo systemctl enable --now netfilter-persistentIf you change the rules again later (say, to open another port), remember to save them again — otherwise the old ruleset comes back after a reboot:
sudo netfilter-persistent saveYou can check that the saved rules load correctly without rebooting the server:
sudo netfilter-persistent reloadℹ️ Tip: running sudo dpkg-reconfigure iptables-persistent at any time asks the same save-current-rules questions again, just like on first install.
(This method is for Ubuntu/Debian. On AlmaLinux/Rocky, as noted in step 1, the firewall is managed by firewalld by default, not iptables-persistent.)
What's next
You've set up a basic firewall by hand: chains, a default DROP policy, and the permissions you need (SSH, HTTP) now survive a reboot. You can reach the same result more briefly through ufw — a friendlier front-end over the same iptables/nftables engine. From here it's worth locking down SSH brute-forcing with the "Fail2ban" article, hardening SSH itself — "SSH hardening" — and checking your setup against the general "Server security checklist".