SSH is a fundamental tool for system administration, but leaving it wide open to the internet is asking for trouble. iptables is your firewall, and we’re going to lock down SSH to only allow access from specific IPs and ports.

Let’s see iptables in action. Imagine you have a server and you want to allow SSH access from your office IP address, 192.168.1.100, and your home IP, 203.0.113.50. You also want to ensure that SSH traffic only comes in on the standard port, 22.

First, let’s list the current rules to see what we’re working with (or if it’s a fresh install).

sudo iptables -L -v -n

This command shows the rules in your iptables tables. -L lists the rules, -v provides verbose output (like packet and byte counts), and -n shows IP addresses and port numbers numerically, which is much faster and clearer than DNS lookups.

Now, let’s add the rules to secure SSH. We’ll assume you’re already connected and have root privileges or can use sudo.

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

# Allow SSH from your office IP
sudo iptables -A INPUT -p tcp -s 192.168.1.100 --dport 22 -j ACCEPT

# Allow SSH from your home IP
sudo iptables -A INPUT -p tcp -s 203.0.113.50 --dport 22 -j ACCEPT

# Drop all other SSH traffic
sudo iptables -A INPUT -p tcp --dport 22 -j DROP

This sequence is crucial. The first rule, iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT, is vital. It tells iptables to permit packets that are part of an already established connection or are related to one. Without this, even if you allow incoming SSH, your outgoing responses would be blocked, and you wouldn’t be able to communicate. The -m conntrack module tracks connection states.

Next, we explicitly allow TCP traffic on port 22 (--dport 22) from our specific trusted IP addresses (-s 192.168.1.100 and -s 203.0.113.50). The -A INPUT flag appends these rules to the end of the INPUT chain, which handles incoming traffic.

Finally, sudo iptables -A INPUT -p tcp --dport 22 -j DROP is the kill switch for any other SSH traffic. Any TCP packet destined for port 22 that doesn’t match the preceding ACCEPT rules will be silently dropped. The order of these rules matters greatly; the DROP rule must come after the specific ACCEPT rules.

To make these rules persistent across reboots, you’ll typically need to save them. The method varies by distribution. On Debian/Ubuntu systems, you can install iptables-persistent:

sudo apt-get update
sudo apt-get install iptables-persistent

During installation, it will ask if you want to save the current IPv4 and IPv6 rules. Say yes. If you need to save them later, use:

sudo netfilter-persistent save

On RHEL/CentOS/Fedora systems, you might use iptables-services:

sudo yum install iptables-services  # or dnf install iptables-services
sudo systemctl enable iptables
sudo systemctl start iptables
sudo service iptables save  # or sudo iptables-save > /etc/sysconfig/iptables

The most surprising thing about iptables is how it handles default policies. By default, many iptables chains (INPUT, FORWARD, OUTPUT) are set to ACCEPT. This means if no rule explicitly matches a packet, it’s allowed. To truly secure a system, you often want to change the default policy for the INPUT chain to DROP and then explicitly ACCEPT only the traffic you need, as we did with SSH. This is a "deny by default" posture.

Consider this sequence to set a restrictive default policy and then allow SSH and loopback traffic:

# Set default policies to DROP
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT # Usually safe to allow outgoing

# Allow loopback traffic (essential for local services)
sudo iptables -A INPUT -i lo -j ACCEPT

# Allow established/related connections (as before)
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Allow SSH from your office IP
sudo iptables -A INPUT -p tcp -s 192.168.1.100 --dport 22 -j ACCEPT

# Allow SSH from your home IP
sudo iptables -A INPUT -p tcp -s 203.0.113.50 --dport 22 -j ACCEPT

# Save the rules (example for Debian/Ubuntu)
sudo netfilter-persistent save

This "deny by default" approach is much more robust. You’ve explicitly stated what’s allowed, and everything else is blocked. The loopback interface (-i lo) is critical because many local services communicate with each other via localhost (127.0.0.1), and these connections would otherwise be dropped.

Once you’ve secured SSH, the next challenge is often managing other services like HTTP/HTTPS or ensuring your DNS resolution still works.

Want structured learning?

Take the full Iptables course →