# Ports and Services

A port is how one IP address hosts many conversations. It is also the first thing to check when something "cannot connect", and the thing people most often misread.

## The mechanics

A TCP or UDP header carries a 16-bit source port and a 16-bit destination port: 0–65535.

| Range | Name | Use |
|---|---|---|
| 0–1023 | Well-known | Assigned by IANA to core services; binding needs privilege (or `CAP_NET_BIND_SERVICE`) |
| 1024–49151 | Registered | Applications and vendor products |
| 49152–65535 | Dynamic / ephemeral | Client side of outbound connections |

The **destination port** is chosen by the client and names the service. The **source port** is chosen by the kernel's ephemeral allocator and identifies this conversation, so 500 browsers can talk to one server on 443 at once.

```bash
ss -lntp                  # what is listening, on which address
ss -tn state established  # conversations, with queue depths
cat /proc/sys/net/ipv4/ip_local_port_range   # Linux ephemeral range (32768 60999)
sysctl net.ipv4.ip_local_port_range          # same, as a setting
```

## Ports you will actually meet

| Port | Protocol | Service | Common mistake |
|---|---|---|---|
| 22 | TCP | SSH / SFTP | Exposed to the internet with passwords enabled |
| 25 | TCP | SMTP relay | Blocked by most providers; use 587 to submit mail |
| 53 | TCP+UDP | DNS | Firewalled "because UDP is enough" — large answers and zone transfers use TCP |
| 67/68 | UDP | DHCP server / client | DHCP relay missing on a routed VLAN |
| 80 | TCP | HTTP | Left open "for redirects" with no redirect configured |
| 123 | UDP | NTP | Blocked, so certificates and logs quietly drift |
| 161/162 | UDP | SNMP / traps | Community string left at `public` |
| 179 | TCP | BGP | Firewalled between peers; session never establishes |
| 389/636 | TCP | LDAP / LDAPS | Cleartext bind allowed |
| 443 | TCP+UDP | HTTPS / HTTP/3 | UDP 443 blocked, so QUIC silently falls back to TCP |
| 445 | TCP | SMB | Reachable from a user VLAN, which is how ransomware spreads |
| 514 | UDP | Syslog | Unencrypted, unauthenticated, usually unread |
| 3306/5432/1433 | TCP | MySQL / Postgres / SQL Server | Bound to `0.0.0.0` and reachable from the internet |
| 3389 | TCP | RDP | Internet-exposed RDP is a standing invitation |
| 6379/27017/9200 | TCP | Redis / MongoDB / Elasticsearch | Default "no auth, no TLS" deployments found by scanners within hours |

Full lists exist, but these are the ones that cause incidents.

## Listening address matters as much as port

```text
127.0.0.1:8080   only this host can connect
0.0.0.0:8080     every interface, including the public one
[::]:8080        every IPv6 interface (often dual-stack with 0.0.0.0)
192.168.1.5:8080 only that interface
```

A database "not exposed to the internet" that binds `0.0.0.0` is exposed to the internet the moment a firewall rule or a security group is wrong. Bind to loopback or the private interface and the mistake becomes impossible.

## Reading a connection refusal correctly

| Observation | Meaning |
|---|---|
| `Connection refused` (RST) | Something answered: no listener on that port, or a firewall rejected with RST |
| Timeout, no reply | Packet dropped: firewall DROP rule, route missing, host down, or asymmetric path |
| `Connection reset by peer` mid-session | Listener died, or a middlebox decided to kill the flow |
| Works from one host, not another | Source-based firewall rule, NAT, or routing difference — not the service |
| `address already in use` | Another process holds the port; `ss -lntp` names it |

Refused vs timeout is the single most useful distinction in troubleshooting, because it tells you whether your packets are being answered at all.

## Ephemeral port exhaustion

Every outbound connection consumes a source port for the duration, plus TIME_WAIT afterwards. With ~28,000 ports per destination IP and a workload that opens and abandons connections rapidly, you can run out:

```bash
ss -s | head -3                     # total sockets, TIME_WAIT count
netstat -an | grep -c TIME_WAIT
```

Symptoms: intermittent `cannot assign requested address` under load, rising latency, retries. Fixes are connection pooling, HTTP keep-alive, more destination IPs, or widening the port range — in that order, because pooling is the real fix.

## Where it goes wrong

- **Assuming one port per service.** HTTP/3 is UDP 443, DNS is both UDP and TCP 53, and many products listen on dozens of ports plus a dynamic range.
- **Fixing the application firewall and forgetting the network one.** Two layers, two sets of rules; the one that blocks is often the one nobody owns.
- **Opening a port "to test" and never closing it.** The test that took thirty seconds becomes a permanent ingress rule.
- **Confusing "listening" with "reachable".** `ss` proves the process; it says nothing about routing or policy.
- **Copying a docker-compose from the internet with `- "5432:5432"`.** That publishes the database on every interface by default.

## Teaching hook

**The apartment-block analogy.** The IP address is the building; the port is the flat number. The postman only needs the building to deliver to the lobby, and the concierge needs the flat number to deliver the letter. A thousand letters a day can arrive at the same building for different flats.

Demo: `python3 -m http.server 8000` on localhost, then connect from another host and fail, then rebind to `0.0.0.0` and succeed. It makes the listening-address distinction physical.

## Related Notes

- [[Core-Protocols/IP-TCP-UDP.md]] — the headers that carry ports
- [[Core-Protocols/TLS-And-HTTPS.md]] — what happens after the port opens
- [[Network-Services/Firewalls-And-ACLs.md]] — which layer drops the packet
- [[Automation-Observability/Packet-Analysis-Wireshark.md]] — seeing RST vs silence
