Comprehensive reference for networking ports reference covering essential concepts, practical examples, and production best practices.
Networking ports are the gateways through which networked services communicate. This reference page details the exact mechanics of port configuration, binding, and troubleshooting across common systems and tools. Use this when a service fails to accept connections, when firewalls block traffic, or when you need to diagnose a socket-level issue—especially in production environments where a single missed port number breaks a service.
To bind a socket to a specific port, use the bind() system call with an sockaddr_in structure:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080); // Port 8080, big-endian
addr.sin_addr.s_addr = inet_addr("192.168.1.100");
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind failed");
// errno = 98 (EADDRINUSE) if port is already in use
}
The htons() function converts host byte order to network byte order. Use htonl() for 32-bit values.
After binding, call listen() to start accepting incoming connections:
if (listen(sock, 128) < 0) {
perror("listen failed");
// errno = 11 (EAGAIN) if the socket is non-blocking
}
The second argument, backlog, defines the maximum number of pending connections in the listen queue. A backlog of 128 means up to 128 unaccepted connections can be queued.
SO_REUSEADDR and SO_REUSEPORTEnable port reuse to avoid EADDRINUSE errors when restarting services:
int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// For multi-process servers, use SO_REUSEPORT
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
SO_REUSEADDR allows bind() to reuse a port even if the socket is in TIME_WAIT state. SO_REUSEPORT enables multiple processes to bind to the same port, distributing incoming connections across them.
> Critical failure mode: Without SO_REUSEADDR, a server restart fails to bind to port 8080 if a previous instance left the socket in TIME_WAIT. The error bind: Address already in use appears, but the service is silent.
[Unit]
Description=My Web Server
After=network.target
[Service]
ExecStart=/usr/local/bin/myserver --port 3000
User=app
Group=app
WorkingDirectory=/opt/myapp
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
# Enable port binding
Socket=web.socket
[Socket]
ListenStream=3000
BindIPv6Only=yes
Accept=yes
MaxConnections=1000
QueueLength=1024
[Install]
WantedBy=multi-user.target
The ListenStream=3000 line tells systemd to create a socket that listens on port 3000. Accept=yes enables socket activation: the service starts only when the first connection arrives.
server {
listen 80;
listen 443 ssl;
listen [::]:80 ipv6only=on;
listen [::]:443 ssl ipv6only=on;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
Use listen [::]:443 ssl ipv6only=on; to bind to IPv6 only. The ipv6only=on directive avoids dual-stack behavior.
> Silent failure: If ipv6only=on is omitted, NGINX binds to both IPv4 and IPv6 on port 443, but connections over IPv6 may not get the SSL configuration correctly due to mismatched socket options.
iptables to allow port 22 and 80# Allow incoming SSH (port 22)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Allow incoming HTTP (port 80)
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
# Allow HTTPS (port 443)
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Save rules
iptables-save > /etc/iptables/rules.v4
ufw to manage port rules# Enable ufw
sudo ufw enable
# Allow specific ports
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Allow port range
sudo ufw allow 1024:65535/tcp
# Allow from specific IP
sudo ufw allow from 192.168.1.0/24 to any port 80
> Common oversight: ufw only applies to INPUT chain by default. To allow OUTPUT traffic, use ufw default output allow.
nftables for advanced port filteringtable ip filter {
chain input {
type filter hook input priority 0; policy accept;
# Accept established connections
ct state established accept
# Allow SSH
tcp dport 22 accept
# Allow HTTP and HTTPS
tcp dport { 80, 443 } accept
# Drop all others
reject with icmp type admin-prohibited
}
chain forward {
type filter hook forward priority 0; policy accept;
# Forward packets between interfaces
}
}
Save with nft -f /etc/nftables.conf and enable via systemctl enable nftables.
> Production pitfall: nftables does not automatically reload rules on systemctl restart nftables. Use nft flush ruleset before applying new rules.
# Netstat: list all TCP and UDP ports
netstat -tuln
# Output example:
# Proto Recv-Q Send-Q Local Address Foreign Address State
# tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
# tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
# udp 0 0 0.0.0.0:53 0.0.0.0:* LISTEN
# Use -p to show PID and program
netstat -tulnp
ss for real-time port diagnostics# Show all listening TCP ports
ss -tuln
# Show only connections to port 80
ss -tuln 'sport = :80'
# Show all connections in SYN-RECEIVED state (half-open)
ss -t state syn-recv
# Show socket details including memory usage
ss -tuln -o
# Watch port 5000 in real time
ss -tuln -i -o -c 'sport = :5000'
> Key insight: ss is faster than netstat and supports filtering with where clauses. Use ss -tuln 'dst 192.168.1.100' to find all services on a specific host.
# Using `nc` (netcat)
nc -zv 192.168.1.100 8080
# Output:
# Connection to 192.168.1.100 8080 port [tcp/http] succeeded!
# (192.168.1.100:8080) 0.002s
# Use -w to set timeout
nc -zvw 5 192.168.1.100 3000
# Use `telnet` as a lightweight alternative
telnet 192.168.1.100 8080
> Failure mode: nc -zv returns success even if the server accepts the connection but immediately closes it. Add -c to keep the connection open:
nc -zvc 192.168.1.100 8080
# Use `tcpdump` to capture packets on port 80
tcpdump -i any port 80 -n -v
# Capture only SYN packets (first step of TCP handshake)
tcpdump -i any 'tcp and port 80 and (tcp[tcpflags] & tcp-syn != 0)'
# Capture incoming connections to port 3000 and write to file
tcpdump -i eth0 -w /tmp/connections.pcap 'dst port 3000'
> Critical detail: tcpdump uses BPF (Berkeley Packet Filter) syntax. The tcp[tcpflags] & tcp-syn != 0 expression extracts the TCP flags byte and checks if the SYN flag is set.
# Check which process is using the port
lsof -i :8080
# Output:
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# myserver 1234 app 6u IPv4 12345 0t0 TCP *:8080 (LISTEN)
# Kill the process
kill 1234
# Or force bind with SO_REUSEADDR
# In code:
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
> Confusion: SO_REUSEADDR does not eliminate TIME_WAIT issues. To fully eliminate EADDRINUSE, combine SO_REUSEADDR with SO_REUSEPORT and set TCP_USER_TIMEOUT on the socket.
# Check if the service is listening
ss -tuln | grep 8080
# Output:
# LISTEN 0 128 *:8080 *:*
# If no output, the service isn't bound.
# Check process status
systemctl status myserver.service
# Verify the service is running and listening on the correct port
journalctl -u myserver.service -f
> Production failure: A service logs "Listening on port 8080" but clients get "Connection refused". The root cause is often a misconfigured ExecStart command that starts the service on localhost (127.0.0.1) only, not on 0.0.0.0.
# Fix: bind to 0.0.0.0 instead of 127.0.0.1
# In code:
addr.sin_addr.s_addr = htonl(INADDR_ANY); // or inet_addr("0.0.0.0")
# Or in config:
{
"host": "0.0.0.0",
"port": 8080
}
| Port | Service | Protocol | Notes |
|---|---|---|---|
| 21 | FTP | TCP | Control port |
| 22 | SSH | TCP | Default port |
| 23 | Telnet | TCP | |
| 25 | SMTP | TCP | |
| 53 | DNS | UDP/TCP | |
| 80 | HTTP | TCP | |
| 110 | POP3 | TCP | |
| 143 | IMAP | TCP | |
| 443 | HTTPS | TCP | |
| 3306 | MySQL | TCP | Default |
| 5432 | PostgreSQL | TCP | |
| 6379 | Redis | TCP | |
| 9000 | PHP-FPM | TCP | |
| 9090 | Prometheus | TCP |
> Edge case: While port 80 is standard for HTTP, a service may accept connections on port 80 but fail to serve HTTP content if it doesn't set Content-Type and Server headers in responses. Use curl -v http://example.com:80 to verify.
net.ipv4.ip_local_port_range to adjust# View current range
cat /proc/sys/net/ipv4/ip_local_port_range
# Output: 32768 60999
# Change to 1024–32767
sysctl -w net.ipv4.ip_local_port_range="1024 32767"
> Failure mode: A high-traffic service opens thousands of connections to a backend. If the ephemeral port range is too small, EADDRNOTAVAIL or EAGAIN appear during connect() calls.
| Tool | Flag/Option | Purpose |
|---|---|---|
bind() | SO_REUSEADDR | Reuse port even in TIME_WAIT |
bind() | SO_REUSEPORT | Allow multiple processes to bind same port |
listen() | backlog | Max pending connections |
ss | -tuln | List all TCP/UDP listening ports |
ss | -o | Show socket options and memory usage |
tcpdump | tcp[tcpflags] & tcp-syn | Filter for SYN packets |
nftables | ipv6only=on | Bind IPv6-only to port |
nginx | listen [::]:443 ssl ipv6only=on | IPv6-only HTTPS |
This page is the definitive guide for any engineer working with network ports in production systems—no guesswork, no fluff.
This page was rewritten on 10 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy