It's two in the morning and SSH is down. The last thing you did was add a DROP rule to the INPUT chain, and now neither ping responds nor does port 22. If you don't have KVM console or rescue mode access, this situation means reinstalling the server. The whole story of "server firewall without losing access" comes down to one simple point: never apply a rule that closes the way back unless you've already opened the way back.
Before any rule, build the rescue path
The order matters. First, make sure you have access to the console or rescue mode from the server panel. If you don't, request activation right now. Without this, any small mistake in the server firewall turns into a multi-hour incident. If you lose access, the recovery path starts with rescue mode and server reinstall; but it's better to never get there.
The second step is to open a second SSH session and keep it. Don't close the first session. Apply the rule in the first session and test from the second. If the second session drops, the first is still alive and you can remove the rule. This simple technique has prevented more lockouts than any other tool.
Timed rule: the rescue timer
The third step is the timer. Before applying the rule, set up a script that reverts the firewall to its previous state if you don't confirm within the next 5 minutes:
cat > /root/fw-rollback.sh <<'EOF'
#!/bin/bash
sleep 300
iptables -F
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
EOF
chmod +x /root/fw-rollback.sh
nohup /root/fw-rollback.sh &
Now apply the rule. If everything went well, kill it with kill %1 or by finding the script's PID. If you made a mistake, 5 minutes later the firewall clears itself and access returns. Don't set this number below 3 minutes; on high-traffic servers, running iptables -F on large chains takes a few seconds and you won't have time to test.
Which ports should you actually leave open
The short answer: only the ports that an active service is listening on. Get the real list with ss -tulpn, not a guessed list. Typical output of a fresh web server:
ss -tulpn | grep LISTEN
tcp LISTEN 0 128 0.0.0.0:22 users:(("sshd",pid=812))
tcp LISTEN 0 511 0.0.0.0:80 users:(("nginx",pid=1204))
tcp LISTEN 0 511 0.0.0.0:443 users:(("nginx",pid=1204))
tcp LISTEN 0 70 127.0.0.1:3306 users:(("mysqld",pid=1502))
Note that MySQL is listening on 127.0.0.1, not 0.0.0.0. That means even if you leave port 3306 open in the firewall, it isn't reachable from outside. But if you've ever changed the bind-address value in my.cnf to 0.0.0.0, that same open port becomes a back door. The guide on MySQL security and restricting access covers this scenario in full.
Don't move the SSH port unless you have a reason
Changing port 22 to something like 2222 isn't a real security layer; it only reduces log noise. An attacker running a full port scan finds the new port in seconds. If you do this, be sure to also add Port 2222 in /etc/ssh/sshd_config and restart the service, otherwise SSH won't come up after a reboot. In practice, I keep the default port and instead enable PasswordAuthentication no and PermitRootLogin prohibit-password. These two lines have more effect than changing the port.
iptables or ufw? A practical choice
If you have only one server and the rules are simple, choose ufw. If you're working with Kubernetes, Docker with bridge networking, or internal NAT, go straight to iptables or nftables. The reason is that Docker writes its own rules into the DOCKER-USER chain and ufw doesn't see them; the result is that your container's port 8080 is open from outside while ufw status says it's blocked. If you run Docker on a VPS, read setting up Docker on a VPS before configuring the firewall.
| Criterion | ufw | Raw iptables |
|---|---|---|
| Setup speed | A few minutes | A few hours |
| Rule readability | High | Low |
| Docker compatibility | Weak | Complete |
| Persistent storage | Automatic | Requires iptables-persistent |
| Troubleshooting | Limited | Complete with LOG |
One practical note about ufw: run the ufw allow 22/tcp command before ufw enable. If you enable first, ufw denies incoming by default and if SSH is on a non-standard port, you'll be cut off that very moment. ufw itself gives a warning that "this may disrupt the current session" and most people ignore it.
This is where they go wrong
The most common mistake I've seen is this: someone adds the rule iptables -A INPUT -j DROP at the end of the chain, then reboots the server and SSH doesn't come up. Why? Because iptables rules are cleared by default after a reboot and the chain's default policy returns to ACCEPT; but if you've saved them with iptables-persistent, that same DROP rule is reapplied after boot and this time you don't even have a rescue session. The sign is that the server responds to ping (because ICMP is excepted in your rule) but port 22 times out. Ping responding is exactly what's misleading; many people think the problem is with the SSH service and run systemctl restart sshd, while the packet never reaches sshd.
Mistake number two: applying a rule to the OUTPUT chain. Someone who wants the server to "only connect outward" changes the OUTPUT policy to DROP and then can't figure out why apt update doesn't work or DNS doesn't resolve. If you're restricting OUTPUT, at minimum leave these open:
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT
Also put the ESTABLISHED,RELATED line in INPUT, otherwise responses to outgoing packets won't return and everything stays half-done. Always put this line as the first rule of the chain; rule order in iptables is meaningful and the first match wins.
Risk-free testing: how to be sure
Don't test from inside the server. curl localhost always responds because it goes through the loopback interface and bypasses the firewall. Test from an external machine with nmap:
nmap -Pn -p 22,80,443,3306,8080 your-server-ip
An open result means the packet is allowed, filtered means it's DROPped, and closed means the firewall allowed it but no service is listening. Take the difference between filtered and closed seriously; many people mistake closed for a firewall problem and spend hours changing rules.
For web services, after every firewall change, run a speed and accessibility test from outside. If the site is hosted on a dedicated server or dedicated server and traffic is high, apply the rules during low-traffic hours; every firewall reload causes a few milliseconds of interruption in packet processing, which is noticeable under heavy load. If server load rises after the changes, the guide to diagnosing high server load is a good starting point.
Persistent storage and periodic review
Save the rules, otherwise everything reverts to its initial state after a reboot:
apt install iptables-persistent
netfilter-persistent save
iptables-save > /etc/iptables/rules.v4
Every three months, run iptables -L -n -v --line-numbers and see which rule has a zero counter. A rule that hasn't caught even a single packet in 90 days is probably no longer needed and only enlarges the attack surface. Take this review seriously; firewalls usually only get added to and never cleaned up.
If you're just setting up infrastructure and don't want to wrestle with raw iptables from day one, a Linux host with full root access is a good starting point; you can build the rules with ufw there and then migrate to iptables. For transferring configuration files between servers, transferring files with scp and rsync speeds things up too.
Frequently asked questions
Why did SSH drop after enabling the firewall but ping still responds?
Because you left the ICMP rule open but didn't open port 22. Ping works at layer 3 and has nothing to do with the state of a TCP port. First log in via the console and check iptables -L INPUT -n --line-numbers, then remove the rule blocking port 22 with iptables -D INPUT <number>.
Does changing the SSH port really improve security?
Not as much as people imagine. A full port scan takes a few seconds and the new port is found. Its real effect is reducing failed-attempt logs. If you want to add real security, choose key-based authentication and disable passwords.
How do I find out which ports are open from outside?
From inside the server, use ss -tulpn to see which service is listening on which address, and scan from an external machine with nmap -Pn -p- your-server-ip. The difference between these two outputs is exactly what the firewall has blocked or opened.
Are iptables rules cleared after a reboot?
Yes, unless you've saved them with netfilter-persistent save or iptables-save to the path /etc/iptables/rules.v4. If you use ufw, the rules are saved automatically and no extra work is needed.