# Port

## What it is

A port is a 16-bit number in the header of every TCP and UDP segment that
tells the receiving machine which of its programs the data belongs to. The
address gets a packet to the right host; the port gets it to the right process
once it is there. Engineers say port number when they want to be precise, and
ports in the plural usually means the set a server listens on. The word is
unrelated to a physical port on a switch, or to porting software to another
platform.

Because the field is 16 bits wide there are 65,536 values, 0 to 65535, and TCP
and UDP each have their own set: TCP port 53 and UDP port 53 are two separate
things that DNS happens to use both of. The TCP specification, RFC 9293,
defines a socket as an internet address joined to a port, and a connection as
a pair of sockets. That makes four values: client address, client port, server
address, server port. It also explains why one server port can carry
thousands of conversations at once. Each conversation differs in at least one
of the other three values, usually the client port.

RFC 6335 divides the range in three. Ports 0–1023 are System Ports, 1024–49151
are [User Ports](https://www.rfc-editor.org/rfc/rfc6335.html#section-6), and
49152–65535 are Dynamic Ports, which IANA never assigns. IANA keeps the registry
that maps a service name to a number: 22 for SSH, 53 for DNS, 443 for HTTPS,
5432 for PostgreSQL. A registration is a convention that clients rely on, and
any program can listen on any free number, which is how SSH ends up on a port
nobody would guess.

The client side of a connection usually gets a port it never chose. When a
program connects out, the operating system picks one from its ephemeral range,
and Linux uses 32768–60999 by default rather than the IANA dynamic range.
Asking to bind port 0 means the same thing: give me any free one.

## Why it matters

The decision behind every port is exposure: which programs answer from
outside, and on which address. A port with no listener earns a refusal. A port
with a listener is a piece of software that anyone who can reach the address
can talk to, bugs included. The cheapest service to secure is one listening on
127.0.0.1, because packets from other machines cannot reach the loopback
address, whatever the firewall says.

So the first question for each service is whether it needs to be reachable
from outside at all. A database that only the application on the same machine
talks to belongs on loopback. A tool only administrators use can stay on
loopback and be reached through a jump host or a VPN. What remains, typically
80 and 443 for a website and one port for SSH, is the public surface. Keeping
it short is a cost decision as much as a security one, because each public
listener needs patching, logging and a firewall rule that matches it.

The second question is the number. Standard ports cost nothing in
configuration: browsers, clients and restrictive networks expect 443 and pass
it. Moving SSH off 22 cuts the noise from scanners that try only the default,
which makes logs easier to read, but a scan of the full range finds it anyway,
and every tool that refers to the port has to learn the new number. That last
part is the one teams forget.

The third is privilege and sharing. On Linux a process needs root or the
CAP_NET_BIND_SERVICE capability to bind a port below 1024, unless
ip_unprivileged_port_start has been lowered from its default of 1024. And
normally one program owns a given address and port, so two services that both
want 443 need something in front to split the traffic, usually a reverse
proxy that reads the host name the client asked for.

## What it looks like in practice

When the company set up its own git forge in July 2026,
[the forge's SSH listener was bound to loopback at port 23231](/portfolio/deployed-the-companys-own-git-forge-121/),
and anyone outside reaches it by way of a jump host, so the count of public
ports stayed where it was. A multiplexer that would serve HTTPS and git over SSH
from one public port, by looking at the first bytes of each connection, is
written and switched off. A listener with no users would still have to be
defended.

In August 2026 a hardening review found that SSH had moved to port 1986
during the host's first hardening, while the intrusion-banning jail went on
watching 22.
[Each of the jail's bans blocked a port that had no listener](/portfolio/found-three-ssh-brute-force-protections-that-never-worked-110/),
so the protection had never once applied. The fix was small. What stayed was
a rule: every security control is paired with an assertion that exercises
it.

The host has 464 MB of memory and no dashboard, so its questions are answered
by
[seven read-only reporting roles that render the live machine to Markdown](/portfolio/built-seven-read-only-host-reporting-roles-120/).
The facts report lists the listening sockets, which is the port inventory the
kernel actually holds rather than the one the configuration describes. Among
the real things the reports turned up was a listening port that no
declaration mentioned.

## Where it goes wrong

**A service meant for the machine itself answers the whole internet.** Many
programs listen on every address by default, shown as `0.0.0.0`, `[::]` or
`*` in the socket list, and a database or admin panel meant to be local is
then one firewall mistake away from exposure. Set the bind address explicitly
to `127.0.0.1` or `::1` and confirm it in the local address column:

```sh
ss -tlnp
ss -ulnp
```

**A protection keeps watching a port the service has left.** The service moved
to a new number, and the firewall rule, the banning jail, the monitoring probe
or a client configuration still names the old one. Every one of those tools
keeps working correctly against a port where no daemon listens. Keep the
number in one variable that every consumer reads, and add a check that
compares the listening socket with what each control is watching.

**Outgoing connections start to fail under load with "Cannot assign requested
address".** Every outgoing connection takes a local port from the ephemeral
range, and a closed connection keeps its four values reserved in TIME_WAIT for
a while afterwards. With Linux's default range of 32768–60999, one client
address can hold 28,232 connections to the same server address and port,
open or recently closed. A proxy or worker that opens a fresh connection per
request to one backend runs through them. Reuse connections through keep-alive
or a pool first; widening the range or adding source addresses only buys
headroom.

**A service started as an ordinary user stops with "Permission denied" on
port 80.** Binding below 1024 needs privilege. Give the service the
CAP_NET_BIND_SERVICE capability through its unit file, put it on a high port
behind a reverse proxy, or, on a host where every local user is trusted, lower
ip_unprivileged_port_start. Running the whole service as root to get one port
is the worst of the trades.

**Startup stops with "Address already in use".** Another process holds the
port, often an old copy of the same service that is still shutting down, or a
second service with the same default (8080 is a popular one). `ss -tlnp`
names the owner. Decide which service owns the number and give each one an
explicit port in its configuration instead of relying on defaults.

## Further reading

- [RFC 9293, section 4: Glossary](https://www.rfc-editor.org/rfc/rfc9293#section-4)
  — the TCP specification's own definitions of port, socket and connection.
- [RFC 6335: IANA Procedures for the Management of the Service Name and Transport Protocol Port Number Registry](https://www.rfc-editor.org/rfc/rfc6335)
  — the three port ranges and how numbers in them are assigned.
- [Service Name and Transport Protocol Port Number Registry](https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml)
  — IANA's list of which service is registered on which number.
- [RFC 768: User Datagram Protocol](https://www.rfc-editor.org/rfc/rfc768)
  — the three-page UDP specification, source and destination ports included.
- [RFC 6056: Recommendations for Transport-Protocol Port Randomization](https://www.rfc-editor.org/rfc/rfc6056)
  — how a client should pick its ephemeral ports so they are hard to guess.
- [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html)
  — Linux kernel settings, among them ip_local_port_range and
  ip_unprivileged_port_start.

<https://engineer.company/glossary/port/>
