>_ josetilabs.com
← blog

01 Nov 2026 · linux, processos, rede, diagnostico · 6 min

How to find processes, ports and connections on Linux

To find what is running, who is listening on which port and which process holds a file, use three tools: ps, ss and lsof. They read the same source, the kernel’s /proc directory, and answer different questions. ps talks about processes, ss about sockets, lsof about open files. Together they cover almost every ‘who is doing what’ diagnosis on a Linux machine.

Terminal em dois paineis: ss -tlnp mostrando python3 pid 381 escutando em 0.0.0.0:8080, e lsof -nP -iTCP -sTCP:LISTEN confirmando o mesmo processo e fd 3
Saida real de um conteiner isolado com um python3 -m http.server na porta 8080.

ps: what is running

ps is a snapshot. It does not refresh the screen like top; it prints once and exits. That makes it predictable for scripts and for pasting into a ticket.

bash
ps aux                 # all processes of all users
ps -ef                 # SysV format, includes PPID and start time
ps -e --forest         # process tree with indentation
ps -o pid,ppid,user,%cpu,%mem,stat,cmd -p 1234   # only process 1234

To find a process by name, prefer pgrep:

bash
pgrep -a nginx         # PID and command line
ps aux | grep '[n]ginx'   # the brackets keep grep itself out of the list

Columns worth reading: PID, PPID, USER, STAT, %CPU, %MEM, TIME and CMD. STAT tells the state: R running, S sleeping, D waiting on I/O (uninterruptible), Z zombie (dead process whose parent never reaped it) and T stopped. A persistent D usually means stuck disk or NFS, not CPU.

To kill safely, use the PID ps showed and check first: kill -TERM <pid>, wait, then kill -KILL <pid> only if it did not exit. Killing by name with killall or pkill is convenient but risky when more than one process shares a similar name.

ss: who is listening on which port

ss replaced netstat and reads straight from the kernel. The most used recipe is ss -tlnp: TCP, listening only, numeric, with process.

bash
ss -tlnp               # TCP listening, with PID and name
ss -ulnp               # UDP listening
ss -tunap              # everything: listening and established connections
ss -tn state established '( dport = :443 or sport = :443 )'

Four columns matter: State (LISTEN, ESTAB, TIME-WAIT), Local Address:Port, Peer Address:Port and Process. If the Process column is empty, run with sudo or check the net_admin capability of your user.

In a test container with a python3 -m http.server on port 8080, ss -tlnp shows 0.0.0.0:8080 with users:(('python3',pid=381,fd=3)). The fd 3 is the socket descriptor. The same information shows up in lsof, which lets you cross-check the two views.

ss -s prints a summary of kernel counters: how many connections in each state. Useful for spotting a pile of TIME-WAIT or half-open connections.

lsof: who holds the file or the port

lsof lists open files. On Linux, sockets, pipes and terminals are files too, so lsof covers the network. lsof -nP -iTCP -sTCP:LISTEN lists who is listening, with the same information as ss plus the executable path.

bash
lsof -nP -iTCP -sTCP:LISTEN        # who listens on TCP
lsof -nP -i :8080                  # who uses port 8080 (TCP or UDP)
lsof -p 381                        # everything process 381 opened
lsof /var/log/app.log              # who has this file open
lsof +D /tmp                       # descend into /tmp
lsof +L1                           # deleted files still open

The deleted-file case is the most valuable: a log rotated with mv or rm keeps occupying space while the process holds it open. lsof +L1 shows those ghosts. The fix is to restart the service or truncate the descriptor via /proc/<pid>/fd/<n>.

lsof -b skips calls that may block (stat on a stuck NFS), useful when the machine is slow and plain lsof hangs.

The three together

Question: “who is on port 8080?”

bash
ss -tlnp | grep ':8080'
lsof -nP -i :8080

Question: “what is this process doing?”

bash
ps -p <pid> -o pid,user,lstart,cmd
lsof -p <pid> | head -30

Question: “why did the disk fill up?”

bash
lsof +L1
lsof / | grep deleted

The answer is almost always a deleted file still open, or a process writing to a path nobody watches. To dig deeper into space usage, see disk full: how to find what is taking the space.

How to verify

Run ss -tlnp on a machine with at least one service listening. The expected output has a line with LISTEN, an address like 0.0.0.0:22 or [::]:80, and the Process column filled in. If the Process column is empty, the command ran without enough privilege.

To confirm the cross-check, run lsof -nP -iTCP -sTCP:LISTEN and verify that the PID and process name match what ss showed.

If it goes wrong

ps, ss or lsof

