
Most hosting outages don’t start with a zero-day. They start with a “quick firewall tweak” that drops your SSH session and strands you outside the box.
This iptables firewall tutorial shows a production-safe way to build and apply a web-hosting ruleset on a VPS or dedicated server in 2026. The goal is simple: change rules without nuking active connections.
The examples assume Ubuntu Server 24.04/26.04, Debian 12/13, AlmaLinux 9/10, or Rocky Linux 9/10. You’ll configure IPv4 and IPv6 rules, keep SSH reachable, and open HTTP/HTTPS. You’ll also add light abuse resistance and make the rules survive reboots.
What you’ll build (and why it fits hosting)
- A default-deny firewall that still allows SSH, HTTP, HTTPS, and optional control panel ports.
- Rules that won’t interrupt existing connections while you apply changes.
- Basic mitigation for noisy traffic (simple rate limits and bad-packet drops).
- Persistent configuration for both IPv4 and IPv6.
- A rollback plan so you can recover fast if you mis-type a rule.
If you host client sites or run production WordPress, do this on a VPS with predictable networking and real console access. A HostMyCode VPS is a good fit for hands-on administration like this.
If you don’t want to maintain firewall rules yourself, managed VPS hosting can handle it for you.
Before you touch iptables: two safety nets
Spend five minutes here. It can save you an hour of recovery later.
- Have console access (VNC/KVM/serial from your provider). If you can’t get a console, schedule a maintenance window. If you can’t schedule one, use the rollback timer below.
- Confirm your SSH source IP (office VPN, home IP, or bastion). If your IP changes often, use a VPN egress or a dedicated jump host.
Install tooling (iptables + persistence)
On Ubuntu/Debian:
sudo apt update
sudo apt install -y iptables iptables-persistent netfilter-persistent
On AlmaLinux/Rocky (firewalld may be default; you can still use iptables directly, but be consistent):
sudo dnf install -y iptables iptables-services
sudo systemctl enable --now iptables
Note: In 2026, many distros use nftables under the hood. This tutorial sticks to iptables because it’s still common on hosting fleets and control-panel servers.
If your system uses the iptables-nft backend, these commands still work. They translate to nftables rules internally.
Snapshot your current rules
sudo iptables-save > ~/iptables.before.v4
sudo ip6tables-save > ~/iptables.before.v6
sudo iptables -S
sudo ip6tables -S
Set up a rollback timer (strongly recommended)
This restores your previous rules after 2 minutes unless you cancel it. Keep at least one SSH session open while you test.
sudo bash -c 'at now + 2 minutes <<EOF
iptables-restore < /root/iptables.before.v4 2>/dev/null || true
ip6tables-restore < /root/iptables.before.v6 2>/dev/null || true
EOF'
Copy the backups into /root so the rollback job can read them:
sudo cp ~/iptables.before.v4 /root/iptables.before.v4
sudo cp ~/iptables.before.v6 /root/iptables.before.v6
If you don’t have at installed:
sudo apt install -y at
sudo systemctl enable --now atd
iptables firewall tutorial: build a clean baseline ruleset
A readable ruleset is usually a simple one. Use a predictable pattern:
- Start from a clean state (carefully).
- Allow established traffic.
- Allow loopback.
- Allow only the ports you actually run.
- Drop everything else.
Important: Do this from a stable SSH session. Don’t paste a port list into production unless you’re sure it matches what the server needs.
Step 1: define your variables
Pick your SSH port and (ideally) a trusted SSH source:
# Example values
SSH_PORT=22
TRUSTED_SSH_CIDR="203.0.113.10/32"
If you can’t allowlist a stable IP, skip TRUSTED_SSH_CIDR and allow SSH from anywhere. Then add Fail2Ban.
Step 2: flush existing rules (only if you’re ready)
sudo iptables -F
sudo iptables -X
sudo iptables -t nat -F
sudo iptables -t nat -X
sudo iptables -t mangle -F
sudo iptables -t mangle -X
For IPv6:
sudo ip6tables -F
sudo ip6tables -X
Step 3: set safe defaults
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT
For IPv6:
sudo ip6tables -P INPUT DROP
sudo ip6tables -P FORWARD DROP
sudo ip6tables -P OUTPUT ACCEPT
Step 4: allow loopback and established connections
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo ip6tables -A INPUT -i lo -j ACCEPT
sudo ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
Step 5: allow SSH (with or without allowlist)
Option A (recommended): SSH allowlist
sudo iptables -A INPUT -p tcp -s 203.0.113.10/32 --dport 22 -m conntrack --ctstate NEW -j ACCEPT
Option B: allow SSH from anywhere
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
Mirror the same for IPv6 if you use IPv6 SSH. If you allowlist, use your v6 CIDR:
sudo ip6tables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
If you’re tightening SSH at the same time, follow our guide on secure SSH access with keys, allowlists, and audit logs. It pairs well with an iptables perimeter.
Step 6: allow web traffic (HTTP/HTTPS)
sudo iptables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT
IPv6:
sudo ip6tables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
sudo ip6tables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT
If you’re enabling QUIC/HTTP/3, you also need UDP 443. Our Nginx QUIC guide covers the web-server side: enable HTTP/3 (QUIC) on Nginx.
Add this firewall rule:
sudo iptables -A INPUT -p udp --dport 443 -j ACCEPT
sudo ip6tables -A INPUT -p udp --dport 443 -j ACCEPT
Step 7: allow essential ICMP (don’t break PMTU)
Blindly blocking ICMP is a great way to create “some pages load, some hang” tickets. Allow it and move on.
sudo iptables -A INPUT -p icmp -j ACCEPT
For IPv6, ICMPv6 is required for basic network health (neighbor discovery, PMTU). Allow it:
sudo ip6tables -A INPUT -p ipv6-icmp -j ACCEPT
Step 8: add basic bad-packet drops
These cut down on junk traffic. They won’t stop a real DDoS, and they don’t try to.
# Drop invalid states
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
sudo ip6tables -A INPUT -m conntrack --ctstate INVALID -j DROP
Step 9 (optional): rate-limit new SSH attempts
This won’t replace Fail2Ban. It can make bursty scans less annoying.
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m recent --set --name SSH
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m recent --update --seconds 60 --hitcount 10 --name SSH -j DROP
If you want application-aware bans (WordPress logins, XML-RPC, wp-admin), use Fail2Ban. It’s easier to live with long-term: Fail2Ban for SSH and WordPress brute-force protection.
Step 10: log a small sample of drops
Logging every dropped packet will fill disks and bury useful signals. Sample a little, and keep the prefix obvious.
sudo iptables -A INPUT -m limit --limit 6/min --limit-burst 20 \
-j LOG --log-prefix "iptables-drop " --log-level 4
Your final drop is already handled by the default policy. If you prefer an explicit rule anyway:
sudo iptables -A INPUT -j DROP
Quick test: don’t guess
From your workstation, verify:
- SSH:
ssh -p 22 user@your_server_ip - HTTP:
curl -I http://yourdomain - HTTPS:
curl -I https://yourdomain
On the server, check counters to confirm packets are hitting the rules you expect:
sudo iptables -L -n -v --line-numbers
Make iptables rules persistent (Ubuntu/Debian and RHEL-family)
If you don’t persist rules, the next reboot can surprise you. Depending on service startup order, you might reopen services or block access.
Ubuntu/Debian with netfilter-persistent
sudo netfilter-persistent save
sudo systemctl enable --now netfilter-persistent
Rules save to:
/etc/iptables/rules.v4/etc/iptables/rules.v6
AlmaLinux/Rocky with iptables-services
sudo service iptables save
sudo systemctl enable --now iptables
Rules typically save to /etc/sysconfig/iptables and /etc/sysconfig/ip6tables.
Hosting-specific port checklist (open only what you run)
Most web servers need only 22, 80, and 443. Panels and mail stacks expand that list fast. Keep it tight, especially on public interfaces.
- cPanel/WHM: 2087 (WHM), 2083 (cPanel), 2086/2082 (non-SSL, usually avoid), 2078/2077 (WebDisk)
- Plesk: 8443, 8880 (non-SSL, often avoid)
- DirectAdmin: 2222
- FTP: 21 + passive range (prefer SFTP instead)
- Mail: 25 (SMTP), 465 (SMTPS), 587 (submission), 110/995 (POP3), 143/993 (IMAP)
Example: allow only WHM to your office IP, not the whole internet:
sudo iptables -A INPUT -p tcp -s 203.0.113.10/32 --dport 2087 -j ACCEPT
Running email on the same box? Before you open mail ports, make sure DNS is correct (SPF/DKIM/DMARC and rDNS).
Two practical references:
- SPF, DKIM, and DMARC setup guide for deliverability
- Reverse DNS (rDNS) setup for VPS and dedicated servers
Common pitfalls (and fast fixes)
You locked yourself out
- Use provider console access, then restore:
iptables-restore < /root/iptables.before.v4 - If the rollback timer is still pending, wait two minutes and reconnect.
- Verify the SSH rule has the right port and the right source IP/CIDR.
HTTPS works but HTTP/3 doesn’t
If you enabled QUIC in Nginx, confirm UDP 443 is allowed. TCP 443 alone won’t carry HTTP/3.
Some clients hang or uploads stall
That’s often PMTU trouble. You probably blocked ICMP or ICMPv6. Re-allow it as shown above.
Your logs are filling disk
Lower the log rate or remove the LOG rule. You can also route firewall logs to a separate file via rsyslog. Start by reducing volume.
Verify from the outside: a quick “hosting reality check”
Run these from a separate machine (not from the server). Replace the IP.
# Basic port scan (only the ports you expect should be open)
nmap -Pn -p 22,80,443 YOUR.SERVER.IP
# If you run a panel
nmap -Pn -p 2087,2083 YOUR.SERVER.IP
If you manage multiple servers, write down each host’s exposed ports in your inventory. During an incident, that note saves time.
When iptables isn’t enough: pair it with monitoring and app-layer controls
iptables is perimeter control. It won’t tell you PHP-FPM is wedged, disk is full, or the kernel is dropping packets under load.
Add monitoring early so you see failures before users do.
Follow our guide to set up uptime checks, alerts, and resource tracking on Ubuntu. You’ll catch problems while they’re still small.
For WordPress-heavy boxes, you’ll usually get more benefit from web-server tuning than from piling on firewall rules. Keep this ruleset tight, then optimize Nginx/PHP: Nginx performance optimization for WordPress and PHP sites.
Summary: your production-ready baseline
- Default DROP for inbound; allow only what you need.
- Keep
ESTABLISHED,RELATEDand loopback allowed. - Allow SSH carefully (prefer allowlists + keys).
- Open 80/443 (and UDP 443 if you run HTTP/3).
- Allow ICMP/ICMPv6 to avoid PMTU headaches.
- Persist rules and test from outside.
If you want a hosting platform where you can apply changes like this confidently, start with a HostMyCode VPS. If your priority is uptime and support rather than day-to-day maintenance, managed VPS hosting is the safer path for production websites.
Need a server that’s meant to be administered—not babysat? HostMyCode offers VPS plans for hands-on control and managed VPS hosting when you want the firewall, updates, and monitoring handled by a team.
FAQ
Should I use UFW or iptables on a hosting VPS?
Use UFW if you want a simpler interface and fewer moving parts. Use iptables if you need precise rule ordering, custom chains, or you’re matching an existing fleet. Either works if you keep the rules small and you test them.
Do I need separate rules for IPv6?
Yes. If your server has IPv6, leaving it unfiltered can expose services you assumed were blocked. Mirror your IPv4 intent in ip6tables and allow ICMPv6.
Will iptables stop DDoS attacks?
It helps with low-level noise (invalid packets, small scans). Large volumetric DDoS requires upstream mitigation and capacity planning. Keep iptables simple and avoid expensive rules that burn CPU under load.
What’s the safest way to apply changes without getting locked out?
Keep an SSH session open, schedule a rollback timer, then apply rules in small steps. Test connectivity after each step, and only persist once you’ve verified from the outside.
What ports should I open for a typical WordPress VPS?
Usually 22 (SSH), 80 (HTTP), and 443 (HTTPS). Add UDP 443 if you run HTTP/3. Avoid exposing database ports publicly, and prefer SFTP over FTP.