Linux12 min read

iptables essentials: a practical guide to Linux firewalling

linuxiptablesfirewallnetworkingsecurity

Every Linux server connected to the internet needs a firewall. iptables is the one that ships with virtually every distribution, and even if you eventually move to nftables or firewalld, understanding iptables helps you understand what those tools are actually doing underneath.

This guide covers the concepts and the commands. Not every option, but enough to be useful on a real server.

Prerequisites

  • A Linux server (Ubuntu, Debian, CentOS, any of them work)
  • Root or sudo access
  • A terminal and some patience

WARNING

Locking yourself out of your own server with a bad firewall rule is a rite of passage. It also sucks. If you’re working on a remote machine, keep a backup session open before you start changing rules.

What iptables does

iptables filters network traffic. It looks at each packet coming into or leaving your machine and decides whether to let it through, drop it, or do something else with it. The decisions are based on rules you define.

The name comes from the “tables” it uses to organize rules, and the “chains” within those tables that process packets in order.

Tables and chains

There are several tables, but you’ll mostly work with two:

filter is the default table. It handles what gets through and what doesn’t. It has three chains:

  • INPUT handles packets destined for your machine
  • FORWARD handles packets routed through your machine
  • OUTPUT handles packets your machine sends out

nat handles address translation. You need it for port forwarding and masquerading. It has PREROUTING, POSTROUTING, and OUTPUT chains.

For most server work, you’re writing rules in the filter table’s INPUT chain. That’s where you decide which services the outside world can reach.

Checking current rules

Before changing anything, see what’s already there:

sudo iptables -L -n -v

The -n flag shows IP addresses and ports as numbers instead of trying to resolve hostnames. The -v adds packet counters. On a fresh install, you’ll probably see empty chains with a default ACCEPT policy.

If you want to see the raw commands that would recreate your current ruleset:

sudo iptables-save

This output is useful for backing up your rules or understanding exactly what’s configured.

Rule syntax

A basic iptables rule follows this pattern:

sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT

Breaking that down:

  • -A INPUT appends the rule to the INPUT chain
  • -p tcp matches TCP protocol
  • --dport 22 matches destination port 22
  • -j ACCEPT jumps to the ACCEPT target (allow the packet)

You can insert rules at a specific position with -I instead of -A:

sudo iptables -I INPUT 1 -p tcp --dport 80 -j ACCEPT

That inserts the rule at position 1 in the chain, so it gets evaluated first.

Common targets

  • ACCEPT allows the packet
  • DROP silently discards it
  • REJECT discards it and sends an error back to the sender

DROP is usually what you want for a firewall. REJECT tells the other side you’re not listening, which leaks information about your server being up and running.

Setting up basic rules

Here’s a starting ruleset for a web server:

# Allow established connections (important, don't skip this)
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Allow loopback
sudo iptables -A INPUT -i lo -j ACCEPT

# Allow SSH
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Allow HTTP and HTTPS
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# Drop everything else
sudo iptables -P INPUT DROP

The first rule is important. It allows packets that belong to connections you already initiated. Without it, your server would accept the initial SSH connection but drop the responses, and you’d get nowhere.

The loopback rule lets your machine talk to itself, which a lot of services depend on.

The last line sets the default policy to DROP. Anything not explicitly allowed gets blocked. That’s the secure approach: deny by default, allow what you need.

Saving rules

iptables rules are lost on reboot unless you save them. On Debian/Ubuntu:

sudo iptables-save > /etc/iptables/rules.v4

On CentOS/RHEL:

sudo service iptables save

Or install iptables-persistent to handle this automatically:

sudo apt install iptables-persistent

During installation it will ask if you want to save current rules. Say yes.

Deleting rules

To delete a rule, you need to know its position in the chain:

sudo iptables -L INPUT --line-numbers

Then delete by number:

sudo iptables -D INPUT 3

That deletes the third rule in the INPUT chain.

To flush all rules and start over:

sudo iptables -F

Be careful with this. If your default policy is DROP and you flush all rules, you just blocked everything.

Blocking an IP address

To drop all traffic from a specific IP:

