ss

List sockets: what is listening, and what is connected

Updated 2026-09-02

ss lists sockets: what is listening, what is connected, and which process owns each one. It comes from iproute2, which Debian marks important, so it is present on every Debian machine down to a minimal container. netstat is not. That one ships in net-tools, which a minimal Debian does not install, so the command in the answer you found is often missing on the machine you are trying to fix.

Reading the columns

State is LISTEN for a socket waiting to be connected to and ESTAB for one carrying a connection. UDP has no connection states, so a bound UDP socket reads UNCONN.

Recv-Q and Send-Q mean two different things depending on which of those it is. On an established connection they are byte counts: data that has arrived and not yet been read by the program, and data sent that the far end has not yet acknowledged. On a listening socket they are connection counts: how many are waiting to be accepted, and the backlog the program asked for when it called listen(). A listening socket whose Recv-Q climbs is one whose program is not accepting fast enough.

The order of the rows means nothing. ss prints them in the order the kernel walked its own tables, so two machines listening on the same ports can disagree about which comes first. No flag sorts them, and a script comparing one listing against another needs -H and a sort of its own.

Who can reach a service

A service on 0.0.0.0:8080 accepts connections from anywhere that can route to the machine. The same service on 127.0.0.1:8080 accepts them only from the machine itself. The port is the same in both, and the address column is the whole difference between a database your application can reach and a database everyone can.

This is why -n is worth typing. Without it ss maps port numbers to the names in /etc/services, so 5432 prints as postgresql whether or not anything resembling PostgreSQL is involved.

Flags that describe one machine only

-e, -i, -m and -o add socket inodes, congestion-control state, memory accounting and retransmit timers to each row. What they print depends on the kernel version and on what the connection has already done, which is why the examples below leave them out: a captured copy would show you figures your own machine will not produce. ss -K closes a matching socket outright, and needs both root and a kernel built with CONFIG_INET_DIAG_DESTROY.

Sample files used on this page

Every example below was run against these files. Recreate them to follow along.

four listening sockets and one connection a container listens on nothing at all until something is started, so the setup script starts four small Python services and every socket on this page is one of them. tips-api listens on 0.0.0.0:8080 with a backlog of 128, tips-db on 127.0.0.1:5432 with a backlog of 5, tips-logs on UDP 0.0.0.0:5140, and tips-cache on the Unix socket /run/tips-cache.sock. A fifth process, tips-client, holds one connection open to tips-api so the page has an established socket to show. The two TCP rows are printed in the order the kernel walked its own tables, so yours may arrive the other way round.

Netid State  Recv-Q Send-Q Local Address:Port Peer Address:Port
udp   UNCONN 0      0            0.0.0.0:5140      0.0.0.0:*
tcp   LISTEN 0      128          0.0.0.0:8080      0.0.0.0:*
tcp   LISTEN 0      5          127.0.0.1:5432      0.0.0.0:*
53 outputs, collapsed by default

What is listening

-l selects listening sockets, -t and -u pick TCP and UDP, and -n leaves port numbers as numbers. Without -l you get connections instead, so a service you went looking for is simply absent from the output.

List the TCP ports something is listening on

ss -ltn

The command to type when you want to know what this machine is offering. Two services here: one reachable from anywhere, one only from the machine itself.

Show output

Your output will differ: the kernel lists sockets in the order it walked its own tables, so the two rows may come back the other way round

State  Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0      128          0.0.0.0:8080      0.0.0.0:*
LISTEN 0      5          127.0.0.1:5432      0.0.0.0:*

List TCP and UDP together

ss -ltun

Adding -u puts UDP alongside TCP and introduces a Netid column to tell them apart. UDP sockets are never LISTEN, because there is no connection to wait for.

Show output

Your output will differ: the kernel lists sockets in the order it walked its own tables, so the TCP rows may come back the other way round

Netid State  Recv-Q Send-Q Local Address:Port Peer Address:Port
udp   UNCONN 0      0            0.0.0.0:5140      0.0.0.0:*
tcp   LISTEN 0      128          0.0.0.0:8080      0.0.0.0:*
tcp   LISTEN 0      5          127.0.0.1:5432      0.0.0.0:*

List only the UDP sockets

ss -lun

-u on its own. A bound UDP socket reports UNCONN and a Send-Q of 0, since neither a connection state nor an accept backlog applies to it.

Show output
State  Recv-Q Send-Q Local Address:Port Peer Address:Port
UNCONN 0      0            0.0.0.0:5140      0.0.0.0:*

See the service names instead of the numbers

ss -lt

