lsof
List open files and the processes holding them
lsof lists open files. That word covers more than it sounds like: a regular file, a directory, a
device, a pipe, a network socket and the program's own executable are all open files, and lsof
reports on every kind in one table. It answers "what is holding this" about a file you cannot
unmount over, a port you cannot bind, and space that will not come back.
The whole table is almost never what you want. With no arguments it prints every open file belonging to every process, which on an idle Debian server is already thousands of lines, most of them shared libraries. A useful invocation narrows: to one path, one process name, one user, one port.
Narrowing has a trap in it. -c, -u, -p, -i and -d combine with OR, so
lsof -c nginx -u www-data lists everything nginx has open plus everything www-data has open.
-a switches the combination to AND, and nothing makes it the default. A command that reads as two
conditions is answering a looser question until the -a is in it.
The FD column carries most of the detail. A number is a file descriptor, followed by the mode the
file was opened in: 3w is descriptor 3 open for writing, 3r for reading, 3u for both. Four
names appear in place of a number. cwd is the process's working directory, rtd its root, txt
the executable it is running, and mem a file mapped into its address space, which for anything
dynamically linked means a dozen shared libraries. -d ^mem drops those, and it is worth typing
before reading anything.
A file that has been unlinked while a process still holds it open is the case nothing else will show
you. The name is gone from its directory, so ls cannot see it and
du does not count it, while the blocks stay allocated until the last descriptor
closes. df goes on reporting them as used, which is why a filesystem can stay full
after the large file has been deleted, and why restarting the process frees the space when deleting
the file did not. lsof +L1 lists the files in that state and nothing else.
-i selects network files. lsof -i :8080 names the process listening on that port and anything
connected to it, which is the same ground ss covers from the socket's side rather
than the process's. Addresses and ports are resolved to names by default, so 8080 prints as
http-alt and 127.0.0.1 as localhost; -P and -n turn off the port and address lookups, and
a -i with both is easier to read as well as faster.
What lsof can see depends on who runs it. Open files are read through /proc, where an ordinary
account may read only its own processes, so the same command without sudo gives a shorter answer
rather than an error. That is worth knowing before concluding that nothing holds a file.
Debian ships lsof in a package of its own, so a minimal install may not have it.
fuser comes from psmisc and answers a narrower version of the same question in
a form that is easier to pipe into a kill.
Sample files used on this page
Every example below was run against these files. Recreate them to follow along.
the files under /srv the setup script starts six small Python services and every open file on this page belongs to one of them. tips-log appends to app.log, tips-job runs as user and reads both queue.dat and spool/job-0142.json, tips-api listens on port 8080, tips-client holds one connection to it, tips-archiver writes site.tar, and tips-tmp wrote a five-megabyte scratch.dat and then deleted it while still holding it open. /srv/cache is a 20M tmpfs of its own, so a question about a whole filesystem lands on that rather than on the container's root. scratch.dat is missing from the listing below because it has no name left.
/srv/cache:
site.tar
/srv/tips:
app.log
queue.dat
spool
/srv/tips/spool:
job-0142.json
What has this file open
Give lsof a path and it names every process holding that file. The FD column says which descriptor and in which mode: 3w is descriptor 3 open for writing, 3r for reading. DEVICE, SIZE/OFF and NODE describe the file itself rather than the process.
Find out what is writing to a log file
lsof /srv/tips/app.log
A single writer, on descriptor 3. This is the question lsof exists for, and a path is all it needs.
Show output
Your output will differ: the pid, the device number and the inode are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-log 152 root 3w REG 0,53 15 460592 /srv/tips/app.log
Ask about several files at once
lsof /srv/tips/app.log /srv/tips/queue.dat
Paths accumulate, and the answer is one table covering all of them. The mode letters differ here because one process is appending and the other only reading.
Show output
Your output will differ: the pids, the device number and the inodes are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-log 152 root 3w REG 0,53 15 460592 /srv/tips/app.log
tips-job 182 user 3r REG 0,53 4096 460590 /srv/tips/queue.dat
Check whether anything has a file open at all
lsof /etc/passwd; echo "exit $?"
Nothing is holding it, so lsof prints nothing and exits 1. A script can read that status rather than testing whether the output came back empty.
Show output
exit 1
Get just the process ids
lsof -t /srv/tips/app.log
-t drops the table and prints one pid per line, in the form kill and xargs want. It is the only output mode that suppresses the header.
Show output
Your output will differ: the pid is this machine's
152
Count how many processes hold a file
lsof -t /srv/tips/app.log | wc -l
A count is often the whole answer: one writer is a service doing its job, and several are a problem. Without -t the table prints a line per descriptor, so the count would be of descriptors rather than of processes.
Show output
1
Test a file before touching it in a script
for f in /srv/tips/queue.dat /etc/passwd; do
if lsof -t "$f" >/dev/null; then echo "$f: in use"; else echo "$f: free"; fi
done
The exit status carries the answer, so the output can go to /dev/null. Worth doing before a rotate, a truncate or a mv that would otherwise land on a file something is mid-write on.
Show output
/srv/tips/queue.dat: in use
/etc/passwd: free
Name the command without reading a table
lsof -F c /srv/tips/app.log | grep ^c
-F prints one field per line with a letter saying which field it is, so c is the command name. A pid line comes with it whether you asked or not, which is why the grep is there.
Show output
ctips-log
Narrowing, and the -a that is not optional
-c, -u, -p, -i and -d all select, and they combine with OR unless -a is present. Two selectors without -a widen the answer rather than narrowing it.
Select by command name
lsof -a -c tips-log -d 3
-c matches the start of the command name, and -d 3 limits the answer to descriptor 3. Both conditions apply because -a is there.
Show output
Your output will differ: the pid, the device number and the inode are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-log 152 root 3w REG 0,53 15 460592 /srv/tips/app.log
See two selectors combine as OR
lsof -c tips-log -u user | awk 'NR>1 {print $1}' | sort -u
The command asks for a process named tips-log and for files owned by user, and gets both sets: tips-job runs as user and is nothing to do with tips-log. Reading this as a two-part filter is how a lsof -c sshd -u root comes back with the whole machine in it.
Show output
tips-job
tips-log
Turn the same two selectors into AND
lsof -a -c tips-log -u user; echo "exit $?"
-a makes the conditions apply together, and no process called tips-log runs as user, so the answer is empty.
Show output
exit 1
List what one user has open
lsof -a -u user -d 3,4
-d takes a list, so 3,4 covers both of this process's data descriptors. Without -a the -d would apply to every process on the machine as well.
Show output
Your output will differ: the pid, the device number and the inodes are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-job 182 user 3r REG 0,53 4096 460590 /srv/tips/queue.dat
tips-job 182 user 4r REG 0,53 32 460591 /srv/tips/spool/job-0142.json
Exclude a user instead of selecting one
lsof -a -u ^root -d 3
A ^ in front of the value negates it. On a real machine -u ^root is the quickest way to see what the service accounts are doing without wading through everything root has open.
Show output
Your output will differ: the pid, the device number and the inode are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-job 182 user 3r REG 0,53 4096 460590 /srv/tips/queue.dat
Ask about two command names
lsof -a -c tips-log -c tips-api -d 3
Repeating a selector ORs it with itself, which is the behaviour you want here: either command, and descriptor 3. The -a applies between the different kinds of selector, not within one kind.
Show output
Your output will differ: the pids, the device numbers and the inodes are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-log 152 root 3w REG 0,53 15 460592 /srv/tips/app.log
tips-api 157 root 3u IPv4 2527152 0t0 TCP *:http-alt (LISTEN)
Exclude a command and search a tree
lsof -a -c ^tips-log -d 3,4 +D /srv/tips
Not tips-log, descriptor 3 or 4, and somewhere under /srv/tips, all applying together. Useful when one noisy service is drowning out whatever else is in a directory.
Show output
Your output will differ: the pid, the device number and the inodes are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-job 182 user 3r REG 0,53 4096 460590 /srv/tips/queue.dat
tips-job 182 user 4r REG 0,53 32 460591 /srv/tips/spool/job-0142.json
Match a prefix across several services
lsof -a -c tips- -d 3,4
-c matches from the beginning of the name rather than the whole of it, so one selector covers a family of processes named alike. The TYPE column changes with what is open: REG for a file, IPv4 for a socket.
Show output
Your output will differ: the pids, the device numbers, the inodes and the socket ids are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-log 152 root 3w REG 0,53 15 460592 /srv/tips/app.log
tips-api 157 root 3u IPv4 2527152 0t0 TCP *:http-alt (LISTEN)
tips-tmp 164 root 3w REG 0,55 5242880 2 /srv/cache/scratch.dat (deleted)
tips-arch 173 root 3w REG 0,55 1048576 3 /srv/cache/site.tar
tips-job 182 user 3r REG 0,53 4096 460590 /srv/tips/queue.dat
tips-job 182 user 4r REG 0,53 32 460591 /srv/tips/spool/job-0142.json
tips-clie 187 root 3u IPv4 2528819 0t0 TCP localhost:45400->localhost:http-alt (ESTABLISHED)
Look at everything one process has open
lsof -a -p "$(lsof -t -a -i :8080 -s TCP:LISTEN)" -d ^mem
Find the pid with one lsof, then hand it to -p in another. -d ^mem drops the shared libraries, which would otherwise be most of the answer.
Show output
Your output will differ: the pid, the device numbers, the inodes and the Python version are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-api 157 root cwd DIR 0,53 4096 460585 /srv/tips
tips-api 157 root rtd DIR 0,53 4096 460534 /
tips-api 157 root txt REG 0,53 6673736 442800 /usr/bin/python3.13
tips-api 157 root 0r CHR 1,3 0t0 61 /dev/null
tips-api 157 root 1w CHR 1,3 0t0 61 /dev/null
tips-api 157 root 2w CHR 1,3 0t0 61 /dev/null
tips-api 157 root 3u IPv4 2527152 0t0 TCP *:http-alt (LISTEN)
Reading the FD column
FD holds a descriptor number for most entries and a name for four of them. cwd is the process's working directory, rtd its root directory, txt the executable it is running, and mem a file mapped into its address space. A number arrives followed by r, w or u for read, write or both.
See the whole picture for one process
lsof -a -c tips-job -d ^mem
The three standard descriptors are there as 0, 1 and 2, pointing at /dev/null because nothing attached a terminal. cwd and txt answer where a process thinks it is and which binary it is really running.
Show output
Your output will differ: the pid, the device number, the inodes and the Python version are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-job 182 user cwd DIR 0,53 4096 460585 /srv/tips
tips-job 182 user rtd DIR 0,53 4096 460534 /
tips-job 182 user txt REG 0,53 6673736 442800 /usr/bin/python3.13
tips-job 182 user 0r CHR 1,3 0t0 61 /dev/null
tips-job 182 user 1w CHR 1,3 0t0 61 /dev/null
tips-job 182 user 2w CHR 1,3 0t0 61 /dev/null
tips-job 182 user 3r REG 0,53 4096 460590 /srv/tips/queue.dat
tips-job 182 user 4r REG 0,53 32 460591 /srv/tips/spool/job-0142.json
Find a process's working directory
lsof -a -c tips-log -d cwd
A service started from the wrong place writes its relative paths somewhere nobody is looking, and cwd is how you confirm it. TYPE is DIR rather than REG, since a directory is a file too.
Show output
Your output will differ: the pid, the device number and the inode are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-log 152 root cwd DIR 0,53 4096 460585 /srv/tips
Find the binary a process is running
lsof -a -c tips-log -d txt
txt is the executable, read from the kernel rather than from the command line, so it survives a process that was started through a wrapper or renamed itself. Here it is the interpreter, because the script has a #! line.
Show output
Your output will differ: the pid, the device number, the inode and the Python version are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-log 152 root txt REG 0,53 6673736 442800 /usr/bin/python3.13
Look at one of the mapped files
lsof -a -c tips-log -d mem | grep locale
mem entries are the shared libraries and data files the loader mapped in, and there are usually a dozen or more per process. A bare lsof scrolls mostly because of them, which is what -d ^mem removes.
Show output
Your output will differ: the pid, the device number, the inode and the size of the locale data are this machine's
tips-log 152 root mem REG 0,53 367708 188274 /usr/lib/locale/C.utf8/LC_CTYPE
See where stdin, stdout and stderr are pointing
lsof -a -c tips-log -d 0,1,2
Descriptors 0, 1 and 2 are the standard streams, and their modes read the way you would expect: 0r for input, 1w and 2w for output. A daemon whose output vanished is one whose 1 and 2 point at /dev/null.
Show output
Your output will differ: the pid and the device number are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-log 152 root 0r CHR 1,3 0t0 61 /dev/null
tips-log 152 root 1w CHR 1,3 0t0 61 /dev/null
tips-log 152 root 2w CHR 1,3 0t0 61 /dev/null
Tell a reader from a writer
lsof -a -d 3 +d /srv/tips
Same descriptor number on both processes, different mode letters. A file with two writers is worth investigating, whereas a file with one writer and several readers is a log.
Show output
Your output will differ: the pids, the device number and the inodes are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-log 152 root 3w REG 0,53 15 460592 /srv/tips/app.log
tips-job 182 user 3r REG 0,53 4096 460590 /srv/tips/queue.dat
Watch a long command name get cut off
lsof -a -c tips-arch -d cwd,3
The COMMAND column is nine characters wide, so tips-archiver arrives as tips-arch. The selector has to match the truncated form too, which is why -c tips-arch finds it at all.
Show output
Your output will differ: the pid, the device number and the inodes are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-arch 173 root cwd DIR 0,55 60 1 /srv/cache
tips-arch 173 root 3w REG 0,55 1048576 3 /srv/cache/site.tar
Print the full command name
lsof +c 0 -a -c tips-arch -d cwd,3
+c sets the column width and 0 means no limit. Worth having in any output you intend to read by name rather than by pid, since two services can easily share their first nine characters.
Show output
Your output will differ: the pid, the device number and the inodes are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-archiver 173 root cwd DIR 0,55 60 1 /srv/cache
tips-archiver 173 root 3w REG 0,55 1048576 3 /srv/cache/site.tar
A directory, and a filesystem
+d looks at one directory, +D descends the whole tree below it. Both take the path as the thing to search rather than the thing to report on, so they find files by where they live rather than by which process opened them.
List open files in one directory
lsof +d /srv/tips
spool/job-0142.json is open and missing from this answer, because +d stops at the top level. The cwd rows appear because a working directory is an open file like any other.
Show output
Your output will differ: the pids, the device number and the inodes are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-log 152 root cwd DIR 0,53 4096 460585 /srv/tips
tips-log 152 root 3w REG 0,53 15 460592 /srv/tips/app.log
tips-api 157 root cwd DIR 0,53 4096 460585 /srv/tips
tips-job 182 user cwd DIR 0,53 4096 460585 /srv/tips
tips-job 182 user 3r REG 0,53 4096 460590 /srv/tips/queue.dat
tips-clie 187 root cwd DIR 0,53 4096 460585 /srv/tips
Search a whole tree
lsof +D /srv/tips
The capital letter descends, and spool/job-0142.json now appears. +D walks the tree itself, so it costs more on a large one and needs permission to read every directory in it.
Show output
Your output will differ: the pids, the device number and the inodes are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-log 152 root cwd DIR 0,53 4096 460585 /srv/tips
tips-log 152 root 3w REG 0,53 15 460592 /srv/tips/app.log
tips-api 157 root cwd DIR 0,53 4096 460585 /srv/tips
tips-job 182 user cwd DIR 0,53 4096 460585 /srv/tips
tips-job 182 user 3r REG 0,53 4096 460590 /srv/tips/queue.dat
tips-job 182 user 4r REG 0,53 32 460591 /srv/tips/spool/job-0142.json
tips-clie 187 root cwd DIR 0,53 4096 460585 /srv/tips
Notice that the search stops at a mount point
lsof +D /srv
/srv/cache is a separate filesystem with two processes working in it, and none of them is here. The walk stops where the mount begins, so a +D / answers about one filesystem rather than about the machine.
Show output
Your output will differ: the pids, the device number and the inodes are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-log 152 root cwd DIR 0,53 4096 460585 /srv/tips
tips-log 152 root 3w REG 0,53 15 460592 /srv/tips/app.log
tips-api 157 root cwd DIR 0,53 4096 460585 /srv/tips
tips-job 182 user cwd DIR 0,53 4096 460585 /srv/tips
tips-job 182 user 3r REG 0,53 4096 460590 /srv/tips/queue.dat
tips-job 182 user 4r REG 0,53 32 460591 /srv/tips/spool/job-0142.json
tips-clie 187 root cwd DIR 0,53 4096 460585 /srv/tips
Let the search cross mount points
lsof -x f +D /srv
-x f permits the walk to follow mounts, and the two processes in /srv/cache appear. It has to come before +D on the command line, and -x l does the same for symbolic links.
Show output
Your output will differ: the pids, the device numbers and the inodes are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-log 152 root cwd DIR 0,53 4096 460585 /srv/tips
tips-log 152 root 3w REG 0,53 15 460592 /srv/tips/app.log
tips-api 157 root cwd DIR 0,53 4096 460585 /srv/tips
tips-tmp 164 root cwd DIR 0,55 60 1 /srv/cache
tips-arch 173 root cwd DIR 0,55 60 1 /srv/cache
tips-arch 173 root 3w REG 0,55 1048576 3 /srv/cache/site.tar
tips-job 182 user cwd DIR 0,53 4096 460585 /srv/tips
tips-job 182 user 3r REG 0,53 4096 460590 /srv/tips/queue.dat
tips-job 182 user 4r REG 0,53 32 460591 /srv/tips/spool/job-0142.json
tips-clie 187 root cwd DIR 0,53 4096 460585 /srv/tips
Ask about a mount point instead of a directory
lsof /srv/cache
A path on its own reports on the filesystem mounted there, rather than walking it, which is the form to use before an umount. It finds the deleted file that +D cannot, since that file no longer has a name to walk to.
Show output
Your output will differ: the pids, the device number and the inodes are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-tmp 164 root cwd DIR 0,55 60 1 /srv/cache
tips-tmp 164 root 3w REG 0,55 5242880 2 /srv/cache/scratch.dat (deleted)
tips-arch 173 root cwd DIR 0,55 60 1 /srv/cache
tips-arch 173 root 3w REG 0,55 1048576 3 /srv/cache/site.tar
Walk the same directory and lose a file
lsof +D /srv/cache
The same four rows minus one: scratch.dat was deleted while open, so nothing in the directory leads to it. Anything that answers by walking names will miss it.
Show output
Your output will differ: the pids, the device number and the inodes are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-tmp 164 root cwd DIR 0,55 60 1 /srv/cache
tips-arch 173 root cwd DIR 0,55 60 1 /srv/cache
tips-arch 173 root 3w REG 0,55 1048576 3 /srv/cache/site.tar
A deleted file that is still open
Deleting a file removes its name. The blocks stay allocated until the last process holding it closes it, so df keeps counting them while du and ls cannot see them. This is the usual reason a filesystem stays full after a large file has been removed.
See df and du disagree
df -h /srv/cache; du -sh /srv/cache
The filesystem reports six megabytes in use; walking the directory finds one. The five between them belong to a file with no name.
Show output
Filesystem Size Used Avail Use% Mounted on
tmpfs 20M 6.0M 14M 30% /srv/cache
1.0M /srv/cache
Confirm there is nothing there to delete
ls /srv/cache
site.tar accounts for the megabyte du reported, and nothing you can remove will recover the other five. This is the point at which most people start restarting services at random.
Show output
site.tar
Find every deleted file still held open
lsof -nP +L1
+L1 selects files whose link count is below 1, which is exactly what an unlinked-but-open file is. Run without a path it covers the whole machine, and on a server that is usually a short list worth reading in full.
Show output
Your output will differ: the pid, the device number and the inode are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
tips-tmp 164 root 3w REG 0,55 5242880 0 2 /srv/cache/scratch.dat (deleted)
Show the link count alongside the files in one filesystem
lsof +L1 /srv/cache
With a path, +L1 adds the NLINK column to the whole answer rather than filtering by it. 0 marks the deleted file, 1 an ordinary one and 2 a directory, counting its own entry and its child's ...
Show output
Your output will differ: the pids, the device number and the inodes are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
tips-tmp 164 root cwd DIR 0,55 60 2 1 /srv/cache
tips-tmp 164 root 3w REG 0,55 5242880 0 2 /srv/cache/scratch.dat (deleted)
tips-arch 173 root cwd DIR 0,55 60 2 1 /srv/cache
tips-arch 173 root 3w REG 0,55 1048576 1 3 /srv/cache/site.tar
Recover the space by ending the process holding it
lsof -t -a +L1 /srv/cache | xargs -r kill
sleep 1
df -h /srv/cache | tail -1
The kernel frees the blocks when the last descriptor closes, so the space comes back when the process ends: 6.0M down to 1.0M with nothing deleted. Check what you are about to kill first, because a service that has a scratch file open is a service in the middle of something.
Show output
tmpfs 20M 1.0M 19M 5% /srv/cache
Sockets and ports
-i selects network files. A socket has no path, so the NAME column holds an address instead, and TYPE reads IPv4 or IPv6 rather than REG. Addresses are resolved to names unless -n and -P say otherwise.
Find out what is using a port
lsof -i :8080
The process listening, and the one connected to it. This is the answer to the message about an address already being in use, and worth running before restarting anything.
Show output
Your output will differ: the pids, the socket ids and the client's port are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-api 157 root 3u IPv4 2527152 0t0 TCP *:http-alt (LISTEN)
tips-clie 187 root 3u IPv4 2528819 0t0 TCP localhost:45400->localhost:http-alt (ESTABLISHED)
Stop lsof translating ports and addresses
lsof -nP -i :8080
The same two sockets with the lookups turned off: -P leaves ports as numbers and -n leaves addresses as addresses. 8080 printing as http-alt comes from /etc/services, and it makes the output harder to match against the port you were asked about.
Show output
Your output will differ: the pids and the socket ids are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-api 157 root 3u IPv4 2531449 0t0 TCP *:8080 (LISTEN)
tips-clie 187 root 3u IPv4 2527150 0t0 TCP 127.0.0.1:45400->127.0.0.1:8080 (ESTABLISHED)
List every network connection on the machine
lsof -nP -i
-i with nothing after it takes the lot. On a working server this is where you find the connection nobody remembered opening.
Show output
Your output will differ: the pids and the socket ids are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-api 157 root 3u IPv4 2531449 0t0 TCP *:8080 (LISTEN)
tips-clie 187 root 3u IPv4 2527150 0t0 TCP 127.0.0.1:45400->127.0.0.1:8080 (ESTABLISHED)
Show only what is listening
lsof -nP -i TCP -s TCP:LISTEN
-s filters on the protocol state, and TCP:LISTEN is the one worth knowing by heart: one line per port this machine will accept a connection on.
Show output
Your output will differ: the pid and the socket id are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-api 157 root 3u IPv4 2531449 0t0 TCP *:8080 (LISTEN)
Show only the established connections
lsof -nP -i TCP -s TCP:ESTABLISHED
TCP:ESTABLISHED asks the question a restart depends on, since these are the clients that will notice one.
Show output
Your output will differ: the pid and the socket id are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-clie 187 root 3u IPv4 2527150 0t0 TCP 127.0.0.1:45400->127.0.0.1:8080 (ESTABLISHED)
Select by address as well as port
lsof -nP -i@127.0.0.1
-i@host narrows to one address, with -i@host:port narrowing further. The listener bound to * is absent because it is not bound to that address, which is the distinction between a service anyone can reach and one only this machine can.
Show output
Your output will differ: the pid and the socket id are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-clie 187 root 3u IPv4 2528819 0t0 TCP 127.0.0.1:45400->127.0.0.1:8080 (ESTABLISHED)
Check whether a port is free
lsof -i :22; echo "exit $?"
Nothing is bound to 22 here, so there is no output and the exit status is 1. A script that wants to pick a free port can loop on this rather than binding and catching the failure.
Show output
exit 1
Count what is attached to a port
lsof -t -i :8080 | wc -l
The listener plus one client. A count like this is what a health check wants, since the number rather than the names says whether anything is connected.
Show output
2
Name the listener without its clients
lsof -F c -a -i :8080 -s TCP:LISTEN | grep ^c
-s TCP:LISTEN drops the clients, leaving the service, and -F c then prints its command name with nothing around it. Swap the -F c for -t when you want the pid instead.
Show output
ctips-api
In a script
-t and -F are the two output modes meant for other programs. The exit status is the third, and 1 means nothing matched rather than something went wrong.
Kill whatever is holding a file
lsof -t /srv/cache/site.tar | xargs -r kill
sleep 1
lsof /srv/cache/site.tar; echo "exit $?"
-r on xargs stops an empty list turning into a bare kill, which would report a usage error rather than doing nothing. Read the list before piping it anywhere: this one ends a process that is mid-write, and a kill -9 in the same position would not let it finish.
Show output
exit 1
Print named fields instead of a table
lsof -F pcn -a -c tips-log -d 3
Each field arrives on its own line, prefixed by a letter naming it: p for pid, c for command, n for name. No column widths, no truncation, and nothing to split on.
Show output
Your output will differ: the pid is this machine's
p152
ctips-log
n/srv/tips/app.log
Ask for one field and get two
lsof -F n -a -c tips-log -d 3
The pid line comes back whether it was asked for or not, since it tells a parser which process the lines after it belong to. Filter it out with grep '^n' rather than assuming a fixed line count.
Show output
Your output will differ: the pid is this machine's
p152
n/srv/tips/app.log
Repeat the search on a timer
lsof -r1 /srv/tips/app.log 2>/dev/null | head -6
-r1 re-runs the search every second and separates each pass with a line of equals signs, which is the closest lsof comes to watching something. Without the head it runs until interrupted.
Show output
Your output will differ: the pid, the device number and the inode are this machine's
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-log 443 root 3w REG 0,53 15 460592 /srv/tips/app.log
=======
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tips-log 443 root 3w REG 0,53 15 460592 /srv/tips/app.log
=======