Overview
Section titled “Overview”When investigating Linux issues such as “the service is running, but I cannot connect” or “I do not know which process is using the port,” you can use ss to inspect the state of sockets managed by the kernel.
In this article, we use an HTTP service running on TCP/80 as an example and check the following points.
- Whether TCP/80 is in a listening state
- Which process owns the listening port
- Whether an HTTP connection can be made from a client
- Whether an active TCP connection can be observed in the
ESTABstate - Whether the displayed results can be narrowed by port number and TCP state
- Whether a conflict can be identified when another process attempts to use the same port
- Whether the listening socket disappears and external connections fail when the service is stopped
We use Apache HTTP Server as the HTTP service for verification. If you are investigating an existing web server, do not stop the service or perform port-conflict tests in a production environment. Adapt the steps to the target port instead.
Variable notation
Section titled “Variable notation”| Variable name | Example setting | Description |
|---|---|---|
<<SERVER_IP>> | 192.0.2.10 | IP address of the destination web server |
Commonly used ss options
Section titled “Commonly used ss options”This article primarily uses the following options.
| Option | Meaning |
|---|---|
-t | Display TCP sockets |
-l | Display only listening sockets |
-n | Display port numbers and addresses numerically without name resolution |
-p | Display information about the process using the socket |
-H | Hide the header line |
state established | Select only TCP connections whose state is established |
sport = :80 | Select only sockets whose local port is 80 |
To inspect process information belonging to other users with -p, this article runs ss with sudo.
Step 1: Prepare an HTTP service for verification
Section titled “Step 1: Prepare an HTTP service for verification”On an RHEL-compatible Linux system, prepare ss, Apache HTTP Server, and Python for the port-conflict test. To avoid depending on executable path resolution, specify dnf using its absolute path.
sudo /usr/bin/dnf install -y iproute httpd python3Place sample content that is easy to verify over HTTP, then start httpd and firewalld. Allow the HTTP service in the current firewalld runtime configuration.
printf '%s\n' 'ss connection test' | sudo tee /var/www/html/index.html >/dev/nullsudo systemctl enable --now httpdsudo systemctl enable --now firewalldsudo firewall-cmd --add-service=httpCheck the service status.
sudo systemctl status httpd --no-pagerIf active (running) is displayed, the HTTP service itself is running. However, whether the service is running and whether TCP/80 is actually listening should be checked separately.
Step 2: Check the TCP/80 listening port and process
Section titled “Step 2: Check the TCP/80 listening port and process”Limit the output to listening TCP sockets, then narrow it further to local port 80.
sudo ss -ltnp '( sport = :80 )'If the TCP/80 line is displayed as LISTEN and the process information contains httpd, the HTTP service actually owns port 80 and is listening on it.
When troubleshooting, checking not only the service state in systemctl but also the socket owner helps distinguish situations such as “the service is running, but it is not listening on the intended port.”
Step 3: Check HTTP communication from another machine
Section titled “Step 3: Check HTTP communication from another machine”Access the server over HTTP from another machine.
curl --fail --show-error http://<<SERVER_IP>>/If the sample content can be retrieved, this confirms not only the listening state on the server but also that TCP/80 is actually reachable from the client.
If this communication fails even though a listening socket exists, the network path to the server port, firewalls, and the destination address should also be investigated.
Step 4: Check the ESTAB state during communication
Section titled “Step 4: Check the ESTAB state during communication”Normal HTTP requests finish quickly, so the connection may close before you can check the ESTAB state. To make the verification reproducible, place a sample CGI that delays the response by 10 seconds.
sudo tee /var/www/cgi-bin/slow.cgi >/dev/null <<'EOF'#!/bin/bashprintf 'Content-Type: text/plain\r\n\r\n'sleep 10printf 'slow response\n'EOFsudo chmod 755 /var/www/cgi-bin/slow.cgisudo restorecon -F /var/www/cgi-bin/slow.cgiNext, for verification, create a background connection from the server itself to the delayed-response URL and save an ESTAB line for TCP/80 while that connection remains open. Combining connection creation and the ss check in the same step prevents the state from being missed even during sequential execution.
sudo sh -c 'rm -f /tmp/ss-established.txt /tmp/ss-established-curl.log; nohup /usr/bin/curl --noproxy "*" --http1.1 --silent --show-error http://127.0.0.1/cgi-bin/slow.cgi >/tmp/ss-established-curl.log 2>&1 & for i in $(seq 1 200); do ss -tn "( sport = :80 )" | grep -m1 "^ESTAB " > /tmp/ss-established.txt && exit 0; sleep 0.1; done; printf "ESTAB was not observed within 20 seconds\\n" >&2; cat /tmp/ss-established-curl.log >&2 || true; ss -tn "( sport = :80 )" >&2 || true; exit 1'Next, access the delayed-response URL from another machine as well.
curl --fail --show-error http://<<SERVER_IP>>/cgi-bin/slow.cgiCheck the result saved on the server.
sudo cat /tmp/ss-established.txtIf there is a line beginning with ESTAB, the socket was observed while the TCP connection between the client and the HTTP server was established.
( sport = :80 ) is a filter for the local port. By checking the ESTAB line in the output, you can narrow the investigation to established connections involving TCP/80. The state established state filter can be used for the same purpose, but troubleshooting is easier if you first confirm the actual line with this procedure and then apply the state filter.
Step 5: Filter listening sockets by port
Section titled “Step 5: Filter listening sockets by port”Again, limit the listening TCP sockets to TCP/80 only.
sudo ss -ltnp '( sport = :80 )'On a server running many services, specifying the target port directly as a filter condition makes the investigation clearer than manually inspecting the entire output of ss -ltnp.
In typical HTTP communication from a client to a server, the local port on the server side is 80, so ( sport = :80 ) is used.
Step 6: Check a port conflict
Section titled “Step 6: Check a port conflict”While httpd is using TCP/80, try to bind another HTTP server process to the same port.
sudo python3 -m http.server 80 --bind 0.0.0.0If TCP/80 is already in use, this startup fails with Address already in use.
When a conflict occurs, do not rely on the error message alone. Use ss to determine which process owns the port.
sudo ss -ltnp '( sport = :80 )'If httpd is displayed, you can identify the process that already holds TCP/80.
The same method can be used when an application reports Address already in use or an equivalent bind error during startup.
Step 7: Check the case where no listening port exists
Section titled “Step 7: Check the case where no listening port exists”When troubleshooting a connection failure, you need to distinguish between “a process is listening, but there is a problem in the communication path” and “there is no listening socket at all.”
For verification, stop the HTTP service.
sudo systemctl stop httpdUse ss to verify that no TCP/80 listening socket remains.
if sudo ss -H -ltn '( sport = :80 )' | grep -q .; then echo "TCP/80 listener still exists" >&2 exit 1else echo "TCP/80 listener is absent"fiWith no listening socket present, try an HTTP connection from another machine.
curl --fail --show-error --connect-timeout 5 http://<<SERVER_IP>>/In this state, the HTTP connection fails.
This combination lets you distinguish whether the external connection fails because no process is listening on TCP/80, or whether the problem lies elsewhere in the communication path after a listening socket exists.
After verification, remove the temporary files and the delayed-response CGI, then return the HTTP service to a running state.
sudo rm -f /var/www/cgi-bin/slow.cgi /tmp/ss-established.txtsudo systemctl start httpdTroubleshooting
Section titled “Troubleshooting”Process information is not displayed with ss -p
Section titled “Process information is not displayed with ss -p”Details about processes owned by other users may not be visible. On an administered server, run the check with appropriate privileges, such as sudo ss -ltnp.
ESTAB cannot be confirmed
Section titled “ESTAB cannot be confirmed”If a TCP connection is short-lived, it may already be closed by the time ss is executed. You need to check with state established while the communication is still active.
After the connection ends, TCP transitions to other states such as TIME-WAIT, so searching only for ESTAB will not display those connections.
LISTEN exists, but external connections are not possible
Section titled “LISTEN exists, but external connections are not possible”LISTEN indicates that a socket on the server is waiting for connections, but it does not guarantee that external clients can actually reach it.
In this case, check the bind address, the host firewall, firewalls along the network path, routing, and the destination IP address in that order.
Summary
Section titled “Summary”When troubleshooting TCP problems with ss, it is easier to narrow down the cause by checking in the following order.
- Check whether the target port is in
LISTEN - Use
-pto identify the process that owns the port - Actually connect from another machine
- If necessary, check active connections with
state established - Narrow the output to the target port with conditions such as
sport - When a port conflict occurs, identify the process that already owns the port
- If there is no listening socket, prioritize checking service startup and configuration
This lets you examine the “service,” “listening socket,” “TCP connection,” and “external connectivity” separately, making it possible to troubleshoot port conflicts and connection failures step by step from the server side.