Drop -n and ss looks each port up in /etc/services. 8080 becomes http-alt and 5432 becomes postgresql, which are the names registered for those numbers rather than anything the running programs claimed.

Show output

Your output will differ: the kernel lists sockets in the order it walked its own tables, so the two rows may come back the other way round

State  Recv-Q Send-Q Local Address:Port       Peer Address:Port
LISTEN 0      128          0.0.0.0:http-alt        0.0.0.0:*
LISTEN 0      5          127.0.0.1:postgresql      0.0.0.0:*

Resolve the addresses but keep the numbers

ss -ltnr

-r asks for hostname lookups on addresses while -n keeps ports numeric. 127.0.0.1 comes back as localhost from /etc/hosts. On a machine with real peers this is the flag that makes ss wait on DNS.

Show output

Your output will differ: the kernel lists sockets in the order it walked its own tables, so the two rows may come back the other way round

State  Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0      128          0.0.0.0:8080      0.0.0.0:*
LISTEN 0      5          localhost:5432      0.0.0.0:*

Drop the header row

ss -ltnH

-H prints the rows and nothing else, ready for awk or wc. The column padding goes with the header, so the fields arrive separated by single spaces.

Show output

Your output will differ: the kernel lists sockets in the order it walked its own tables, so the two rows may come back the other way round

LISTEN 0      128      0.0.0.0:8080 0.0.0.0:*
LISTEN 0      5      127.0.0.1:5432 0.0.0.0:*

Count what is listening

ss -ltnH | wc -l

Without -H the header would be counted as a socket, and every answer this returned would be one too many.

Show output
2

Restrict the output to IPv4

ss -ltn4

-4 and -6 select the address family. Worth knowing when a service appears twice in a listing, once per family, and you only care about one of them.

Show output

Your output will differ: the kernel lists sockets in the order it walked its own tables, so the two rows may come back the other way round

State  Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0      128          0.0.0.0:8080      0.0.0.0:*
LISTEN 0      5          127.0.0.1:5432      0.0.0.0:*

Ask what is listening on IPv6

ss -ltn6

Nothing here binds an IPv6 address, so the header arrives with no rows under it. A service you expected to find is either not running or bound to the other family, and this is how you separate those two.

Show output
State Recv-Q Send-Q Local Address:Port Peer Address:Port

Look at one port in a mixed listing

ss -lntu | grep 8080

Fine at a prompt. In a script prefer a filter expression, because grep 8080 also matches a peer on port 18080 and an address that happens to contain the digits.

Show output
tcp   LISTEN 0      128          0.0.0.0:8080      0.0.0.0:*

Get a summary of every socket on the machine

ss -s

Counts by transport rather than a row per socket. Useful as a first look at a machine you have just been handed, particularly when the number of sockets is itself the problem.

Show output

Your output will differ: the totals and the closed count depend on what the machine has been doing

Total: 10
TCP:   50 (estab 2, closed 46, orphaned 0, timewait 0)

Transport Total     IP        IPv6
RAW	  0         0         0
UDP	  1         1         0
TCP	  4         4         0
INET	  5         5         0
FRAG	  0         0         0

Which process holds the port

-p adds the program, its PID and the file descriptor number. It needs root to report processes it does not own, and it silently prints nothing in that column rather than warning you.

Show the process behind each listening socket

ss -ltnp

users:(("tips-api",pid=35,fd=3)) names the program, the process holding the socket, and which of its descriptors it is. The PID changes every boot; the name and the descriptor are what you act on.

Show output

Your output will differ: the PID is whatever the kernel handed the service on this boot, and the kernel lists sockets in the order it walked its own tables

State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0      128          0.0.0.0:8080      0.0.0.0:*    users:(("tips-api",pid=35,fd=3))
LISTEN 0      5          127.0.0.1:5432      0.0.0.0:*    users:(("tips-db",pid=36,fd=3))

Type the incantation everyone types

ss -tulpn

TCP and UDP, listening only, numeric, with processes. The letters are the same five whatever order you write them in, and this order is the one that stuck.

Show output

Your output will differ: the PID is whatever the kernel handed the service on this boot, and the kernel lists sockets in the order it walked its own tables

Netid State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
udp   UNCONN 0      0            0.0.0.0:5140      0.0.0.0:*    users:(("tips-logs",pid=37,fd=3))
tcp   LISTEN 0      128          0.0.0.0:8080      0.0.0.0:*    users:(("tips-api",pid=35,fd=3))
tcp   LISTEN 0      5          127.0.0.1:5432      0.0.0.0:*    users:(("tips-db",pid=36,fd=3))

Find what is holding one port

ss -tlnp sport = :8080

