Latest / Linux Server Admin with Fexingo: Sysadmin, Bash, and Server Engineering / How to Use Linux iptables for Advanced Network Firewall Rules
Transcript
- Lucas: So you lock yourself out of a server via iptables. Luna: Oh, that's the classic. You add a rule, hit enter, and suddenly your SSH session freezes. Lucas: Right. And this is why I always have a recovery console or IPMI session open when I'm playing with firewall rules. But today I want to go beyond the basics—three iptables patterns that actually make a difference in production. Luna: I'm listening. Because most people just open port 22, 80, 443, and call it done. Lucas: Exactly. And that works until it doesn't. First pattern: rate-limiting incoming SSH connections. You can do this with the 'hashlimit' module. Luna: I've used 'connlimit' before. Same idea? Lucas: Similar but different. 'connlimit' limits concurrent connections from a single IP. 'hashlimit' limits the rate of new connections over time. So for SSH brute-force, you'd want something like this: 'iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m hashlimit --hashlimit-above 5/minute --hashlimit-burst 10 --hashlimit-mode srcip --hashlimit-name ssh_rate_limit -j DROP'. That drops any new SSH connection from an IP that exceeds five per minute, with a burst of ten. Luna: Okay, that's specific. And the burst allows for legitimate retries, like if you mistype a password. Lucas: Exactly. Second pattern: connection tracking. A lot of people forget that iptables can track connection state. You want to allow established and related traffic, but only allow new incoming traffic on specific ports. Luna: So like a stateful firewall rule. Lucas: Yes. The classic: 'iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT'. Then add your specific allow rules for SSH, HTTP, etc. This is way more secure than just opening ports wide open. Luna: What about the FORWARD chain? That's where people get tripped up. Lucas: Good point. Third pattern: using FORWARD and NAT for a simple DMZ. Say you have a web server on an internal IP, 192.168.1.100. You want the internet to reach it on ports 80 and 443, but you don't want that server initiating outbound connections. Luna: That's a common setup for a bastion host. Lucas: Exactly. So you set up DNAT: 'iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:80'. Then in the FORWARD chain, you allow only NEW and ESTABLISHED traffic to that IP on those ports. And you block anything else. Luna: And you need IP forwarding enabled in sysctl. Lucas: Right. 'net.ipv4.ip_forward = 1'. Otherwise DNAT does nothing. The beauty of this approach is that even if the web server gets compromised, it can't reach out to the internet to phone home. Luna: That's a solid isolation pattern. One thing I've noticed: people often forget to save their iptables rules. Reboot and everything's gone. Lucas: Yeah, that's a rite of passage. On Debian-based systems, you can install 'iptables-persistent' which saves and restores rules automatically. On RHEL, it's 'iptables-services' or you just use 'iptables-save > /etc/sysconfig/iptables'. Then 'iptables-restore < /etc/sysconfig/iptables' on boot. Luna: I've seen people wrap that in a systemd unit file. Lucas: That works too. The key is to test your ruleset before making it persistent. I usually write a script that applies the rules, then I test connectivity, and if it breaks, I have the recovery console undo it. Luna: Speaking of testing, how do you inspect iptables counters to see if your rules are actually being hit? Lucas: Use 'iptables -L -v'. That shows packet and byte counts per rule. So if you see your DROP rule for SSH rate-limiting has zero packets after a week, either the rule is wrong or nobody's trying to brute-force you. Luna: Or the rule order is wrong. Because iptables processes rules top-down, so if you have an ACCEPT rule above your DROP rule, the DROP never triggers. Lucas: Exactly. Rule ordering is critical. I always put the ESTABLISHED,RELATED rule first, then specific ACCEPT rules, then rate-limiting, then a default DROP at the end. Luna: What about the INPUT policy? Should it be DROP or ACCEPT? Lucas: I prefer a default DROP policy. Set 'iptables -P INPUT DROP'. Then explicitly allow what you need. That way, if you forget to add a rule, you're blocking by default instead of allowing by default. Luna: Makes sense. But you have to be careful not to lock yourself out if you change the policy while SSH is running. Lucas: Right. So I always add a rule to allow SSH first: 'iptables -A INPUT -p tcp --dport 22 -j ACCEPT'. Then change the policy. Luna: You know, this conversation reminds me why I appreciate good firewall management. It's one of those things that feels daunting until you break it down into patterns. Lucas: Totally. And honestly, the fact that we can have this discussion ad-free and without corporate sponsorship is pretty cool. A handful of listeners chip in monthly through Buy Me a Coffee at fexingo, and that's what keeps shows like this going. So if you find these episodes useful, that's the whole engine. Luna: Yeah. No ads, no sponsors—just community support. It's a model that works because people who actually use this stuff find value in it. Lucas: Anyway, back to iptables. One more pattern I want to cover: logging dropped packets. When you're debugging, you can log before dropping. Luna: You mean using the LOG target. Lucas: Yes. 'iptables -A INPUT -j LOG --log-prefix "IPTABLES_DROP: " --log-level 4'. Then you check 'dmesg' or your syslog. But careful—if you have a lot of traffic, it'll flood your logs. Luna: So use it temporarily during debugging, then remove it. Lucas: Exactly. And that's a good segway to another tip: use iptables chains to organize rules. Instead of dumping everything in INPUT, you can create custom chains like 'SSH_BRUTE' and jump to them. Luna: That sounds cleaner. How do you create a custom chain? Lucas: 'iptables -N SSH_BRUTE'. Then add rules to that chain, and in the INPUT chain, add 'iptables -A INPUT -p tcp --dport 22 -j SSH_BRUTE'. Luna: Nice. That makes reading the ruleset a lot easier. Lucas: And you can flush just that chain with 'iptables -F SSH_BRUTE' without touching the main rules. Luna: One more thing: iptables vs nftables. We're in 2026, most distros ship nftables. Should people still learn iptables? Lucas: Good question. The iptables command still works on most systems because of compatibility layers. But nftables is the future. However, the concepts—chains, rules, matches, targets—are the same. If you understand iptables, moving to nftables is straightforward. Luna: So it's not wasted effort. Lucas: Not at all. Plus, a lot of older documentation and scripts still use iptables syntax. So knowing it is practical. Luna: Alright, I'm convinced. Time to audit my own server's ruleset. Lucas: Do it. Start with 'iptables -L -v --line-numbers'. That gives you a numbered list. Then plan your ruleset on paper before applying. Luna: And have a recovery plan. Lucas: Always. That's the sysadmin golden rule.