Skip to content

Investigating Listening Ports and Connection States with the ss Command

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 ESTAB state
  • 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 nameExample settingDescription
<<SERVER_IP>>192.0.2.10IP address of the destination web server

This article primarily uses the following options.

OptionMeaning
-tDisplay TCP sockets
-lDisplay only listening sockets
-nDisplay port numbers and addresses numerically without name resolution
-pDisplay information about the process using the socket
-HHide the header line
state establishedSelect only TCP connections whose state is established
sport = :80Select 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.

Terminal window
sudo /usr/bin/dnf install -y iproute httpd python3

Place sample content that is easy to verify over HTTP, then start httpd and firewalld. Allow the HTTP service in the current firewalld runtime configuration.

Terminal window
printf '%s\n' 'ss connection test' | sudo tee /var/www/html/index.html >/dev/null
sudo systemctl enable --now httpd
sudo systemctl enable --now firewalld
sudo firewall-cmd --add-service=http

Check the service status.

Terminal window
sudo systemctl status httpd --no-pager

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

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

Terminal window
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/bash
printf 'Content-Type: text/plain\r\n\r\n'
sleep 10
printf 'slow response\n'
EOF
sudo chmod 755 /var/www/cgi-bin/slow.cgi
sudo restorecon -F /var/www/cgi-bin/slow.cgi

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

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

Terminal window
curl --fail --show-error http://<<SERVER_IP>>/cgi-bin/slow.cgi

Check the result saved on the server.

Terminal window
sudo cat /tmp/ss-established.txt

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

Again, limit the listening TCP sockets to TCP/80 only.

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

While httpd is using TCP/80, try to bind another HTTP server process to the same port.

Terminal window
sudo python3 -m http.server 80 --bind 0.0.0.0

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

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

Terminal window
sudo systemctl stop httpd

Use ss to verify that no TCP/80 listening socket remains.

Terminal window
if sudo ss -H -ltn '( sport = :80 )' | grep -q .; then
echo "TCP/80 listener still exists" >&2
exit 1
else
echo "TCP/80 listener is absent"
fi

With no listening socket present, try an HTTP connection from another machine.

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

Terminal window
sudo rm -f /var/www/cgi-bin/slow.cgi /tmp/ss-established.txt
sudo systemctl start httpd

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.

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.

When troubleshooting TCP problems with ss, it is easier to narrow down the cause by checking in the following order.

  1. Check whether the target port is in LISTEN
  2. Use -p to identify the process that owns the port
  3. Actually connect from another machine
  4. If necessary, check active connections with state established
  5. Narrow the output to the target port with conditions such as sport
  6. When a port conflict occurs, identify the process that already owns the port
  7. 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.

Category: Linux