How to Block External Port Access on Ubuntu Using UFW (Complete Production‑Ready Guide)
Introduction
In modern server environments, security is not optional. One of the most common and dangerous mistakes made on production servers is leaving unnecessary ports open to the public internet. Open ports are an open invitation for:
-
Port scanning
-
Brute‑force attacks
-
Unauthorized access
-
Exploitation of misconfigured services
Many backend services like FastAPI, Django, Node.js, PostgreSQL, Redis, Celery, internal admin panels, etc., are not meant to be exposed publicly. These services should only be accessible inside the server or via a reverse proxy like Nginx.
This article is a deep, production‑grade guide on how to:
-
Block ports from outside access
-
Allow access only inside the server
-
Properly configure UFW (Uncomplicated Firewall)
-
Avoid common mistakes
-
Secure FastAPI, Node.js, PostgreSQL, and similar services
-
Understand UFW rule priority
-
Apply best practices used in real production systems
This guide is written for Ubuntu servers (18.04, 20.04, 22.04, 24.04).
Understanding the Problem
Let’s take a real‑world example:
You have:
-
FastAPI running on port 8000
-
React / Node app running on port 3000
-
PostgreSQL on port 5432
-
Nginx serving public traffic on 80 / 443
You want:
-
🌍 Public internet → ONLY 80 & 443
-
🏠 Internal server → 3000, 8000, 5432
-
❌ External access → blocked for 3000 & 8000
This is exactly how production servers are designed.
What Is UFW?
UFW (Uncomplicated Firewall) is a frontend for iptables designed to make firewall configuration simple, readable, and manageable.
Why UFW?
-
Simple syntax
-
Enabled by default on Ubuntu
-
Production safe
-
Supports IPv4 & IPv6
-
Used by cloud providers and enterprises
How UFW Works (Very Important)
UFW processes rules top to bottom.
👉 First match wins
This means:
-
If an
ALLOWrule appears before aDENYrule, traffic is allowed -
Rule order matters
Many developers add deny rules but forget existing allow rules — making the firewall ineffective.
Step 1: Check UFW Status
sudo ufw status
If inactive:
sudo ufw enable
Step 2: View Rule Order (Critical Step)
sudo ufw status numbered
Example output:
[1] 80/tcp ALLOW Anywhere
[2] 443/tcp ALLOW Anywhere
[3] 8000/tcp ALLOW Anywhere
[4] 3000/tcp ALLOW Anywhere
[5] 8000 DENY Anywhere
[6] 3000 DENY Anywhere
⚠️ This configuration is WRONG.
Why?
Because ALLOW 8000 appears before DENY 8000.
Step 3: Remove Public ALLOW Rules
You must delete ALLOW Anywhere rules for internal ports.
sudo ufw delete 3
sudo ufw delete 4
Also remove IPv6 rules if present.
Step 4: Block Ports from Outside
sudo ufw deny 8000
sudo ufw deny 3000
Now external traffic is blocked.
Step 5: Allow Only Localhost Access
This allows internal communication inside the server.
sudo ufw allow from 127.0.0.1 to any port 8000
sudo ufw allow from 127.0.0.1 to any port 3000
For IPv6 localhost:
sudo ufw allow from ::1 to any port 8000
sudo ufw allow from ::1 to any port 3000
Final Correct Firewall State
sudo ufw status
Expected result:
8000 DENY Anywhere
3000 DENY Anywhere
8000 ALLOW 127.0.0.1
3000 ALLOW 127.0.0.1
✔ Secure
✔ Clean
✔ Production‑ready
Binding Services to Localhost (Best Practice)
Firewall rules are good, but binding services correctly is even better.
FastAPI / Uvicorn
uvicorn main:app --host 127.0.0.1 --port 8000
Django
python manage.py runserver 127.0.0.1:8000
Node / React
npm start -- --host 127.0.0.1 --port 3000
Never bind internal services to 0.0.0.0 in production.
Using Nginx as Reverse Proxy
Nginx listens on 80 / 443 and forwards traffic internally.
Example:
server {
listen 443 ssl;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Public users never see port 8000.
PostgreSQL Security
If PostgreSQL is internal only:
sudo ufw deny 5432
sudo ufw allow from 127.0.0.1 to any port 5432
Also update postgresql.conf:
listen_addresses = 'localhost'
Testing Your Configuration
External test:
telnet SERVER_IP 8000
❌ Should fail
Internal test:
curl http://127.0.0.1:8000
✅ Should succeed
Common Mistakes to Avoid
-
❌ Leaving
ALLOW Anywhererules -
❌ Binding services to
0.0.0.0 -
❌ Forgetting IPv6 rules
-
❌ Exposing databases publicly
-
❌ Using firewall without reverse proxy
Production Security Checklist
-
✅ UFW enabled
-
✅ Only 80 / 443 public
-
✅ Internal ports blocked
-
✅ Services bound to localhost
-
✅ Nginx reverse proxy
-
✅ SSH protected
-
✅ IPv6 handled
Conclusion
Blocking external port access is not optional — it is a core production security requirement.
With proper UFW rules, correct service binding, and Nginx as a reverse proxy, you achieve:
-
Higher security
-
Cleaner architecture
-
Better scalability
-
Reduced attack surface
This setup is used by:
-
Cloud providers
-
Enterprises
-
High‑traffic production systems
If you follow this guide exactly, your Ubuntu server will be secure, professional, and production‑ready.
Need Help?
If you want:
-
Custom rules for Docker
-
Kubernetes / k3s setup
-
Cloud firewall (AWS / GCP / Azure)
-
Nginx + SSL hardening
You can build on this foundation confidently.
Happy securing 🔐