The question behind every "address already in use". Filtering beats grepping the whole table, because a filter is applied by the kernel and matches the port rather than the digits.

Show output

Your output will differ: the PID is whatever the kernel handed the service on this boot

State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0      128          0.0.0.0:8080      0.0.0.0:*    users:(("tips-api",pid=35,fd=3))

Pull out just the PID

ss -ltnpH sport = :8080 | grep -oP 'pid=\K[0-9]+'

grep -oP with \K drops everything up to the match, so nothing but the digits survives and the result can be passed straight to kill. Kill whatever is using a port has the whole procedure.

Show output

Your output will differ: the PID is whatever the kernel handed the service on this boot

35

Ask about the loopback-only service

ss -ltnp '( sport = :5432 )'

Parentheses let a filter be built up from several terms. A single term does not need them, and writing them anyway means you can add an or later without rearranging anything.

Show output

Your output will differ: the PID is whatever the kernel handed the service on this boot

State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0      5          127.0.0.1:5432      0.0.0.0:*    users:(("tips-db",pid=36,fd=3))

Drop the State column when the state is implied

ss -lptn state listening '( sport = :5432 )'

Naming a state explicitly removes the State column, since every row would carry the same value. Convenient for a fixed-width parse, and a surprise the first time a column you were counting on disappears.

Show output

Your output will differ: the PID is whatever the kernel handed the service on this boot

Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
0      5          127.0.0.1:5432      0.0.0.0:*    users:(("tips-db",pid=36,fd=3))

Count the sockets your own services hold

ss -lntup | grep -c tips-

The count covers the two TCP listeners and the UDP one. A quick way to confirm a deployment brought up everything it should have.

Show output
3

Sort a listing by address

ss -ltnpH | sort -k4

The kernel returns sockets in the order it walked its own tables, which is not an order you can rely on. Sorting on the local address column gives two runs something comparable.

Show output

Your output will differ: the PID is whatever the kernel handed the service on this boot

LISTEN 0      128      0.0.0.0:8080 0.0.0.0:* users:(("tips-api",pid=35,fd=3))
LISTEN 0      5      127.0.0.1:5432 0.0.0.0:* users:(("tips-db",pid=36,fd=3))

Connections, not just listeners

Drop -l and ss reports connections rather than listeners. Each connection appears twice on a machine talking to itself, once from each end.

List established connections

ss -tn

A single connection fills two rows here, because both of its ends are on this machine. The client's port was picked by the kernel from the ephemeral range and means nothing beyond this connection.

Show output

Your output will differ: the client's port is taken from the ephemeral range, so it differs on every connection, and the two ends of one connection may be listed either way round

State Recv-Q Send-Q Local Address:Port  Peer Address:Port
ESTAB 0      0          127.0.0.1:33156    127.0.0.1:8080
ESTAB 0      0          127.0.0.1:8080     127.0.0.1:33156

Show listeners and connections at once

ss -tan

-a means all, which is -l and its opposite together. The listening socket on 8080 and the connection it accepted are separate sockets, and this is the view where that stops being an abstraction.

Show output

Your output will differ: the client's port is taken from the ephemeral range, so it differs on every connection, and the two ends of one connection may be listed either way round

State  Recv-Q Send-Q Local Address:Port  Peer Address:Port
LISTEN 0      128          0.0.0.0:8080       0.0.0.0:*
LISTEN 0      5          127.0.0.1:5432       0.0.0.0:*
ESTAB  0      0          127.0.0.1:8080     127.0.0.1:33156
ESTAB  0      0          127.0.0.1:33156    127.0.0.1:8080

Select connections by state

ss -tn state established

state takes any TCP state name: established, time-wait, close-wait, syn-sent and the rest. A pile of close-wait sockets means a program is not closing what it finished with.

Show output

Your output will differ: the client's port is taken from the ephemeral range, so it differs on every connection, and the two ends of one connection may be listed either way round

Recv-Q Send-Q Local Address:Port  Peer Address:Port
0      0          127.0.0.1:8080     127.0.0.1:33156
0      0          127.0.0.1:33156    127.0.0.1:8080

Count established connections

ss -tH state established | wc -l

The number to graph when you want to know whether a service is busy. Take it per port with a sport filter, or the listening socket's own accepted connections get lost among everything else the machine is doing.

Show output
2

Look at connections to one port

ss -tn '( dport = :8080 )'

dport is the far end's port, so this finds the client side of the connection rather than the server's. One row, where the unfiltered listing showed two.

Show output

Your output will differ: the client's port is taken from the ephemeral range, so it differs on every connection

State Recv-Q Send-Q Local Address:Port  Peer Address:Port
ESTAB 0      0          127.0.0.1:33156    127.0.0.1:8080

