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 ALLOW rule appears before a DENY rule, 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

  1. ❌ Leaving ALLOW Anywhere rules

  2. ❌ Binding services to 0.0.0.0

  3. ❌ Forgetting IPv6 rules

  4. ❌ Exposing databases publicly

  5. ❌ 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 🔐

apione.in

Comments

Leave a Reply