
Your firewall shouldn’t feel like a black box. On a hosting VPS or dedicated server, you want a ruleset you can read line by line. You should be able to audit it quickly and restore it under stress.
This IPTables firewall configuration tutorial builds a hosting-safe baseline. You’ll cover SSH protection, web + DNS + mail ports, sane logging, light rate limits, and persistent rules that survive reboots.
The examples assume Ubuntu Server 24.04/26.04 LTS-style layouts and iptables-nft (the default wrapper on modern Ubuntu). The same rule logic applies on Debian 12/13 and most RHEL-family systems.
If you’d rather not own firewall change control, a managed VPS hosting plan from HostMyCode can handle reviews and rollouts. The configuration still stays auditable.
What you’ll build (ports, policy, and guardrails)
Start by writing down what the server actually needs to do. Hosting boxes often require more than “open 80/443.”
Guessing is how you break mail, DNS, or panel access.
- Default policy: DROP inbound, ACCEPT outbound
- Allow: SSH (22 or custom), HTTP (80), HTTPS (443)
- Often allow (if you run these services): DNS (53 TCP/UDP), SMTP (25/587/465), IMAP/POP (143/993/110/995), FTP (21 + passive range), control panel ports (WHM/cPanel/DirectAdmin/Plesk)
- Protect: basic SSH brute-force rate limiting, drop invalid states
- Persist: rules survive reboot via
iptables-persistent - Optional: NAT for a private network or container bridge
Commercial reality check: If you host client sites or run a reseller node, “minimal ports” can quietly break things.
Common casualties include mail delivery, AutoSSL/DCV checks, DNS lookups, and control panel access.
Document first. Enforce second.
Prerequisites and a no-lockout safety plan
Do these three things before you flip INPUT to DROP:
- Open a second SSH session and leave it connected as your safety rope.
- Confirm your SSH port and your current source IP (or office VPN IP range).
- Schedule an automatic rollback so you can recover from a typo.
To schedule a rollback (Ubuntu/Debian), use at:
sudo apt update
sudo apt install -y at
echo "sudo iptables -F; sudo iptables -P INPUT ACCEPT; sudo iptables -P FORWARD ACCEPT; sudo iptables -P OUTPUT ACCEPT" | sudo at now + 10 minutes
If everything works after your changes, remove the queued job:
sudo atq
sudo atrm <job_id>
If you’re still relying on password logins, fix that before you tighten the edge. This pairs well with HostMyCode’s SSH key setup guide tutorial.
Install the tools (iptables + persistence)
On Ubuntu/Debian, install persistence up front. That way, your rules won’t vanish after a reboot:
sudo apt update
sudo apt install -y iptables iptables-persistent
During install you may be asked to save current rules. On a fresh server, either choice is fine.
We’ll replace them cleanly.
On RHEL-family systems (AlmaLinux/Rocky), persistence is typically handled differently. It’s often done via iptables-services. The rule syntax below remains the same.
Baseline rules: start clean, accept loopback, keep established traffic
Put the rules in a script so you can rerun them consistently. This also makes changes easier to diff later.
Save this as /root/fw-iptables-baseline.sh:
sudo nano /root/fw-iptables-baseline.sh
Paste the following and adjust the SSH port if needed:
#!/bin/sh
set -eu
SSH_PORT="22"
# Flush existing rules
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
# Default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow loopback
iptables -A INPUT -i lo -j ACCEPT
# Drop invalid packets early
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
# Allow established/related traffic
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# SSH (basic allow)
iptables -A INPUT -p tcp --dport "$SSH_PORT" -m conntrack --ctstate NEW -j ACCEPT
# Web
iptables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT
# ICMP (optional but recommended for PMTU + diagnostics)
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 5/second --limit-burst 10 -j ACCEPT
# Log and drop the rest (rate-limited)
iptables -A INPUT -m limit --limit 10/min --limit-burst 20 -j LOG --log-prefix "iptables-drop: " --log-level 4
iptables -A INPUT -j DROP
Make it executable and run it:
sudo chmod +x /root/fw-iptables-baseline.sh
sudo /root/fw-iptables-baseline.sh
Now verify what’s active:
sudo iptables -S
sudo iptables -L -n -v --line-numbers
Pitfall: Don’t omit ESTABLISHED,RELATED. Without it, SSH and other active connections can stall once packets stop matching “NEW.”
Hosting ports: DNS and email without creating a relay
If the server runs DNS (authoritative or resolver), allow 53/TCP and 53/UDP. Add these lines above the logging rule:
# DNS
iptables -A INPUT -p udp --dport 53 -j ACCEPT
iptables -A INPUT -p tcp --dport 53 -m conntrack --ctstate NEW -j ACCEPT
Mail servers typically need multiple ports. A common, sensible set for Postfix/Dovecot is:
- 25/tcp (SMTP)
- 587/tcp (submission)
- 465/tcp (smtps, if you use it)
- 143/tcp + 993/tcp (IMAP + IMAPS)
- 110/tcp + 995/tcp (POP3 + POP3S, if needed)
Add rules only for the services you actually run:
# SMTP / submission
iptables -A INPUT -p tcp --dport 25 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 587 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 465 -m conntrack --ctstate NEW -j ACCEPT
# IMAP/POP
iptables -A INPUT -p tcp --dport 143 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 993 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 110 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 995 -m conntrack --ctstate NEW -j ACCEPT
If you’re chasing deliverability issues, firewall rules are only part of it.
Reverse DNS and authentication usually decide the outcome.
Keep these two references close:
SSH brute-force resistance with iptables rate limits (simple, effective)
Fail2Ban is usually the better long-term option. It reacts to real log events and can alert you.
Still, a small iptables limiter helps. It cuts background noise and reduces wasted CPU on repeated connection attempts.
Replace the basic SSH allow rule with a two-stage limiter using the recent module. In your script, remove:
iptables -A INPUT -p tcp --dport "$SSH_PORT" -m conntrack --ctstate NEW -j ACCEPT
Add this instead:
# SSH: allow, but limit repeated new connections per source IP
iptables -A INPUT -p tcp --dport "$SSH_PORT" -m conntrack --ctstate NEW -m recent --set --name SSH
iptables -A INPUT -p tcp --dport "$SSH_PORT" -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 10 --name SSH -j DROP
iptables -A INPUT -p tcp --dport "$SSH_PORT" -m conntrack --ctstate NEW -j ACCEPT
This drops sources that attempt 10+ new SSH connections in 60 seconds. It won’t stop a distributed attack.
It does shut down the common “one noisy IP” pattern.
If you want smarter bans and alerting, set up log monitoring with HostMyCode’s VPS log monitoring tutorial.
Control panel and SFTP/FTP considerations (cPanel, DirectAdmin, Plesk)
Control panels bring their own ports. Treat those as admin-only unless you have a clear reason to expose them.
A good default is simple. Customer-facing services stay public. Admin ports should only accept traffic from your office IP or VPN.
Common ports:
- cPanel/WHM: 2083 (cPanel), 2087 (WHM), 2086 (WHM non-SSL, avoid), 2096 (webmail)
- DirectAdmin: 2222
- Plesk: 8443
- SFTP: 22 (same as SSH)
- FTP: 21 + passive range (varies)
Example: restrict WHM to a single admin IP (replace 203.0.113.10):
ADMIN_IP="203.0.113.10"
iptables -A INPUT -p tcp -s "$ADMIN_IP" --dport 2087 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p tcp -s "$ADMIN_IP" --dport 2083 -m conntrack --ctstate NEW -j ACCEPT
If you run a hosting business on a VPS, a HostMyCode VPS gives you the isolation you need.
It’s built for firewall policy, control panels, and clear service boundaries.
NAT and forwarding (only if you actually need it)
NAT only matters if this server routes traffic for something else.
Common cases include a private subnet behind a second interface, or a container bridge you want reachable.
If you don’t have that setup, skip this section.
Scenario: Your public interface is eth0. Your private subnet is on eth1 (10.10.0.0/24). You want private hosts to reach the internet through this server.
1) Enable IP forwarding:
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-ipforward.conf
sudo sysctl -p /etc/sysctl.d/99-ipforward.conf
2) Add NAT masquerading:
iptables -t nat -A POSTROUTING -s 10.10.0.0/24 -o eth0 -j MASQUERADE
3) Allow forwarding (tight scope):
iptables -A FORWARD -i eth1 -o eth0 -s 10.10.0.0/24 -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i eth0 -o eth1 -d 10.10.0.0/24 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
Quick diagnostic: If private hosts can ping out but name lookups fail, check DNS.
You likely didn’t allow DNS from the private side, or there’s no resolver reachable from that subnet.
Make rules persistent (and keep a rollback copy)
Once the ruleset behaves as expected, save it. On Ubuntu/Debian with iptables-persistent:
sudo sh -c 'iptables-save > /etc/iptables/rules.v4'
If you can, test with a real reboot window:
sudo reboot
After reboot, verify:
sudo iptables -L -n -v
Rollback copy: Keep a dated export in /root. It makes fast reverts much easier:
sudo iptables-save > /root/iptables-$(date +%F).rules.v4
Logging: where to look, and how to avoid filling disks
The rules above log dropped packets with a prefix. On Ubuntu/Debian you’ll usually find them in:
/var/log/kern.log/var/log/syslog- Or via:
journalctl -k
Search quickly:
sudo journalctl -k --since "1 hour ago" | grep "iptables-drop" | tail -n 50
Disk safety: The example uses rate-limited logging for a reason. An unlimited LOG rule can chew through disk and I/O during routine scans.
Validate your firewall from the outside (real checks)
Don’t assume you’re done because your SSH session stayed open. Test from a second network.
You want to see what the internet sees.
- From your laptop: scan only the ports that should be open.
nmap -Pn -p 22,80,443 your.server.ip
- From the server: confirm DNS resolution and outbound HTTPS both work.
getent hosts www.hostmycode.com
curl -I https://www.hostmycode.com
If SSL issuance or renewals fail after firewall changes, the cause is usually outside this inbound policy.
The common culprits are blocked HTTP validation, broken DNS, or outbound restrictions in a stricter environment.
Keep this troubleshooting guide handy: SSL renewal troubleshooting tutorial.
Practical hosting checklist (copy/paste before you go live)
- SSH works on the intended port from your admin network
- HTTP/HTTPS reachable and returns the right vhost/certificate
- DNS works (if hosted locally): both UDP and TCP 53 respond
- Mail ports opened only if you run mail services on this host
- Control panel ports restricted to admin IPs/VPN
- Drop logging is rate-limited
iptables-saveexported to/etc/iptables/rules.v4- Rollback plan tested (or at least staged with
at)
Summary: a firewall you can explain and reproduce
iptables still earns its keep in 2026 because it’s explicit. You can audit every rule, store it in version control, and reapply it fast after an incident.
The baseline here keeps common hosting services reachable. It also cuts down the background noise every public IP attracts.
If you’d like this level of hardening without carrying the operational overhead during upgrades and migrations, HostMyCode can help.
Start with a HostMyCode VPS for full control, or choose managed VPS hosting if you want an ops team to review, apply, and monitor a clean, hosting-safe firewall policy.
If you’re tightening firewall rules as part of a broader server cleanup, do it on infrastructure you can snapshot and scale without drama. HostMyCode offers HostMyCode VPS plans for hands-on admins and managed VPS hosting if you want changes reviewed, applied, and monitored with less risk.
FAQ
Will this iptables setup break WordPress or WooCommerce?
Not if you allow 80/443 and keep OUTPUT set to ACCEPT (as shown). WordPress issues after a firewall change usually come from blocked outbound DNS/HTTP in stricter environments, not from inbound rules.
Do I need to open port 3306 for MySQL?
Almost never on a single-server hosting setup. Keep databases bound to 127.0.0.1 or a private network interface. Only open 3306 if you have a specific remote DB requirement and you can lock it to trusted IPs.
Is iptables still valid in 2026, or should I use nftables?
On many modern distros, the iptables command is a compatibility layer over nftables (iptables-nft). It’s still a practical interface for predictable hosting rules, especially when you need straightforward commands and persistence.
How do I safely apply changes on a remote server?
Keep two SSH sessions open. Schedule an automatic rollback with at. Then apply rules.
Once validated from an external network, save with iptables-save and cancel the rollback job.
What’s the next step after iptables for a hosting server?
Add log-based banning (Fail2Ban), enforce SSH keys, and build backups you’ve actually restored. If you haven’t done it yet, start with HostMyCode’s VPS backup automation tutorial.