Securing SSH with iptables
Practical iptables recipes for locking down SSH access.
iptables is a command-line utility and the standard interface for managing the Netfilter firewall built into the Linux kernel. It lets you create and modify rules that control how network packets are filtered and routed. Working with iptables requires root privileges.
Core concepts
A rule consists of a match criterion, a target action, and a packet counter. When an incoming packet satisfies the criterion, the action is applied and the counter increments. Rules are evaluated in order — sequence matters.
- Criterion — a logical expression that inspects packet and connection properties to decide whether a rule applies. Multiple criteria are combined with a logical AND.
- Action (target) — what to do with a packet when it matches: accept it, drop it, pass it to another chain, and so on.
- Counter — tracks how many packets matched the rule and their total size in bytes.
A chain is an ordered list of rules. Chains come in two flavors:
- Built-in chains — created automatically when a table is initialized. Each has a default policy that applies to any packet not matched by other rules. Built-in chain names are always uppercase:
PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING. - User-defined chains — created manually, scoped to their own table. Stick to lowercase names to avoid confusion with built-in chains and targets.
A table is a collection of chains grouped by purpose. Table names are lowercase. You specify a table with the -t table_name flag — if omitted, iptables defaults to filter.
1. Rate-limiting connections (brute force protection)
To guard against password brute-forcing, you can cap the number of new SSH connections from a single IP — say, 2 attempts per minute — and drop anything beyond that threshold.
## Create the sshguard chain
/sbin/iptables -N sshguard
# Optional: log blocked attempts
#/sbin/iptables -A sshguard -m state --state NEW -m recent --name SSH --rcheck --seconds 60 --hitcount 2 -j LOG --log-prefix "SSH-shield: "
# Drop connections that exceed the rate limit
/sbin/iptables -A sshguard -m state --state NEW -m recent --name SSH --update --seconds 60 --hitcount 2 -j DROP
# Accept and register the IP if under the limit
/sbin/iptables -A sshguard -m state --state NEW -m recent --name SSH --set -j ACCEPT
/sbin/iptables -A sshguard -j ACCEPT
## Route all SSH traffic through sshguard
/sbin/iptables -A INPUT -p tcp --dport 22 -j sshguard
These rules rely on the recent kernel module, which maintains dynamic lists of IP addresses. Here's what each option does:
--name name— the name of the IP list to work with (defaults toDEFAULT)--rcheck— checks whether the sender's IP is in the list; returnsfalseif not found--update— same as--rcheck, but also refreshes the timestamp if the IP is found--hitcount hits— returnstruewhen the packet count from a given IP meets or exceeds the specified value; used with--rcheckor--update--seconds seconds— defines how long an IP stays in the list after being added--set— adds the sender's IP to the list, or updates its entry if already present--remove— removes an IP from the list; returnsfalseif not found
Important
Using --update instead of --rcheck means every new connection attempt resets the timer. If someone keeps trying without waiting, their 60-second cooldown keeps starting over.
2. Dynamic port open / close (port knocking)
Rather than leaving SSH exposed around the clock, you can hide it behind a knock sequence. The port opens only for the IP that sends the right knock — and closes again on demand.
To open SSH access, hit port 1500 first — via telnet:
telnet myserver 1500
or directly from a browser:
http://192.168.0.100:1500
To close it again, knock on port 1499.
iptables -N sshguard
# Allow connection if the IP is already in the list
iptables -A sshguard -m state --state NEW -m recent --rcheck --name SSH -j ACCEPT
# Allow packets for already-established connections
iptables -A sshguard -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A sshguard -j DROP
# Open: add IP to the list when port 1500 is hit
#iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 1500 -j LOG --log-prefix "SSH-open: "
iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 1500 -m recent --name SSH --set -j DROP
# Close: remove IP from the list when port 1499 is hit
iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 1499 -m recent --name SSH --remove -j DROP
## Route SSH traffic through sshguard
iptables -A INPUT -p tcp --dport 22 -j sshguard
Access is granted only to the IP that performed the knock.
3. Timed SSH access
This approach lets you open SSH for a specific IP for a fixed window of time — useful when you need temporary access without leaving a port permanently exposed.
## Create and flush the sshguard chain
iptables -N sshguard
iptables -F sshguard
# Allow connection if IP is in the list and last seen within 30 hours (108000 seconds)
iptables -A sshguard -m state --state NEW -m recent --update --seconds 108000 --name SSH -j ACCEPT
# Allow packets for already-established connections
iptables -A sshguard -m state --state ESTABLISHED,RELATED -j ACCEPT
# Drop everything else
iptables -A sshguard -j DROP
## Register the IP when port 222 is hit
iptables -A INPUT -m state --state NEW -p tcp --dport 222 -m recent --name SSH --set
## Route SSH traffic through sshguard
iptables -A INPUT -p tcp --dport 22 -j sshguard
To gain access, connect to port 222 first:
ssh user@server.name -p 222
After that, your IP will have SSH access for the duration specified. Each subsequent SSH connection refreshes the timer. If you'd rather not extend the window on every connect, replace --update --seconds 108000 with --rcheck.
Access is granted only to the IP that performed the initial connection.
To see which IPs currently have SSH access:
cat /proc/net/ipt_recent/SSH
Help
If you have any questions or need assistance, please contact us through the ticket system — we're always here to help!