Networking Ports Reference

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.

Port Binding and Listening

Bind to a specific port on a socket

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.

Listen on a bound port

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.

Use SO_REUSEADDR and SO_REUSEPORT

Enable 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.

Configuration and Service Files

Systemd service file: bind to port 3000

[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.

NGINX: listen on multiple ports

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.

Firewalls and Port Filtering

Configure 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

Use 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.

Use nftables for advanced port filtering

table 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.

Diagnosing Port Issues

List all listening ports

# 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

Use 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.

Check if a port is open

# 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

Trace incoming connections to a port

# 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.

Common Errors and Their Fixes

EADDRINUSE: Cannot bind to port

# 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.

Port not reachable: "Connection refused"

# 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 Number Conventions

Standard well-known ports

PortServiceProtocolNotes
21FTPTCPControl port
22SSHTCPDefault port
23TelnetTCP
25SMTPTCP
53DNSUDP/TCP
80HTTPTCP
110POP3TCP
143IMAPTCP
443HTTPSTCP
3306MySQLTCPDefault
5432PostgreSQLTCP
6379RedisTCP
9000PHP-FPMTCP
9090PrometheusTCP

> 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.

Dynamic and ephemeral ports

# 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.

Summary of Key Flags and Options

ToolFlag/OptionPurpose
bind()SO_REUSEADDRReuse port even in TIME_WAIT
bind()SO_REUSEPORTAllow multiple processes to bind same port
listen()backlogMax pending connections
ss-tulnList all TCP/UDP listening ports
ss-oShow socket options and memory usage
tcpdumptcp[tcpflags] & tcp-synFilter for SYN packets
nftablesipv6only=onBind IPv6-only to port
nginxlisten [::]:443 ssl ipv6only=onIPv6-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.