Find who is connected to a given host

ss -tn dst 127.0.0.1

dst matches the peer address and takes a network as readily as a host, so dst 10.0.0.0/8 answers "what is this machine talking to on the internal network".

Show output

Your output will differ: the client's port is taken from the ephemeral range, so it differs on every connection, and the two ends of one connection may be listed either way round

State Recv-Q Send-Q Local Address:Port  Peer Address:Port
ESTAB 0      0          127.0.0.1:8080     127.0.0.1:33156
ESTAB 0      0          127.0.0.1:33156    127.0.0.1:8080

Count every TCP socket, listeners included

ss -tanH | wc -l

The total is the two listening sockets plus the two ends of the one connection. Compare it against the established count to see how much of a machine's socket table is doing nothing.

Show output
4

Filtering by port and address

A filter expression goes after the options. sport and dport are the local and remote ports, src and dst the addresses, and they combine with and, or and not. Keep the expression in single quotes so the shell leaves the parentheses alone.

Match one port exactly

ss -ltn sport = :8080

The colon is part of the syntax rather than a typo: :8080 is a port, where 8080 on its own would be read as an address.

Show output
State  Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0      128          0.0.0.0:8080      0.0.0.0:*

Match either of two ports

ss -ltn '( sport = :8080 or sport = :5432 )'

The quotes matter. Without them the shell treats the parentheses as its own syntax and the command fails before ss sees any of it.

Show output

Your output will differ: the kernel lists sockets in the order it walked its own tables, so the two rows may come back the other way round

State  Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0      128          0.0.0.0:8080      0.0.0.0:*
LISTEN 0      5          127.0.0.1:5432      0.0.0.0:*

Match a port above a number

ss -ltn sport gt :5000

gt, lt, ge and le compare ports numerically. Both services here are above 5000, so both come back.

Show output

Your output will differ: the kernel lists sockets in the order it walked its own tables, so the two rows may come back the other way round

State  Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0      128          0.0.0.0:8080      0.0.0.0:*
LISTEN 0      5          127.0.0.1:5432      0.0.0.0:*

Match a range of ports

ss -ltn '( sport >= :5000 and sport <= :6000 )'

The symbols work as well as the words. This is how you check whether anything has appeared in the range you reserved for one application.

Show output
State  Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0      5          127.0.0.1:5432      0.0.0.0:*

Find services bound to loopback only

ss -ltn src 127.0.0.1

src matches the local address, so this asks which services are reachable from this machine and nowhere else. The one on 0.0.0.0 does not match, because it is bound to every address rather than to this one.

Show output
State  Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0      5          127.0.0.1:5432      0.0.0.0:*

Select by state without naming a port

ss -ltn state listening

state listening alongside -l is redundant, and it is worth seeing on its own because it removes the State column exactly as it did with a port filter.

Show output

Your output will differ: the kernel lists sockets in the order it walked its own tables, so the two rows may come back the other way round

Recv-Q Send-Q Local Address:Port Peer Address:Port
0      128          0.0.0.0:8080      0.0.0.0:*
0      5          127.0.0.1:5432      0.0.0.0:*

Ask about a port nothing is using

ss -ltn sport = :9999

A header and no rows. ss exits 0 either way, so a script has to look at whether anything was printed rather than at the exit status.

Show output
State Recv-Q Send-Q Local Address:Port Peer Address:Port

Get a clean empty answer

ss -ltnH sport = :9999 | wc -l

Zero rows, with -H keeping the header out of the count. Build a check on this form.

Show output
0

Exclude a port instead of selecting it

ss -ltn not sport = :8080

not negates the term after it. Handy for reviewing what is listening once you have accounted for the service you already know about.

Show output
State  Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0      5          127.0.0.1:5432      0.0.0.0:*

Write the same exclusion with !=

ss -ltn '( sport != :8080 )'

!= reads better inside a longer expression, where a leading not has to be tracked across several terms to see what it governs.

Show output
State  Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0      5          127.0.0.1:5432      0.0.0.0:*

Watch the protocol flags narrow a filter

ss -tln '( sport = :8080 or sport = :5140 )'

The filter names both ports and only one row comes back, because -t had already excluded UDP before the filter was applied. A term that matches nothing is not an error, which makes this an easy half hour to lose.

Show output
State  Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0      128          0.0.0.0:8080      0.0.0.0:*

Filter UDP by port

ss -uln sport = :5140

The same filter syntax applies to UDP. sport is still the local port, even though nothing here is connected to anything.

Show output
State  Recv-Q Send-Q Local Address:Port Peer Address:Port
UNCONN 0      0            0.0.0.0:5140      0.0.0.0:*

