Port Numbers
Beginner to Intermediate
3 of 5
The Transport Layer’s Job
The transport layer provides end-to-end communication between applications on different hosts. The IP layer gets a packet to the right machine, but how does the operating system know which application on that machine should receive the packet? That is solved by port numbers.
A port number is a 16-bit unsigned integer (range: 0 to 65535). Combined with an IP address, it forms a socket address that uniquely identifies one endpoint of a network connection. Both TCP and UDP use port numbers for this purpose.
Key Terms:
Port numbers are divided into three ranges, each managed differently:
| Category | Range | Assigned By | Description |
|---|---|---|---|
| Well-Known Ports | 0 – 1023 | IANA (strict) | Permanently assigned to standard services. Require an approved RFC. Privileged on Linux (root needed). |
| Registered Ports | 1024 – 41951 | IANA (less strict) | Allocated to developers on a less stringent basis. Availability is not guaranteed for registered purpose. |
| Dynamic / Private | 49152 – 65535 | Local use | Intended for ephemeral (short-lived) ports assigned by OS to client sockets automatically. |
Well-known ports are permanently reserved for specific standard services. Here are some important examples:
| Service | Protocol | Port | Purpose |
|---|---|---|---|
| FTP (data) | TCP | 20 | File Transfer Protocol — data transfer |
| FTP (control) | TCP | 21 | File Transfer Protocol — commands |
| SSH | TCP | 22 | Secure Shell — encrypted remote login |
| Telnet | TCP | 23 | Unencrypted remote terminal |
| DNS | UDP/TCP | 53 | Domain Name System — name resolution |
| HTTP | TCP | 80 | HyperText Transfer Protocol — web |
| HTTPS | TCP | 443 | HTTP over TLS — secure web |
Note that the same well-known port number is usually registered under both TCP and UDP even if a service uses only one protocol. This avoids confusion across protocols.
On Linux, ports in the range 0 to 1023 are privileged. Only a process with the CAP_NET_BIND_SERVICE capability (traditionally root) can bind to these ports. This is a security measure:
| Normal User Process
Tries to bind to port 22 (SSH) ❌ bind() fails with EACCES |
vs | Root / CAP_NET_BIND_SERVICE
Tries to bind to port 22 (SSH) ✅ bind() succeeds |
On modern Linux, you can grant just this specific capability to a binary without making it fully root: setcap cap_net_bind_service=+ep /path/to/program
When a client creates a socket and does not call bind(), the OS automatically assigns a temporary (ephemeral) port number. The client does not care which port it uses — it just needs a port for the server to send replies back.
| Client IP: 192.168.1.100 Port: 54321 (auto-assigned ephemeral) |
SYN ──────────────→
← SYN-ACK
ACK ──────────────→
|
Server IP: 10.0.0.5 Port: 80 (well-known, manually bound) |
The server knows where to reply because the client’s ephemeral port is included in every packet as the source port. On Linux, the range of ephemeral ports is controlled by:
# View current ephemeral port range on Linux
cat /proc/sys/net/ipv4/ip_local_port_range
# Typical output: 32768 60999
# Change the range (temporary, resets on reboot)
echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range
Binding to port 0 explicitly also triggers ephemeral port assignment: addr.sin_port = htons(0); — the OS picks a free port automatically.
#include <stdio.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
struct servent *svc;
/*
* getservbyname() looks up a service by name in /etc/services
* Returns struct servent with port in network byte order
*/
svc = getservbyname("ssh", "tcp");
if (svc != NULL) {
printf("Service: %-10s Port: %d Protocol: %s\n",
svc->s_name,
ntohs(svc->s_port), /* convert to host byte order */
svc->s_proto);
}
svc = getservbyname("http", "tcp");
if (svc != NULL) {
printf("Service: %-10s Port: %d Protocol: %s\n",
svc->s_name,
ntohs(svc->s_port),
svc->s_proto);
}
/*
* getservbyport() looks up a service by port number
* Port must be in NETWORK byte order
*/
svc = getservbyport(htons(443), "tcp");
if (svc != NULL) {
printf("Port 443 = Service: %s\n", svc->s_name);
}
return 0;
}
/* Output:
Service: ssh Port: 22 Protocol: tcp
Service: http Port: 80 Protocol: tcp
Port 443 = Service: https
*/
Finding Your Ephemeral Port After bind(port=0)
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main() {
int fd;
struct sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
fd = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(0); /* 0 = let OS pick */
addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(fd, (struct sockaddr *)&addr, sizeof(addr));
/*
* Use getsockname() after bind() to discover which
* ephemeral port the OS assigned.
*/
getsockname(fd, (struct sockaddr *)&addr, &addrlen);
printf("OS assigned ephemeral port: %d\n", ntohs(addr.sin_port));
close(fd);
return 0;
}
Q1. What is a port number and why is it needed?A port number is a 16-bit number (0–65535) used by the transport layer (TCP/UDP) to identify which application on a host should receive an incoming packet. The IP address gets the packet to the right machine; the port number gets it to the right application (process). Without port numbers, the OS would not know whether an incoming packet is for a web browser, an SSH session, or a database.
Q2. What are the three categories of port numbers and their ranges?Well-known ports (0–1023): Permanently assigned by IANA to standard services like SSH (22), HTTP (80), HTTPS (443). Require an approved RFC for assignment and are privileged on Linux. Registered ports (1024–41951): Allocated by IANA to developers on a less strict basis — not guaranteed to be available. Dynamic/Private ports (49152–65535): Intended for ephemeral (temporary client-side) use, assigned automatically by the OS.
Q3. Why are ports 0–1023 called privileged ports on Linux?On Linux, only processes with the CAP_NET_BIND_SERVICE capability (traditionally superuser/root) can bind to ports 0–1023. This prevents a malicious normal-user program from impersonating a trusted service — for example, a fake SSH server on port 22 that steals passwords when users try to log in. Legitimate system daemons run as root or with specific capabilities.
Q4. What is an ephemeral port and who assigns it?An ephemeral port is a short-lived port number automatically assigned by the OS to a client socket that has not called bind(). The client needs a port so the server knows where to send its replies. The OS picks an unused port from the dynamic range. On Linux, this range is defined in /proc/sys/net/ipv4/ip_local_port_range (typically 32768–60999). You can also trigger this by binding to port 0 explicitly.
Q5. If TCP port 80 and UDP port 80 are different entities, why does IANA assign the same number to a service on both?Although TCP and UDP port 80 are technically distinct (a TCP connection to port 80 and a UDP datagram to port 80 are handled separately), IANA assigns the same number to a service (HTTP) on both protocols to avoid confusion. Even if HTTP only uses TCP, reserving the same UDP port number prevents another service from using UDP 80 and causing ambiguity when people see port 80 in use.
Q6. How can you find the port assigned to a socket after binding to port 0?Use the getsockname() system call after bind(). It fills in the sockaddr_in (or sockaddr_in6) structure with the actual address and port that the OS assigned. Read sin_port and convert with ntohs() to get the port in host byte order.
Q7. What Linux file controls the ephemeral port range and how do you change it?The file /proc/sys/net/ipv4/ip_local_port_range contains two numbers: the low and high end of the ephemeral port range. You can read it with cat and change it with echo "low high" > /proc/sys/net/ipv4/ip_local_port_range. This change is temporary (lost on reboot). For a permanent change, add the setting to /etc/sysctl.conf: net.ipv4.ip_local_port_range = 1024 65535.
Continue Learning
Next: UDP — connectionless, unreliable, and when to prefer it over TCP.
