UFW Firewall Setup for Nginx on Ubuntu: 5 Essential Rules

- How to Set Up a UFW Firewall for Nginx on Ubuntu
- Before You Start: Check UFW's Status
- Rule 1: Set Default Policies (Deny In, Allow Out)
- Rule 2: Allow SSH Before You Enable UFW
- Rule 3: Allow Web Traffic with 'Nginx Full'
- Rule 4: Rate-Limit SSH Against Brute Force
- Rule 5: Keep Internal Services Off the Public List
- Verify and Manage Your Rules
- Common Mistakes to Avoid
How to Set Up a UFW Firewall for Nginx on Ubuntu
UFW (Uncomplicated Firewall) is Ubuntu's built-in front end for the kernel's netfilter firewall, and it ships installed but disabled on Ubuntu Server. Securing an Nginx web server with UFW takes five rules: set default policies to deny incoming and allow outgoing traffic, allow SSH before enabling the firewall, allow web traffic with the Nginx Full application profile, rate-limit SSH with ufw limit, and keep every internal service — MySQL, PHP-FPM, Redis — off the public rule list entirely.
The short version looks like this:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
The rest of this guide explains what each rule does, why the order matters, and how to verify and manage the result. If you're starting from a bare server, our LEMP stack install guide covers getting Nginx running in the first place; this article assumes Nginx is installed and you're connected over SSH.
A public server gets probed by automated scanners within minutes of coming online. The firewall is not a finishing touch — it belongs in the same session as the install.
Before You Start: Check UFW's Status
First, confirm UFW is present and see its current state:
sudo ufw status verbose
On a fresh Ubuntu Server install you'll see Status: inactive. That means no UFW rules are being enforced yet — every port your services listen on is reachable from the internet.
If the command isn't found (some minimal images omit it), install it:
sudo apt update
sudo apt install ufw
Installing UFW does not enable it. Nothing changes until you run ufw enable, which is exactly the safety margin you want: you can stage all five rules first, then switch the firewall on knowing SSH is already allowed.
Rule 1: Set Default Policies (Deny In, Allow Out)
A firewall is only as good as its default posture. The standard policy for a web server is: refuse everything inbound unless explicitly allowed, and permit everything outbound so the server can fetch packages, renew certificates, and make DNS queries.
sudo ufw default deny incoming
sudo ufw default allow outgoing
These are actually UFW's defaults on Ubuntu, but set them explicitly anyway. It costs two commands, it makes your setup reproducible on servers where someone changed the defaults, and it documents your intent.
With deny incoming as the baseline, every later rule is an exception you consciously carve out. That's the right mental model: your public rule list should be a short, complete inventory of what the world is allowed to reach.
Rule 2: Allow SSH Before You Enable UFW
This is the step that locks people out of their own servers. If you enable UFW with a default-deny inbound policy and no SSH rule, your current session may survive, but the next connection attempt will be refused — and on a remote VPS with no console access, that's a genuine emergency.
So allow SSH first:
sudo ufw allow OpenSSH
OpenSSH is an application profile registered by the openssh-server package; it opens TCP port 22. If you've moved SSH to a non-standard port, allow that port explicitly instead:
sudo ufw allow 2222/tcp
Only after the SSH rule exists should you enable the firewall:
sudo ufw enable
UFW will warn that enabling may disrupt existing SSH connections — that warning is precisely why this rule comes before enable, not after. Confirm with y, then verify from a second terminal that you can still open a fresh SSH session before you close the one you're in. UFW also persists across reboots once enabled, so this is one-time setup, not something to script into cron.
Rule 3: Allow Web Traffic with 'Nginx Full'
On Ubuntu, the Nginx package registers UFW application profiles — named bundles of ports defined in /etc/ufw/applications.d/. List them:
sudo ufw app list
You should see three Nginx entries:
| Profile | Ports opened | Use when |
|---|---|---|
Nginx HTTP |
80/tcp | Serving plain HTTP only |
Nginx HTTPS |
443/tcp | Serving HTTPS only (rare — you almost always need 80 too) |
Nginx Full |
80/tcp and 443/tcp | Serving HTTPS with HTTP available for redirects and renewals |
For any real site, use Nginx Full:
sudo ufw allow 'Nginx Full'
The quotes matter — the profile name contains a space. Why open port 80 even on an HTTPS-only site? Two reasons. First, your Nginx config should redirect HTTP requests to HTTPS, and that redirect can only happen if port 80 is reachable. Second, Certbot's default HTTP-01 challenge validates domain ownership over port 80, so Let's Encrypt certificate issuance and renewal depends on it staying open.
To see exactly what a profile opens before you trust it:
sudo ufw app info 'Nginx Full'
Prefer profiles over raw port numbers where they exist. Allow Nginx Full in your status output tells a future admin what the rule is for; 80,443/tcp makes them go look it up.
If you're hosting several sites on one server, the firewall rule doesn't change — ports 80 and 443 serve all of them, and Nginx server blocks handle routing each domain to the right site.
Rule 4: Rate-Limit SSH Against Brute Force
An open port 22 collects password-guessing attempts around the clock. UFW has a built-in mitigation: the limit rule, which denies a source IP that opens 6 or more connections within 30 seconds.
Replace the plain allow rule with a limit rule:
sudo ufw delete allow OpenSSH
sudo ufw limit OpenSSH
Legitimate use is unaffected — you'd have to reconnect several times in half a minute to trip it — but it blunts automated brute-force scripts considerably.
Two honest caveats. First, limit is a mitigation, not a substitute for key-based SSH authentication with passwords disabled; do both. Second, if you use tools that open many rapid SSH connections (some deployment scripts, rsync loops, parallel Ansible runs), you can trigger the limit yourself — in that case keep the plain allow rule for a trusted admin IP:
sudo ufw allow from 203.0.113.10 to any port 22 proto tcp
Do not apply limit to the Nginx ports. Web browsers legitimately open multiple connections in quick succession, and CDNs or proxies funnel many users through one IP — rate limiting for web traffic belongs in Nginx itself, not the packet filter.
Rule 5: Keep Internal Services Off the Public List
The most important rules on a LEMP server are the ones you don't add. MySQL (port 3306) and PHP-FPM should never appear in your UFW rule list, because nothing outside the server should reach them:
- MySQL on Ubuntu binds to
127.0.0.1by default, so it isn't reachable externally anyway — and with default-deny, UFW would refuse the traffic even if it were. Never runufw allow 3306because a tutorial told you to. - PHP-FPM communicates with Nginx over a local Unix socket (or a loopback TCP port). Firewalls don't apply to Unix sockets; there's nothing to allow.
- Redis, Memcached, and similar caches follow the same pattern: loopback-only binding, no firewall rule.
If you genuinely need remote database access — say, from a separate application server — allow it only from that server's specific IP:
sudo ufw allow from 203.0.113.20 to any port 3306 proto tcp
That still requires changing MySQL's bind-address, which is a deliberate architectural decision, not a default. For everyone else, the correct number of database rules is zero.
The test is simple: run sudo ufw status and read every line aloud. If you can't say what a rule is for and who needs it, delete it.
Verify and Manage Your Rules
After setup, confirm the full picture:
sudo ufw status verbose
A healthy Nginx web server firewall reads like this:
Status: active
Default: deny (incoming), allow (outgoing), disabled (routed)
To Action From
-- ------ ----
OpenSSH LIMIT Anywhere
Nginx Full ALLOW Anywhere
OpenSSH (v6) LIMIT Anywhere (v6)
Nginx Full (v6) ALLOW Anywhere (v6)
The (v6) entries are automatic — UFW mirrors rules for IPv6 when it's enabled in /etc/default/ufw, which it is by default.
Deleting a Rule
Use numbered mode to remove rules precisely:
sudo ufw status numbered
sudo ufw delete 2
UFW shows you the rule and asks for confirmation before deleting. Note that numbers shift after each deletion, so re-run status numbered between deletes rather than firing off several numbers in a row.
You can also delete by repeating the original rule with delete prefixed, as in sudo ufw delete allow 'Nginx Full' — useful in scripts where numbering is unreliable.
Resetting or Disabling
Two commands worth knowing and using carefully:
sudo ufw disable # turns enforcement off, keeps your rules
sudo ufw reset # disables AND wipes all rules back to defaults
If you reset, you're back to square one — re-add the SSH rule before enabling again.
Common Mistakes to Avoid
- Enabling UFW before allowing SSH. The classic lockout. Always
allow OpenSSH(or your custom port) first. - Allowing only
Nginx HTTPS. Breaks HTTP-to-HTTPS redirects and Certbot's HTTP-01 renewals. UseNginx Full. - Opening 3306 "to fix a connection error". Database connection problems on a single-server LEMP setup are configuration issues, not firewall issues — MySQL and your PHP app talk over loopback.
- Forgetting the firewall exists. When a new service "doesn't work" from outside, check
ufw statusbefore spending an hour in the service's own config. - Treating UFW as complete security. A firewall controls which ports are reachable; it does nothing about vulnerable code behind an open port. Pair it with HTTPS, key-based SSH, and regular
apt upgraderuns.
Five rules, two of which are "don't add rules you don't need" — that's genuinely all a standard Nginx server requires from UFW. The best firewall configuration is a short one you fully understand.