Skip to content

Understanding Firewalls & Ports

💡
Before you start

You need Python 3 and a text editor — nothing else, and no administrator rights. Check with python3 --version in a terminal (on Windows, py --version); any 3.x is fine. If that prints nothing useful, do Introduction to Python first.

The first four steps build a tiny firewall in Python and watch it accept and drop packets. It touches nothing on your machine — it is a model you can read top to bottom, which is the whole point: a real firewall makes the same decision the same way, just faster and in the kernel. The last step only reads the real firewall already on your computer; it changes no rule. When you are ready to configure a real firewall, the step-by-step for that is Set Up a UFW Firewall.

What is a Firewall?

A firewall is a security system that monitors and controls network traffic based on predetermined rules. It acts as a barrier between a trusted internal network and untrusted external networks (like the internet).

Think of a firewall as a security guard at a building entrance. It checks each visitor (network packet) against a list of rules and decides whether to let them in, send them out, or turn them away.

How Firewalls Work: Packet Filtering

At the most basic level, firewalls examine each network packet and check it against rules based on:

  • Source IP address: Where the packet came from
  • Destination IP address: Where the packet is going
  • Port number: Which service the packet is trying to reach
  • Protocol: TCP, UDP, ICMP, etc.
  • Direction: Inbound (coming in) or outbound (going out)
Stateless firewall Examines each packet independently. Simpler but less intelligent.
Stateful firewall Tracks the state of connections. Knows whether a packet is part of an established conversation. More secure.

Understanding Network Ports

Ports are numbered endpoints (0-65535) that identify specific services on a computer. When you visit a website, your browser connects to port 443 (HTTPS) or port 80 (HTTP) on the web server.

Well-Known Ports (0-1023)

Port 22 SSH (Secure Shell) - Remote terminal access
Port 53 DNS (Domain Name System) - Name resolution
Port 80 HTTP - Unencrypted web traffic
Port 443 HTTPS - Encrypted web traffic
Port 25 SMTP - Email sending
Port 3389 RDP - Windows Remote Desktop

TCP vs UDP

TCP (Transmission Control Protocol) Reliable, ordered delivery. Used for web, email, SSH, file transfers. Connection-oriented.
UDP (User Datagram Protocol) Fast but no delivery guarantee. Used for DNS, video streaming, gaming, VoIP. Connectionless.

Types of Firewalls

Software firewall Runs on your computer (Windows Firewall, UFW on Linux). Protects that specific device.
Hardware firewall A dedicated device (your router has a basic one). Protects your entire network.

For best protection, use both: a hardware firewall at the network edge (your router) and software firewalls on each device.

The Default Deny Principle

The most secure firewall approach is "default deny": block everything by default, then create specific rules to allow only the traffic you need.

💡
Default deny in practice

Block all incoming connections by default. Then allow only the specific ports your services need (e.g., port 80/443 for a web server, port 22 for SSH). This minimizes attack surface.

# Example with UFW (Linux):
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp    # Only allow SSH
sudo ufw allow 443/tcp   # Only allow HTTPS
sudo ufw enable

Now Do It Yourself: Build a Firewall in Five Steps

A firewall sounds like a wall; it is really a short list you read from the top. Each line says “traffic that looks like this gets this verdict”, and whatever no line mentions falls through to a default. You will build that list in fifteen lines of Python, watch it accept the traffic you want and drop the rest, learn why the order of the lines matters, see the single mistake that quietly exposes a server, and then read the real firewall on your own machine. Every block of output below was produced by running this exact code.

1
Write the rule list and a default of “drop”

Go: open a terminal in a folder you can write to (mkdir fw-lab && cd fw-lab) and open your text editor.

Do: save this as firewall.py and run python3 firewall.py. The rules allow three ways in; the default drops everything else.

rules = [
    ("tcp", 22,  "ACCEPT"),   # SSH
    ("tcp", 443, "ACCEPT"),   # HTTPS
    ("tcp", 80,  "ACCEPT"),   # HTTP
]
DEFAULT = "DROP"

def decide(proto, port):
    for r_proto, r_port, verdict in rules:   # top to bottom, first match wins
        if proto == r_proto and port == r_port:
            return verdict, "rule %s/%d" % (r_proto, r_port)
    return DEFAULT, "default policy"

for proto, port in [("tcp",22),("tcp",443),("tcp",3306),("tcp",23),("udp",53)]:
    verdict, why = decide(proto, port)
    print("%-5s port %-5d -> %-6s (%s)" % (proto, port, verdict, why))

You should see: the two allowed ports pass, and everything else is dropped by the default — nobody had to write a rule for the database or telnet:

tcp   port 22    -> ACCEPT (rule tcp/22)
tcp   port 443   -> ACCEPT (rule tcp/443)
tcp   port 3306  -> DROP   (default policy)
tcp   port 23    -> DROP   (default policy)
udp   port 53    -> DROP   (default policy)