sudo iptables -A INPUT -s 192.168.1.100 -j DROP

To block a whole subnet:

sudo iptables -A INPUT -s 10.0.0.0/8 -j DROP

You can also block outbound traffic to an IP:

sudo iptables -A OUTPUT -d 192.168.1.100 -j DROP

Rate limiting

SSH brute-force attacks are constant on any public server. Rate limiting helps:

sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --set
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 4 -j DROP

This allows 3 new SSH connections per 60 seconds from the same IP. The fourth attempt gets dropped. Adjust the numbers to taste.

Port forwarding

To forward traffic from port 8080 on your server to port 80 on another machine:

sudo iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.50:80
sudo iptables -A FORWARD -p tcp -d 192.168.1.50 --dport 80 -j ACCEPT

You also need to enable IP forwarding:

echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward

To make it permanent, edit /etc/sysctl.conf and uncomment the net.ipv4.ip_forward=1 line.

Logging

You can log dropped packets for debugging:

sudo iptables -A INPUT -j LOG --log-prefix "IPTABLES-DROP: "

Logs go to /var/log/kern.log or /var/log/messages depending on your distribution. Be careful with this in production, logging every dropped packet can fill your disk fast if you’re under attack.

A better approach is to log only specific traffic:

sudo iptables -A INPUT -p tcp --dport 23 -j LOG --log-prefix "TELNET-ATTEMPT: "
sudo iptables -A INPUT -p tcp --dport 23 -j DROP

Listing and inspecting rules

Show rules with packet counts:

sudo iptables -L -n -v

Show rules with line numbers:

sudo iptables -L INPUT -n --line-numbers

Show the raw commands:

sudo iptables-save

Show rules for a specific chain:

sudo iptables -L OUTPUT -n -v

A complete example

Here’s a ruleset for a server running SSH, a web server, and a database that should only be accessible from the local network:

# Flush existing rules
sudo iptables -F

# Default policies
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT

# Allow established connections
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Allow loopback
sudo iptables -A INPUT -i lo -j ACCEPT

# Allow SSH
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Allow HTTP and HTTPS
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# Allow MySQL from local network only
sudo iptables -A INPUT -p tcp --dport 3306 -s 192.168.1.0/24 -j ACCEPT

# Log and drop everything else
sudo iptables -A INPUT -j LOG --log-prefix "IPTABLES-DROP: "
sudo iptables -A INPUT -j DROP

This gives you a server that accepts SSH, web traffic, and local database connections, blocks everything else, and logs what it blocks.

Common mistakes

Forgetting the established connections rule. You add it, things work, you think it’s optional. It’s not. Without it, return traffic for your outbound connections gets blocked and everything breaks.

Setting DROP policy before adding ACCEPT rules. Do it in the other order. Set your allow rules first, then set the default policy. Otherwise you lock yourself out the moment you set the policy.

Not saving rules. You spend an hour crafting the perfect ruleset, the server reboots for a kernel update, and everything is gone. Save your rules.

Blocking yourself. If you’re connected via SSH and change the SSH port rule, you’re done. Keep a backup session open or use a console connection.

When to use something else

iptables works, but it’s showing its age. nftables is its replacement in newer kernels and has a cleaner syntax. firewalld provides a higher-level interface that’s easier to manage on distributions that support it. UFW (Uncomplicated Firewall) is Ubuntu’s attempt to make iptables less painful.

For a single server with simple needs, any of these work fine. For complex setups with lots of rules, nftables scales better. For servers managed by configuration tools like Ansible or Puppet, the tool usually handles whichever firewall backend you have.

But understanding iptables matters even if you use something else. The concepts are the same. nftables still has chains and rules. firewalld still uses netfilter under the hood. When something breaks or you need to debug a networking issue, knowing how packets flow through iptables helps you figure out where the problem is.

Remarks

iptables looks intimidating until you use it a few times. The commands are verbose, but the logic is straightforward: match packets, decide what to do with them. Start with a simple ruleset, test it, save it, and build from there. The examples above cover the common cases. For everything else, man iptables has the full list of match extensions and targets.

Related Posts