cheat sheet

ps, ss & netstat

Inspect running processes (ps), list network connections and listening ports (ss / netstat). Covers output formats, filtering, process trees, and socket state analysis.

ps, ss & netstat — Processes & Sockets

What it is

ps, ss, and netstat are standard Linux system inspection utilities for monitoring running processes and network connections. ps (process status) is POSIX-standard and available on every Unix system, shipped on Linux via the procps-ng package (current release 4.0.6, January 2026) alongside pgrep, pkill, top, and vmstat. ss (socket statistics) is the modern Linux replacement for netstat, querying the kernel directly over a netlink socket — significantly faster than walking /proc/net/*. netstat was deprecated back in 2001 and has been removed from the default install on most current distributions; reach for ps to see what processes are running and their resource usage, ss to list open ports and active network connections, and netstat only on older systems or on macOS/BSD where it is still standard.


ps — Process Status

Syntax styles

ps accepts both BSD (ps aux) and POSIX/Unix (ps -ef) style options — don't mix them.

Most common invocations

ps aux (BSD style) and ps -ef (POSIX style) are the two standard ways to list every process on the system — use either; they show the same processes with slightly different column layouts. BSD-style adds %CPU and %MEM columns that are absent from the POSIX format.

bash
ps aux               # all processes, user-friendly (BSD)
ps -ef               # all processes, full format (POSIX)
ps -eF               # POSIX + extra columns
ps -ely              # POSIX + long format (no truncation)
ps axjf              # BSD + forest (process tree)
ps -ejH              # POSIX + hierarchical tree

Output (ps aux):

text
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0 168756 13200 ?        Ss   Apr23   0:04 /sbin/init
root         423  0.0  0.1  47380  8916 ?        Ss   Apr23   0:00 /lib/systemd/systemd-journald
root         512  0.0  0.0  22200  4096 ?        Ss   Apr23   0:00 /lib/systemd/systemd-udevd
www-data    1234  0.0  0.5  12340  5120 ?        Ss   09:00   0:00 nginx: master process
www-data    1235  0.0  0.3   9876  3456 ?        S    09:00   0:00 nginx: worker process
alice       8821  0.0  0.2  16788  4200 pts/0    Ss   10:14   0:00 bash
alice       9103  0.0  0.1  11212  3040 pts/0    R+   10:22   0:00 ps aux

Output (ps -ef):

text
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 Apr23 ?        00:00:04 /sbin/init
root         423       1  0 Apr23 ?        00:00:00 /lib/systemd/systemd-journald
www-data    1234       1  0 09:00 ?        00:00:00 nginx: master process
www-data    1235    1234  0 09:00 ?        00:00:00 nginx: worker process
alice       8821    8800  0 10:14 pts/0    00:00:00 bash
alice       9110    8821  0 10:22 pts/0    00:00:00 ps -ef

Column reference (BSD aux)

ColumnMeaning
USERProcess owner
PIDProcess ID
%CPUCPU usage %
%MEMMemory usage %
VSZVirtual memory size (KB)
RSSResident (physical) memory (KB)
TTYControlling terminal
STATProcess state
STARTStart time
TIMECumulative CPU time
COMMANDCommand + arguments

Process states (STAT)

CodeMeaning
RRunning or runnable
SSleeping (interruptible)
DUninterruptible sleep (I/O)
TStopped (signal or debugger)
ZZombie (finished, not reaped)
<High priority
NLow priority
LLocked pages in memory
sSession leader
lMulti-threaded
+Foreground process group

Filtering and searching

Pipe ps output through grep for quick name searches, or use pgrep which searches process names directly and returns just the PIDs — cleaner for scripting and avoids the classic grep self-inclusion problem.

bash
ps aux | grep nginx                  # find by name
ps aux | grep -v grep | grep nginx   # exclude grep itself
ps -C nginx                          # by command name (POSIX)
ps -p 1234                           # by PID
ps -p 1234,5678                      # multiple PIDs
ps -u alice                          # by user
ps --ppid 1234                       # children of PID

# Find PID of a program
pgrep nginx                          # print matching PIDs
pgrep -l nginx                       # with process names
pgrep -a nginx                       # with full command
pgrep -u root nginx                  # by user + name

Output (ps aux | grep -v grep | grep nginx):

text
www-data    1234  0.0  0.5  12340  5120 ?  Ss  09:00  0:00 nginx: master process
www-data    1235  0.0  0.3   9876  3456 ?  S   09:00  0:00 nginx: worker process
www-data    1236  0.0  0.3   9876  3456 ?  S   09:00  0:00 nginx: worker process

Output (pgrep nginx):

text
1234
1235
1236

Output (pgrep -l nginx):

text
1234 nginx
1235 nginx
1236 nginx

Custom output format

-o (or -eo to select all processes) lets you specify exactly which columns appear in the output and in what order, using comma-separated specifier names. Combine with --sort=-COLUMN to rank processes by CPU or memory without needing a separate sort call.

bash
ps -eo pid,ppid,user,%cpu,%mem,vsz,rss,stat,comm
ps -eo pid,user,comm --sort=-%cpu | head -15     # top CPU consumers
ps -eo pid,user,comm --sort=-%mem | head -15     # top memory consumers
ps -eo pid,etime,comm                            # elapsed time since start
ps -eo pid,lstart,comm                           # absolute start time

# All threads of a process
ps -T -p 1234
ps -eLf | grep 1234

Output (ps -eo pid,user,comm --sort=-%cpu | head -15):

text
    PID USER     COMMAND
   4521 alice    python3
   1234 www-data nginx
   8800 root     sshd
   9200 bob      node
    423 root     systemd-journal

Process tree

A process tree view shows parent-child relationships between processes, making it easy to identify which daemon spawned a misbehaving worker. ps axjf produces an ASCII art tree inline; pstree is a dedicated tool with cleaner output and options to show PIDs and usernames.

bash
ps axjf                  # ASCII art tree (BSD)
ps -ejH                  # indented tree (POSIX)
pstree                   # dedicated tool
pstree -p                # with PIDs
pstree -u                # with usernames
pstree -a                # with arguments

Output (pstree -p):

text
systemd(1)─┬─sshd(800)───sshd(8800)───bash(8821)───pstree(9210)
            ├─nginx(1234)─┬─nginx(1235)
            │             └─nginx(1236)
            ├─systemd-journal(423)
            └─cron(611)

Watch processes

watch reruns a command at a fixed interval and updates the display in place, giving a lightweight live view of process state without the overhead of top. Useful for monitoring a specific workload without switching to an interactive tool.

bash
watch -n 1 'ps aux --sort=-%cpu | head -20'

Output: (none — exits 0 on success)


ss — Socket Statistics (modern netstat)

ss is the modern replacement for netstat — faster, more features, same basic concepts.

Common invocations

ss -tuln is the go-to command: it shows all TCP and UDP listening sockets in numeric form (no slow DNS lookups). Add -p to see which process owns each socket — this requires root to see sockets owned by other users.

bash
ss -tuln          # TCP+UDP listening sockets, numeric (most common)
ss -tulnp         # + show process name/PID
ss -ta            # all TCP connections
ss -ua            # all UDP sockets
ss -x             # UNIX domain sockets
ss -s             # summary statistics
ss -i             # TCP internal info (window, RTT)

Output (ss -tulnp):

text
Netid  State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
tcp    LISTEN  0       128     0.0.0.0:22           0.0.0.0:*          users:(("sshd",pid=800,fd=3))
tcp    LISTEN  0       511     0.0.0.0:80           0.0.0.0:*          users:(("nginx",pid=1234,fd=6))
tcp    LISTEN  0       511     0.0.0.0:443          0.0.0.0:*          users:(("nginx",pid=1234,fd=8))
tcp    LISTEN  0       128     127.0.0.1:5432       0.0.0.0:*          users:(("postgres",pid=2100,fd=5))
udp    UNCONN  0       0       0.0.0.0:68           0.0.0.0:*          users:(("dhclient",pid=345,fd=6))

Output (ss -s):

text
Total: 142
TCP:   38 (estab 24, closed 8, orphaned 0, timewait 8)

Transport Total     IP        IPv6
RAW	  0         0         0
UDP	  6         4         2
TCP	  30        22        8
INET	  36        26        10
FRAG	  0         0         0

Option flags

FlagMeaning
-tTCP sockets
-uUDP sockets
-xUNIX domain sockets
-lListening sockets only
-aAll sockets (listening + connected)
-nNumeric (no DNS/service name resolution)
-pShow process (requires root for others' processes)
-eExtended info (UID, inode)
-oShow timer info
-rResolve hostnames
-4IPv4 only
-6IPv6 only

Filtering with expressions

ss accepts a filter expression after the flags to match by TCP state, source address, destination port, or remote host — all evaluated by the kernel before results are returned, which is much faster than piping to grep on a system with thousands of connections.

bash
ss -tnp state established                    # established TCP
ss -tnp state time-wait                      # TIME-WAIT
ss -tnp state listening                      # listening
ss -tnp '( dport = :80 or sport = :80 )'    # port 80
ss -tnp dst 10.0.0.1                         # connections to host
ss -tnp src :22                              # connections from port 22
ss -tnp dport :\> :1024                      # dest port > 1024

Output: (none — exits 0 on success)

TCP states

ESTABLISHED, SYN-SENT, SYN-RECV, FIN-WAIT-1, FIN-WAIT-2, TIME-WAIT, CLOSE, CLOSE-WAIT, LAST-ACK, LISTEN, CLOSING

bash
ss -t state established '( dport = :443 )' # HTTPS connections
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn  # count by state

Output (ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn):

text
     24 ESTABLISHED
      8 TIME-WAIT
      4 LISTEN
      2 CLOSE-WAIT

What's listening on a port

When a port is unexpectedly in use or a service fails to bind, use ss -tlnp | grep :PORT to identify the process holding it. Combine with sudo if the socket belongs to another user — without it, the process column will be empty for sockets you don't own.

bash
ss -tlnp | grep :8080
ss -tnlp sport = :443
sudo ss -tlnp | grep '"nginx"'   # filter by process name

Output (ss -tlnp | grep :8080):

text
tcp  LISTEN  0  128  0.0.0.0:8080  0.0.0.0:*  users:(("node",pid=9200,fd=19))

netstat — Legacy (still common)

netstat is deprecated on Linux (replaced by ss) and is no longer installed by default on current Debian, Ubuntu, Fedora, RHEL, or Arch — you typically have to apt install net-tools (or equivalent) to get it back. It remains the standard tool on macOS and BSD, where the iproute2 ss replacement does not exist.

bash
netstat -tuln          # TCP+UDP listening, numeric (like ss -tuln)
netstat -tunap         # all TCP/UDP + process info
netstat -rn            # routing table
netstat -i             # interface statistics
netstat -s             # summary by protocol
netstat -an            # all connections, numeric

Output (netstat -tuln):

text
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
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:5432          0.0.0.0:*               LISTEN
udp        0      0 0.0.0.0:68              0.0.0.0:*

macOS / BSD netstat differences

bash
# macOS: -p specifies protocol, not process
netstat -anp tcp       # macOS: all TCP (not process info)
netstat -anp udp       # macOS: all UDP

# Get process info on macOS
sudo lsof -i :8080     # what's on port 8080
sudo lsof -i tcp       # all TCP connections

Output: (none — exits 0 on success)


lsof — List Open Files / Ports

lsof (list open files) shows every file descriptor a process has open — including regular files, sockets, pipes, and devices. On Linux, "everything is a file," so lsof -i is an effective alternative to ss for correlating network connections with specific processes, and lsof +D /path reveals which processes hold files open in a directory.

bash
lsof -i                          # all network connections
lsof -i :8080                    # what's using port 8080
lsof -i tcp                      # all TCP connections
lsof -i tcp:443                  # port 443 specifically
lsof -i @192.168.1.1             # connections to/from IP
lsof -p 1234                     # files opened by PID 1234
lsof -u alice                    # files opened by user
lsof -c nginx                    # files opened by nginx processes
lsof +D /var/log                 # files open in /var/log tree

Output (lsof -i :80):

text
COMMAND   PID      USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx    1234  www-data    6u  IPv4  23456      0t0  TCP *:http (LISTEN)
nginx    1235  www-data    6u  IPv4  23456      0t0  TCP *:http (LISTEN)
nginx    1236  www-data    6u  IPv4  23456      0t0  TCP *:http (LISTEN)

Practical recipes

bash
# What process is listening on port 80?
sudo ss -tlnp | grep ':80 '
sudo lsof -i :80

# All established connections and their process
sudo ss -tnp state established

Output (sudo ss -tnp state established):

text
State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
ESTAB   0       0       10.0.0.10:22        10.0.0.5:52314     users:(("sshd",pid=8800,fd=4))
ESTAB   0       0       10.0.0.10:443       203.0.113.42:49182 users:(("nginx",pid=1235,fd=12))
ESTAB   0       0       10.0.0.10:443       203.0.113.99:51033 users:(("nginx",pid=1236,fd=14))
bash
# Count connections per remote IP
ss -tn state established | awk '{print $5}' | cut -d: -f1 | \
  sort | uniq -c | sort -rn | head -20

Output:

text
     42 203.0.113.42
     17 10.0.0.5
      8 192.168.1.101
      3 198.51.100.7
bash
# Find zombie processes
ps aux | awk '$8=="Z" {print $2, $11}'

Output:

text
14321 defunct-worker
14989 old-handler
bash
# Kill all processes matching name (safer than killall)
pgrep -x stale-worker | xargs kill -TERM

# Top 10 memory-hungry processes
ps aux --sort=-%mem | awk 'NR<=11 {printf "%-10s %5s %5s %s\n",$1,$3,$4,$11}'

Output:

text
USER       %CPU  %MEM COMMAND
alice       1.2   4.8 java
www-data    0.0   1.3 nginx
postgres    0.0   0.9 postgres
bob         0.3   0.7 node
root        0.0   0.4 python3
bash
# Watch connection count to port 443
watch -n 2 'ss -tn state established dst :443 | wc -l'

# Find processes with open network connections
sudo lsof -i -n -P | grep ESTABLISHED | awk '{print $1,$9}' | sort -u

Output:

text
chrome   203.0.113.42:443->10.0.0.10:55210
nginx    10.0.0.10:443->203.0.113.42:49182
sshd     10.0.0.10:22->10.0.0.5:52314

On modern Linux, always prefer ss over netstat. ss reads directly from the kernel's netlink socket rather than walking /proc/net/*, making it significantly faster on systems with many connections. As of 2026, netstat is no longer installed by default on Debian, Ubuntu, Fedora, RHEL, or Arch — install the net-tools package if you need it.


Sources