If not: a SyntaxError almost always means a mistyped bracket or a missing comma in the rules list — the arrows in the output are -> (a hyphen and a greater-than), and Python prints them literally from the format string; you do not type any special character.

2
Open a port by adding one line

Go: same file.

Do: your team just launched a service on port 8080. Add one rule for it to the rules list and add ("tcp",8080) to the test packets, then run it again.

    ("tcp", 8080, "ACCEPT"),   # new internal dashboard

You should see: port 8080 now prints ACCEPT (rule tcp/8080) where before it would have been dropped by the default. That single line is what “opening a port in the firewall” means — no more, no less.

If not: if 8080 still drops, you added the rule but forgot to add ("tcp",8080) to the list of packets being tested, so nothing is asking about it. The rule list and the traffic are two separate things.

3
Prove that order matters: first match wins

Go: a fresh file, order.py.

Do: put a DROP for port 3306 above an ACCEPT for the same port, then ask about a packet to 3306.

rules = [
    ("tcp", 3306, "DROP"),     # block the database port
    ("tcp", 3306, "ACCEPT"),   # a later ACCEPT for the same port
]
def decide(port):
    for rp, rport, v in rules:
        if rp=="tcp" and rport==port:
            return v
    return "DROP"
print("packet tcp/3306 ->", decide(3306))
print("(the ACCEPT below the DROP never runs)")

You should see: packet tcp/3306 -> DROP. The ACCEPT written underneath it is dead — the loop stops at the first line that matches. This is why a real firewall’s rule order is not cosmetic: a broad DROP placed too high can silently cancel every specific ALLOW beneath it.

If not: if you get ACCEPT, you have the two rules in the other order — put the DROP first, as shown, to see the effect.

4
See the one mistake that exposes a server: the wrong default

Go: a fresh file, default.py.

Do: flip the default from DROP to ACCEPT, keep a single rule that blocks the database, and test three services — one you remembered and two you did not.

rules = [("tcp", 3306, "DROP")]   # you remembered the database
DEFAULT = "ACCEPT"                # ...but the default lets everything else in
def decide(port):
    for rp, rport, v in rules:
        if rp=="tcp" and rport==port: return v
    return DEFAULT
for port, name in [(3306,"database"),(6379,"redis you forgot"),(9200,"search you forgot")]:
    print("%-18s tcp/%-5d -> %s" % (name, port, decide(port)))

You should see: the database is blocked, but the two services you never wrote a rule for are wide open:

database           tcp/3306  -> DROP
redis you forgot   tcp/6379  -> ACCEPT
search you forgot  tcp/9200  -> ACCEPT

This is the whole argument for default deny. With DEFAULT = "DROP" you can only be exposed by a port you explicitly opened; with DEFAULT = "ACCEPT" you are exposed by every port you forgot, which is an endless list. Deny by default, then allow the few things you actually need.

If not: if everything shows DROP, you left DEFAULT as "DROP" — the point of this step is to set it to "ACCEPT" and watch the forgotten services leak.

5
Read the real firewall on your own machine — without changing it

Go: a terminal on your own Linux computer. This step only reads; it enables nothing and blocks nothing.

Do: run sudo ufw status verbose. On a Red Hat-family system use sudo firewall-cmd --list-all instead.

You should see: either Status: inactive (no firewall is running yet) or a Default: line followed by your real ALLOW rules — the same shape you just built in Python, now enforced by the kernel. Read the Default: line first: deny (incoming) is the posture you want.

If not: ufw: command not found means it is not installed (sudo apt install ufw); installing it changes nothing until you enable it. When you are ready to turn a firewall on safely — allowing your own SSH in before you close the door, so you never lock yourself out — follow Set Up a UFW Firewall step by step.

🎉
Check yourself before moving on

Without scrolling up: a server runs a firewall whose default policy is ACCEPT, with one rule that drops port 3306. A colleague starts a new database on port 5432 and forgets to tell anyone. Is port 5432 reachable from the internet? Answer: yes. Nothing matches port 5432, so it falls through to the default — and the default is ACCEPT. With a default of DROP it would have been closed automatically. This is exactly the leak from step 4.

Now do it without the page: go back to your step-1 firewall.py and add a rule that allows DNS replies — UDP on port 53. You will need to widen the rule tuples and the decide match to compare the protocol too, not just the port. Test it with the packet ("udp",53) and confirm it flips from DROP to ACCEPT.

Summary

In this tutorial, you learned:

  • What firewalls are and how packet filtering works
  • The difference between stateful and stateless firewalls
  • Common network ports and what they are used for
  • TCP vs UDP protocols
  • Hardware vs software firewalls
  • The default deny principle for maximum security
🎉
You now understand firewall fundamentals!

This knowledge applies to every firewall tool you will encounter, from UFW to iptables to Windows Firewall to enterprise solutions.