How to Configure a Firewall for Apache

How to configure a firewall for Apache

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:

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:

I always confirm Apache’s listening ports first:

sudo apachectl -S
sudo ss -tulnp | grep apache2

Real-World Use Cases I’ve Run Into

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)

Security Best Practices I Follow

Performance Considerations

Every packet gets evaluated against the rule set, so I:

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

References

Exit mobile version