The first time I put a fresh Apache server on the public internet without touching the firewall, I watched the access logs fill up with scanner traffic within minutes. Bots were probing for open ports, poking at admin paths, and trying default credentials before I’d even finished installing my first virtual host. That experience taught me something I now treat as non-negotiable: a firewall isn’t an optional extra for an Apache deployment, it’s step one.
In this post I’m walking through how I configure a firewall for Apache, covering firewalld, ufw, and raw iptables/nftables, so you can pick whichever fits your distribution and comfort level.
What a Firewall Actually Does for a Web Server
A firewall filters packets based on rules I define — deciding what’s allowed to reach the machine and what gets dropped before it ever touches Apache. For a web server, I keep the job narrow on purpose:
- Allow inbound traffic on the ports Apache is actually listening on (usually 80 for HTTP and 443 for HTTPS).
- Allow the administrative access I need to manage the box (SSH on port 22, ideally locked to known IPs).
- Block everything else, including any service running on the machine that shouldn’t be internet-facing.
Without this filtering, every service on the box — even ones I forgot I installed — is potentially reachable from anywhere.
Prerequisites
Before I touch any firewall rules, I make sure I have:
- A Linux server with Apache installed (or about to be)
- Root or sudo access
- A clear idea of which ports my applications actually need
I always confirm Apache’s listening ports first:
sudo apachectl -S
sudo ss -tulnp | grep apache2
Real-World Use Cases I’ve Run Into
- A public production web server where I only want 80/443/22 reachable, nothing else.
- An internal staging box I restrict to my office or VPN IP range.
- A multi-service host where Apache shares the machine with a database that should never be internet-facing.
- Compliance work, where PCI-DSS and similar standards explicitly require a host-based firewall.
Configuring firewalld (RHEL, CentOS, Fedora, AlmaLinux, Rocky Linux)
firewalld uses “zones” and predefined “services” mapped to specific ports, which I find easier to reason about than raw port numbers once I got used to it.
Checking status
sudo systemctl status firewalld
sudo firewall-cmd --state
Allowing HTTP and HTTPS
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
Verifying
sudo firewall-cmd --list-services
sudo firewall-cmd --list-all
Restricting SSH to a specific IP range
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" service name="ssh" accept'
sudo firewall-cmd --reload
Allowing a custom port
If I’ve got an app server sitting behind a reverse proxy:
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload
Configuring UFW (Ubuntu, Debian)
UFW is the front end I reach for on Debian-based boxes — “Uncomplicated Firewall” really does live up to the name.
Setting sensible defaults
sudo ufw default deny incoming
sudo ufw default allow outgoing
Allowing SSH before enabling UFW — I never skip this
sudo ufw allow OpenSSH
# or explicitly:
sudo ufw allow 22/tcp
I’ve locked myself out of a remote box exactly once by forgetting this step, and once was enough to make it a permanent habit.
Allowing Apache traffic
UFW auto-detects Apache’s application profile once Apache is installed:
sudo ufw app list
sudo ufw allow 'Apache Full' # allows both 80 and 443
If I only want HTTP for the moment:
sudo ufw allow 'Apache' # port 80 only
Enabling the firewall
sudo ufw enable
sudo ufw status verbose
Restricting by IP
sudo ufw allow from 203.0.113.10 to any port 22 proto tcp
Configuring iptables / nftables Directly
When I want full manual control, or I’m teaching someone the fundamentals, I drop down to raw iptables:
# Flush existing rules (be careful on a live server)
sudo iptables -F
# Allow loopback
sudo iptables -A INPUT -i lo -j ACCEPT
# Allow established/related connections
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow SSH
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Allow HTTP/HTTPS
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Drop everything else
sudo iptables -A INPUT -j DROP
I persist the rules on Debian/Ubuntu like this:
sudo apt install iptables-persistent
sudo netfilter-persistent save
Newer systems are shifting to nftables as the default backend — same logic, different syntax (nft add rule inet filter input tcp dport {80,443} accept), and I expect to lean on it more going forward.
Rate Limiting and Brute-Force Protection
I also use the firewall to throttle abusive traffic before it ever reaches Apache:
# iptables: limit new connections to port 80 to 25/minute per IP
sudo iptables -A INPUT -p tcp --dport 80 -m state --state NEW -m recent --set
sudo iptables -A INPUT -p tcp --dport 80 -m state --state NEW -m recent --update --seconds 60 --hitcount 25 -j DROP
For SSH specifically, I pair firewall rules with fail2ban, which reads logs and dynamically bans IPs showing malicious patterns.
Mistakes I’ve Made (So You Don’t Have To)
- Enabling the firewall without allowing SSH first — locks you out fast.
- Forgetting to open 443 after adding an SSL certificate, so HTTPS silently fails while HTTP keeps working.
- Leaving default database ports open to the public internet when the database only ever needs local access.
- Not persisting
iptablesrules, so a reboot wipes out protection I thought was permanent. - Confusing firewall rules with SELinux/AppArmor — a firewall controls network access; SELinux/AppArmor controls what a process can do once traffic gets through. I need both.
Security Best Practices I Follow
- Least privilege: only open the ports I actually need.
- Default deny on inbound traffic, with explicit exceptions.
- Pair firewall rules with
fail2banfor dynamic banning. - If Apache sits behind a load balancer, I restrict direct access to Apache’s port and only allow the proxy’s IP.
- Regular port audits with
ss -tulnpornmapfrom an external host. - Log dropped packets for forensic visibility, without going overboard and filling the disk.
Performance Considerations
Every packet gets evaluated against the rule set, so I:
- Keep rules as short and specific as possible.
- Put the most frequently matched rules (like established connections) near the top of the chain.
- Watch connection tracking memory usage — overly aggressive state tracking can exhaust kernel memory under DDoS conditions.
- For very high-traffic sites, I offload filtering to a cloud provider’s network firewall or a CDN/WAF in front of Apache, taking pressure off the origin’s own firewall.
Troubleshooting
Website unreachable after enabling the firewall
sudo firewall-cmd --list-all # or
sudo ufw status verbose
I check that 80/443 are explicitly allowed.
SSH connection refused after firewall changes If I still have an active session, I don’t close it until I’ve verified SSH access with a fresh connection in a separate terminal.
Firewall rules disappear after reboot I make sure firewalld or ufw are enabled at boot:
sudo systemctl enable firewalld
sudo systemctl enable ufw
FAQs
Do I need a firewall if my server is already behind a cloud provider’s security group? Yes. Cloud security groups filter traffic at the network edge, but a host-based firewall gives me defense in depth in case the security group gets misconfigured or the server moves.
Will a firewall slow down my website? Properly configured rules add negligible latency in my experience. Performance issues usually come from overly complex rule chains, not firewalls in general.
Can a firewall stop DDoS attacks? A host-based firewall mitigates small-scale abuse but can’t stop large volumetric DDoS — that requires upstream protection from an ISP, cloud provider, or dedicated mitigation service.
Should I block all countries except my own? Geo-blocking can cut down noise, but I never treat it as a substitute for proper firewall and application security, since attackers can route through allowed regions.
Summary and Key Takeaways
- A firewall is the first layer of defense I set up on any Apache deployment.
firewalldandufwgive me accessible, service-aware management on RHEL-based and Debian-based systems;iptables/nftablesgive me full manual control.- I always allow SSH before enabling a restrictive firewall.
- I combine firewall rules with
fail2ban, SELinux/AppArmor, and application-level hardening. - I audit open ports regularly and persist rules across reboots.
References
- Apache HTTP Server Documentation: https://httpd.apache.org/docs/
- firewalld Documentation: https://firewalld.org/documentation/
- UFW Documentation (Ubuntu): https://help.ubuntu.com/community/UFW
- Netfilter/iptables Project: https://www.netfilter.org/documentation/
- fail2ban Documentation: https://www.fail2ban.org/