Tool Answers Reads When to use
ps who is running, with CPU and memory /proc//stat process inventory, scripts
ss who listens or is connected /proc/net/tcp ports, connections, TCP states
lsof who holds a file, socket or port /proc//fd locked file, busy port, ghost disk usage

None replaces the others. ps knows nothing about ports, ss knows nothing about files, lsof shows no CPU. The trio covers the diagnosis.

FAQ

ss or netstat, which one today? Use ss. netstat comes from the net-tools package, which is in maintenance mode and does not track recent kernel changes. ss is the default on current distributions.

Why does ss not show the process name? Not enough privilege. Run with sudo or add the net_admin capability to the user. In a container, check whether the capability was granted.

lsof prints too much, how to filter? Combine selectors: lsof -p <pid> for one process, lsof -u <user> for one user, lsof -i TCP:443 for one port. Selectors of the same kind are ORed; use -a to AND across kinds.

How to tell if a process is a zombie? ps aux | grep 'Z' in the STAT column, or ps -o pid,stat,cmd -p <pid>. A zombie uses no CPU or memory but holds a PID. The parent must call wait() or be restarted.

The artifact below runs without ps, ss or lsof. Why? Because all three read /proc. The script reads the same files they read, which proves the information lives in the kernel, not in the tool. In production, use ps, ss and lsof; the script is for understanding and for minimal environments.

Artifact

bash
#!/usr/bin/env bash
# Recipes for finding processes, ports and open files.
# Works by reading /proc directly: that is what ps, ss and lsof do underneath.
# Run as root to see processes of other users.

set -euo pipefail

# demo: open a test port with perl (no real service needed)
perl -MIO::Socket::INET -e 'my $s=IO::Socket::INET->new(LocalPort=>8765,Listen=>5,Reuse=>1) or die $!; sleep 300;' >/dev/null 2>&1 &
DEMO_PID=$!
sleep 1

echo "=== 1. processes: who is running ==="
for d in /proc/[0-9]*; do
    pid=${d##*/}
    [ -r "$d/stat" ] || continue
    name=$(awk '{print $2}' "$d/stat" 2>/dev/null | tr -d '()') || continue
    cmd=$(tr '\0' ' ' < "$d/cmdline" 2>/dev/null | sed 's/ *$//')
    [ -z "$cmd" ] && cmd="[$name]"
    printf '%6s  %-16s %s\n' "$pid" "$name" "$cmd"
done | sort -n | head -12

echo
echo "=== 2. listening ports (equivalent to ss -tlnp) ==="
decode_hex() {
    local hex=$1
    local iphex=${hex%:*} porthex=${hex#*:}
    local a=$((16#${iphex:6:2})) b=$((16#${iphex:4:2})) c=$((16#${iphex:2:2})) d=$((16#${iphex:0:2}))
    local port=$((16#$porthex))
    printf '%s.%s.%s.%s:%s' "$a" "$b" "$c" "$d" "$port"
}
inode_to_pid() {
    local target=$1
    for fd in /proc/[0-9]*/fd/*; do
        [ -L "$fd" ] || continue
        if [ "$(readlink "$fd" 2>/dev/null)" = "socket:[$target]" ]; then
            local pid=${fd#/proc/}; pid=${pid%%/*}
            echo "$pid"
            return
        fi
    done
}
awk 'NR>1 && $4=="0A" {print $2, $10}' /proc/net/tcp | while read -r endhex inode; do
    pid=$(inode_to_pid "$inode")
    printf '%-22s pid=%s\n' "$(decode_hex "$endhex")" "${pid:-?}"
done

echo
echo "=== 3. open files of a PID (equivalent to lsof -p) ==="
TARGET_PID=${1:-1}
echo "Target PID: $TARGET_PID"
if [ -d "/proc/$TARGET_PID/fd" ]; then
    for fd in /proc/$TARGET_PID/fd/*; do
        [ -e "$fd" ] || continue
        num=${fd##*/}
        target=$(readlink "$fd" 2>/dev/null || echo "?")
        printf '  fd %-4s -> %s\n' "$num" "$target"
    done
else
    echo "  (process $TARGET_PID does not exist)"
fi

# clean up the demo
kill "$DEMO_PID" 2>/dev/null || true
wait "$DEMO_PID" 2>/dev/null || true

To watch processes, memory and I/O in real time, see how to see what is using memory on Linux.

Sources: ps(1), ss(8), lsof(8). Tested in a debian:stable-slim container on 01 Nov 2026.

Read next

// By e-mail

Posts published here also go out by e-mail. Delivery has not started yet: whoever leaves an address now joins the first batch.

I store only your e-mail, the date and the record of this consent. No cookies, no trackers, nothing shared with third parties. Details in the privacy policy.