TL;DR
Complete guide to configuring UFW on Ubuntu for web servers covering default policies, application rules, rate limiting, and common stack configurations.
Key facts
- Topology
- UFW on Ubuntu for web servers
TL;DR
UFW (Uncomplicated Firewall) is the standard firewall interface on Ubuntu. For web servers, the goal is simple: deny everything inbound by default, then explicitly allow only SSH, HTTP, and HTTPS. This guide covers installation through to production-ready rules for common web stacks including MySQL, Redis, and admin panel access restrictions.
Install and enable UFW
UFW is pre-installed on most Ubuntu versions. If missing:
sudo apt update && sudo apt install -y ufw
Before enabling, ensure your SSH rule is in place so you do not lock yourself out:
sudo ufw allow OpenSSH
sudo ufw --force enable
sudo ufw status
Set default policies
Deny all inbound traffic and allow all outbound. This is the foundation — every open port must be an explicit decision:
sudo ufw default deny incoming
sudo ufw default allow outgoing
Allow web traffic
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
If you use a non-standard SSH port:
sudo ufw delete allow OpenSSH
sudo ufw allow 2222/tcp comment 'SSH custom port'
Rate limit SSH
UFW's built-in rate limiting blocks IPs that attempt more than six connections in 30 seconds:
sudo ufw limit 2222/tcp comment 'SSH rate limited'
This provides a basic layer of brute-force protection without fail2ban (though both together is recommended).
Restrict admin panel access by IP
If you run a Filament admin panel, phpMyAdmin, or any management interface, restrict access to known IPs only:
sudo ufw allow from 203.0.113.10 to any port 443 proto tcp comment 'Admin office IP'
sudo ufw allow from 198.51.100.0/24 to any port 443 proto tcp comment 'VPN subnet'
For more granular path-based restrictions, handle this at the nginx level and use UFW for port-level controls.
Database and cache — local only
MySQL and Redis should never be exposed to the public internet. Bind them to localhost in their own configurations and ensure UFW has no rule allowing external access:
# Verify no rule exposes MySQL or Redis
sudo ufw status numbered | grep -E '3306|6379'
If another server in your private network needs database access, allow only that specific IP:
sudo ufw allow from 10.0.1.5 to any port 3306 proto tcp comment 'App server to MySQL'
sudo ufw allow from 10.0.1.5 to any port 6379 proto tcp comment 'App server to Redis'
Common web stack rules
A typical Laravel or Node.js web server needs exactly this:
sudo ufw status verbose
# Status: active
# Default: deny (incoming), allow (outgoing), disabled (routed)
#
# To Action From
# -- ------ ----
# 2222/tcp LIMIT IN Anywhere # SSH rate limited
# 80/tcp ALLOW IN Anywhere # HTTP
# 443/tcp ALLOW IN Anywhere # HTTPS
No additional ports should be open unless you have a documented reason.
Enable logging
sudo ufw logging on
sudo ufw logging medium
Logs write to /var/log/ufw.log. Review blocked connections to spot scanning attempts:
sudo grep "UFW BLOCK" /var/log/ufw.log | tail -20
IPv6 support
Ensure UFW applies rules to both IPv4 and IPv6:
grep "IPV6=yes" /etc/default/ufw
If it reads IPV6=no, edit the file and reload:
sudo sed -i 's/IPV6=no/IPV6=yes/' /etc/default/ufw
sudo ufw reload
Checking status and resetting
# Numbered list for easy deletion
sudo ufw status numbered
# Delete a specific rule by number
sudo ufw delete 4
# Full reset — removes all rules and disables UFW
sudo ufw reset
With Reflex
Reflex monitors your firewall state and alerts you when rules change unexpectedly, when ports appear open that should not be, or when UFW is disabled — catching configuration drift before it becomes a security incident. See How it works.