A single IP address can host hundreds of services simultaneously: web server on port 80, mail server on port 25, SSH on port 22, your own application on port 8080. The mechanism that makes this work is the port number — a 16-bit identifier that distinguishes one connection or service from another on the same IP.
This post is a thorough but practical reference: what ports are, the three IANA-defined ranges, the well-known ports you should recognize, ephemeral ports and why they matter for NAT, and how to think about port choices for your own applications.
The Basic Idea
An IP address identifies a host. A port number identifies a service on that host. Together they form a socket address like 203.0.113.5:443 — “host 203.0.113.5, service on port 443.”
Ports are part of the transport layer (TCP and UDP). The IP layer cares about routing packets to the right host; the transport layer cares about delivering them to the right process within that host.
A connection is defined by a 5-tuple:
- Protocol (TCP or UDP)
- Source IP
- Source port
- Destination IP
- Destination port
Two different connections from the same host to the same server (e.g., two tabs both fetching the same site) are distinguished by their source ports.
The Three Port Ranges
Port numbers are 16 bits — values 0 to 65535. IANA divides them into three ranges:
Well-known ports: 0-1023
Reserved for system services and core protocols. On Unix, binding to these ports requires root privileges by default.
Examples:
- 22 — SSH
- 25 — SMTP (email)
- 53 — DNS
- 80 — HTTP
- 110 — POP3
- 143 — IMAP
- 443 — HTTPS
- 853 — DNS over TLS
- 993 — IMAPS
- 995 — POP3S
These ports are universally recognized. When you type https://example.com without a port, your browser knows to use port 443 because HTTPS = 443 is well-known.
Registered ports: 1024-49151
Assigned by IANA on request but not reserved for system services. Many application protocols have registered ports here.
Examples:
- 1433 — SQL Server
- 1521 — Oracle
- 3306 — MySQL
- 3389 — RDP (Windows Remote Desktop)
- 5432 — PostgreSQL
- 5900 — VNC
- 6379 — Redis
- 8080 — HTTP alternate (common for dev servers and proxies)
- 8443 — HTTPS alternate
- 9092 — Kafka
- 27017 — MongoDB
- 11211 — Memcached
These aren’t reserved like well-known ports. Anyone can bind to 8080. But by convention, 3306 means MySQL — if you run something else on that port, you’re going to confuse tools.
Dynamic / ephemeral ports: 49152-65535
Assigned automatically by the OS for outgoing connections. When your browser opens a connection to a server, the OS picks an unused ephemeral port for the source side.
Linux’s actual ephemeral range is /proc/sys/net/ipv4/ip_local_port_range — usually 32768-60999 by default in 2026, slightly different from IANA’s 49152+. Windows uses something close to the IANA range. Either way, ephemeral ports are managed by the OS, not by the application.
How Ephemeral Ports Work
When your browser fetches https://example.com:
- The browser asks the OS to open a connection to
example.com:443. - The OS picks an unused port from the ephemeral range — say, 51234.
- The connection is
<your_ip>:51234 → example.com:443. - The browser opens a second tab to the same site. The OS picks another ephemeral port — say, 51235.
- The two connections coexist because their source ports differ.
This is fundamental to how TCP allows many parallel connections from one host. Without ephemeral ports, you could only have one connection to a given destination port at a time.
Ports and NAT
For NAT and CGNAT, ports get more interesting. Inside a home network, dozens of devices share one public IP. NAT keeps them straight by remapping source ports:
Phone (192.168.1.10:51234) → NAT → (203.0.113.5:62001) → Internet
Laptop (192.168.1.11:51234) → NAT → (203.0.113.5:62002) → Internet
Both devices used source port 51234 internally. The NAT remapped them to different external ports (62001 and 62002) so responses come back correctly.
Each NAT mapping requires a port. A home NAT has 65,535 ports to work with, so it supports tens of thousands of concurrent connections. A CGNAT trying to share one public IP across thousands of users is much tighter — and that’s why CGNAT sometimes runs out of ports under load.
Privileged Ports
On Unix systems, binding to ports below 1024 requires root. The historical reason: ports 0-1023 were considered “trusted” services, and you didn’t want unprivileged users impersonating them.
In practice this matters for:
- Web servers —
nginxandapacheneed root to bind 80/443. They typically start as root, bind, then drop to an unprivileged user. - Containers — Modern Docker, Kubernetes deployments often bind to a high port internally (like 8080) and use a load balancer or
setcapto route external 80/443 to the container. setcap— Lets a specific binary bind to low ports without full root.setcap 'cap_net_bind_service=+ep' /path/to/my-server.
For most modern deployments, the rule is “don’t run apps as root; have your load balancer or platform handle port 80/443.”
Choosing a Port for Your Application
If you’re writing a new service:
Pick a port not commonly conflicted
Don’t use 3306 unless you’re MySQL. Don’t use 6379 unless you’re Redis. Tools and people will be confused.
Use the registered range (1024-49151) for stable services
If your service has a “default port” that users will configure, pick something in this range that isn’t already in widespread use. Common conventions: 8080 for HTTP alternates, 5xxx-9xxx for “application servers.”
Don’t use the ephemeral range
Ports above 49152 are for OS-assigned outgoing connections. Don’t bind your server to 60000 — it might collide with an ephemeral port the OS wants to assign.
Make it configurable
Whatever default you pick, let users override it. Hardcoded ports break deployments.
Common Pitfalls
Port conflicts in development
Two services both wanting port 8080. Or you killed a process but the kernel hasn’t released the port (TIME_WAIT). Use lsof -i :8080 or ss -tnlp | grep 8080 to find what’s using a port.
Firewall blocks
A service is running on port 12345 but external clients can’t connect. Almost always a firewall (host iptables/nftables, cloud security group, corporate firewall). Verify with nc -zv host 12345 from outside.
IPv6 and ports
Ports are the same in IPv6 as in IPv4. The address space is bigger; the port space isn’t. [2001:db8::1]:443 is a valid IPv6 socket address.
NAT and inbound ports
If you want external clients to connect to a service behind NAT, you need port forwarding on the NAT (router config). Or use a reverse-tunnel service (ngrok, Cloudflare Tunnel) that handles this.
Source-port exhaustion
A client making thousands of connections per second to the same destination can exhaust its ephemeral ports. Set SO_REUSEADDR and SO_REUSEPORT appropriately, or pool connections.
Reading a netstat Output
Quick reference for the output you see when running netstat -an or ss -tan:
LISTEN 0.0.0.0:80 0.0.0.0:* # nginx, listening on port 80 from any IP
ESTAB 192.168.1.5:51234 203.0.113.5:443 # outgoing connection from my port 51234 to their port 443
TIME-WAIT 192.168.1.5:51200 203.0.113.5:443 # recently-closed connection still tracked
- Local Address:Port — the side of your machine
- Foreign Address:Port — the side of the remote
- State — TCP state machine state (LISTEN, ESTABLISHED, TIME-WAIT, CLOSE-WAIT, etc.)
Port Scanning and Security
Tools like nmap enumerate which ports are open on a remote host:
nmap -p- example.com # scan all 65535 ports
nmap -sT example.com # full TCP connect scan
nmap -sS example.com # SYN-scan (faster, more stealthy)
For security: don’t expose ports you don’t need. Every open port is potential attack surface. Modern firewalls (cloud security groups, host iptables) default to “deny inbound” and require explicit allow rules.
Ports and Application Layer
Most application protocols ride on top of TCP or UDP and assume certain port conventions. HTTP/HTTPS, SSH, SMTP, DNS — each has a default port that the protocol assumes unless you specify otherwise.
This is why a URL like https://example.com:8443 works: you’re saying “speak HTTPS but use port 8443 instead of the default 443.” The protocol is HTTPS; the port is overridden.
TL;DR
- Ports are 16-bit numbers (0-65535) that multiplex services on a single IP.
- Well-known ports (0-1023) are reserved for system services. Need root on Unix to bind.
- Registered ports (1024-49151) are for application protocols by convention.
- Ephemeral ports (49152-65535, or 32768-60999 on Linux) are auto-assigned by the OS for outgoing connections.
- Each connection is a 5-tuple: protocol + source IP + source port + dest IP + dest port.
- NAT remaps source ports to keep many internal hosts disambiguated behind one public IP.
- Port scanning is a security concern; don’t expose ports you don’t need.
- Pick a sensible default port for your services; make it configurable; don’t squat on well-known ports.
The port layer is the glue between IP addressing and application services. Combined with public/private IP addressing and NAT/CGNAT, it’s most of what you need to reason about how packets actually flow between hosts. For the IP intelligence on top of all this — country, ASN, network type — the Ip2Geo API is the layer above.