Find the services bound to every interface

ss -ltnH src 0.0.0.0

The counterpart to filtering on 127.0.0.1. These are the sockets exposed beyond the machine, and on a host you have just been given they are the list worth reading first.

Show output
LISTEN 0      128    0.0.0.0:8080 0.0.0.0:*

Unix sockets and other families

Most of what a Debian machine listens on has no port at all. Unix domain sockets are files, and services from the system logger to the package manager's own helpers use them in preference to a loopback port.

List listening Unix sockets

ss -lx

-x selects the Unix family. The path takes the place of an address, and the number beside it is the socket's inode rather than a port.

Show output

Your output will differ: the number beside the path is the socket's inode, which is different on every machine

Netid State  Recv-Q Send-Q        Local Address:Port     Peer Address:Port
u_str LISTEN 0      5      /run/tips-cache.sock 20858118            * 0

Spell the family out

ss -f unix -l

-f takes a family name, so -f unix is -x written so a reader does not have to remember which letter that was. inet, inet6 and link are the others worth knowing.

Show output

Your output will differ: the number beside the path is the socket's inode, which is different on every machine

Netid State  Recv-Q Send-Q        Local Address:Port     Peer Address:Port
u_str LISTEN 0      5      /run/tips-cache.sock 20858118            * 0

Strip a Unix socket listing for a script

ss -lxH

Without the header the columns collapse to single spaces, so awk '{print $5}' gets the path. u_str is a stream socket, and u_dgr would be a datagram one.

Show output

Your output will differ: the number beside the path is the socket's inode, which is different on every machine

u_str LISTEN 0      5      /run/tips-cache.sock 20858118 * 0

Ask for connected Unix sockets rather than listening ones

ss -x

Without -l the Unix family behaves like any other: this asks for connections and nothing here has one. The listener from the previous examples is still there, and -l or -a is what includes it.

Show output
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port

Count every Unix socket at once

ss -aH -f unix | wc -l

-a covers listening and connected together. On a desktop this number runs into the hundreds, since the display server, the session bus and the audio daemon each hold several.

Show output
1

Look at raw sockets

ss -w

-w selects raw sockets, the ones a program opens to speak a protocol the kernel does not handle for it. ping holds one while it runs, and on most machines the list is empty.

Show output
Recv-Q Send-Q Local Address:Port Peer Address:Port

In a script

ss is safe to build on once -n stops a name lookup changing the output and -H stops the header being mistaken for data. The exit status is 0 whether or not anything matched, so test the output.

Test whether a port is taken

ss -ltnH sport = :8080 | grep -q . && echo listening || echo free

grep -q . succeeds when at least one line arrived. The pattern to use before starting a service on a fixed port, rather than starting it and reading the error.

Show output
listening

See the same test come back clear

ss -ltnH sport = :9999 | grep -q . && echo listening || echo free

The same test on a port nothing has bound. Both branches are worth checking when you write this, since a test that can only say one thing is not a test.

Show output
free

Print the state and the address

ss -ltnH sport = :8080 | awk '{print $1, $4}'

With -H the fields are stable: state, Recv-Q, Send-Q, local address, peer address. Adding -p appends a sixth, so a script that indexes fields should pick its flags once and keep them.

Show output
LISTEN 0.0.0.0:8080

List every address being listened on

ss -ltpnH | awk '{print $4}' | sort

Sorted so the answer does not depend on the order the kernel walked its tables. Useful as a record to compare against after a change.

Show output
0.0.0.0:8080
127.0.0.1:5432

Read one socket's accept backlog

ss -ltnH '( sport = :5432 )' | awk '{print $3}'

The third field of a listening row is the backlog the program asked for. Compare it against the connection rate a service is expected to take, and against Recv-Q when connections are being refused.

Show output
5

Count connections to one port

ss -tnH state established '( sport = :8080 )' | wc -l

Narrowed by sport, so the count belongs to one service and not to everything the machine happens to be doing.

Show output
1

Read the address a service is bound to

ss -ltnH sport = :5432 | awk '{print $4}' | cut -d: -f1

Splitting on the colon leaves the address without the port. Comparing it against 0.0.0.0 is how a check tells a service that is exposed from one that is not.

Show output
127.0.0.1

Print both queue columns

ss -ltnH | awk '{print $2, $3}'

For a listening socket these are the waiting connections and the backlog. A monitoring check that reports the first as a fraction of the second says more than either number alone.

Show output

Your output will differ: the kernel lists sockets in the order it walked its own tables, so the two rows may come back the other way round

0 128
0 5