Skip to content

Ports & Firewalls

We’ve gotten a request all the way to a machine. But a machine runs many programs — a web server, a database, an SSH daemon — and they all share one IP address. Ports are how the machine knows which program a connection is for, and firewalls are how it decides whether to let that connection in at all. This page is where networking turns into security: every open port is a door, and the whole game is opening as few as possible.

Recall from the network stack that the transport layer adds a port number to every connection. An IP address gets you to the machine; the port gets you to the process on it. The full coordinate is IP:port93.184.216.34:443 means “the HTTPS server on that host.”

Some port numbers are conventions everyone agrees on:

22 SSH 443 HTTPS 5432 PostgreSQL
53 DNS 3306 MySQL 6379 Redis
80 HTTP 8080 common dev 27017 MongoDB

Ports 0–1023 are well-known (binding them usually needs root, a small privilege guard). Above that is fair game for your apps. None of these are magic — they’re just defaults; you can run HTTPS on 8443. But conventions are why curl example.com knows to try port 80 without being told.

For a program to receive connections, it must bind to a port and start listening. “Bind” means “reserve this port; route its traffic to me.” Only one process can listen on a given port at a time — which is the source of the famous Address already in use error when you start a server and an old copy is still holding the port.

A subtle, security-relevant detail is which address a process binds to:

bind 0.0.0.0:8080 -> listen on ALL interfaces — reachable from the network
bind 127.0.0.1:8080 -> listen on loopback ONLY — reachable only from this machine

Binding a database to 0.0.0.0 exposes it to the whole network; binding it to 127.0.0.1 keeps it private to the host. Getting this wrong is how databases end up on the public internet. Your first tool here is ss:

Terminal window
ss -tlnp # every TCP port being listened on, and the process behind it
ss -tlnp | grep :5432 # is anything listening on the Postgres port — and bound where?

That single command answers “is my service even up, and is it exposed?” — the first question in countless incidents.

There aren’t enough IPv4 addresses for every device to have a public one (the scarcity from the stack page). NAT (Network Address Translation) solves this: a router gives devices private addresses (192.168.x.x) and translates them to its single public address on the way out, remembering which internal machine each connection belongs to so replies come back to the right place.

laptop 192.168.1.5 ----+
phone 192.168.1.6 ----+--> [ Router / NAT ] --> public IP 203.0.113.7 --> internet
TV 192.168.1.7 ----+ (rewrites source address, tracks the mapping)

NAT is why your home devices can reach the internet but the internet can’t reach them unbidden — there’s no inbound mapping until an outbound connection creates one. That’s accidental security, and it’s why exposing a service from behind NAT requires explicit port forwarding. In the cloud the same idea appears as private subnets with a NAT gateway: instances reach out for updates, but nothing reaches in.

A firewall is a rule set deciding which traffic is allowed. The single most important principle in all of network security fits in two words: default deny. Block everything, then open only the specific ports you’ve decided to expose. The opposite — allow everything, then try to block the bad stuff — is unwinnable, because you can’t enumerate every threat in advance.

DEFAULT ALLOW (wrong) DEFAULT DENY (right)
open: everything closed: everything
then: block known-bad -> then: open 443 (and maybe 22)
you must predict every attack attacker must find your one open door

The mindset: a port that isn’t open can’t be attacked. So expose 443 to the world, perhaps 22 (SSH) to your office IP only, and nothing else — your database and internal services stay reachable only from inside the network.

You’ll meet firewalls at two levels:

  • Cloud security groups (AWS), NSGs (Azure), firewall rules (GCP) — stateful, default-deny rules attached to instances or networks, managed through the cloud API or Infrastructure as Code. This is the modern, primary control: declarative, auditable, and applied before traffic ever reaches the host.
  • iptables / nftables — the Linux kernel’s own packet filter, configured per host. Lower-level and powerful, but per-machine and easy to drift. Containers use it heavily under the hood, and tools like ufw are friendly front-ends to it.
Terminal window
# A security group rule (conceptual): allow HTTPS from anywhere, SSH from the office only
# inbound tcp 443 from 0.0.0.0/0 (HTTPS — the public internet)
# inbound tcp 22 from 203.0.113.0/24 (SSH — office network only)
# (everything else: implicitly denied)
# The host-level equivalent with ufw:
sudo ufw default deny incoming
sudo ufw allow 443/tcp
sudo ufw allow from 203.0.113.0/24 to any port 22

Prefer security groups as the primary boundary — they’re declarative and version-controlled — and treat host firewalls as defense-in-depth, not your only line.

Step back from ss -tlnp and reason about ports and firewalls as the security boundary they are:

  • Why do they exist? A port exists because one machine runs many programs sharing one IP, so the full coordinate IP:port says which process a connection is for. A firewall exists because every open port is a door, and someone has to decide which doors open at all.
  • What problem do they solve? Ports disambiguate processes and let a service bind to claim traffic; the bind address (0.0.0.0 = all interfaces vs 127.0.0.1 = loopback only) is what keeps a database private. Firewalls solve attack surface via default-deny: block everything, then open only 443 and maybe 22 from your office.
  • What are the trade-offs? Default-deny is more friction than default-allow — every new exposure needs a deliberate rule — but the alternative is unwinnable, because you can’t enumerate every threat in advance. Security groups are declarative and applied before traffic reaches the host; iptables/ufw are per-host and easy to drift.
  • When should I avoid it? You don’t avoid the firewall — you choose its layer. Prefer cloud security groups as the primary, version-controlled boundary via IaC; treat host-level iptables/ufw as defense-in-depth, not your only line.
  • What breaks if I remove it? Remove default-deny and your attack surface becomes everything someone ever opened “to test it” and forgot — the GitHub 1.35 Tbps memcached flood happened because port 11211 sat open to the internet. A port that isn’t open can’t be attacked; remove the firewall and they all are.
  1. An IP address gets you to a machine. What does a port add, and why does one machine need many of them?
  2. What’s the difference between binding to 0.0.0.0 and 127.0.0.1, and how could the wrong choice expose a database to the internet?
  3. Explain NAT in one or two sentences. Why does it mean the internet can’t reach your laptop unbidden?
  4. State the default-deny principle and explain why “allow everything, then block bad traffic” is a losing strategy.
  5. When would you use a cloud security group versus host-level iptables/ufw, and why are security groups usually the preferred primary control?
Show answers
  1. A port identifies which process on the machine a connection is for. One machine runs many programs sharing one IP (web server, database, SSH daemon), so the port disambiguates them — the full coordinate is IP:port.
  2. Binding to 0.0.0.0 listens on all interfaces, making the service reachable from the whole network; binding to 127.0.0.1 listens on loopback only, reachable just from the host. Bind a database to 0.0.0.0 (instead of 127.0.0.1) and it can end up exposed on the public internet.
  3. NAT lets many private-addressed devices share one public IP: the router rewrites the source address on the way out and tracks the mapping so replies return to the right device. The internet can’t reach your laptop unbidden because there’s no inbound mapping until an outbound connection creates one.
  4. Default deny means block everything, then open only the specific ports you’ve chosen to expose. “Allow everything, then block bad traffic” is losing because you can’t enumerate every threat in advance — the attacker only needs one gap you didn’t predict.
  5. Use a cloud security group as the primary boundary: it’s stateful, declarative, version-controllable via IaC, auditable, and applied before traffic reaches the host. Use host-level iptables/ufw as defense-in-depth, since per-host rules are lower-level and easy to let drift.