In Progress
Unit 1, Lesson 21
In Progress

lsof; or: what process is using my port?!

Have you ever gone to start a local development server, only to have it complain that the port is already taken? In this episode you’ll learn about how to use lsof utility to track down exactly which process is hogging a port.

Video transcript & code

lsof

So here we are, all ready to write some code, we’re starting up our local app server, da de da…

rails s

…and wait, what the heck?

# rails s
=> Booting WEBrick
=> Rails 6.0.2.2 application starting in development http://localhost:3000
=> Run `rails server --help` for more startup options
/usr/local/bundle/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/stack.rb:37: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/usr/local/bundle/gems/actionpack-6.0.2.2/lib/action_dispatch/middleware/static.rb:110: warning: The called method `initialize' is defined here
[2020-04-17 18:49:41] INFO  WEBrick 1.6.0
[2020-04-17 18:49:41] INFO  ruby 2.7.0 (2019-12-25) [x86_64-linux]
Exiting
Traceback (most recent call last):
        24: from bin/rails:4:in `<main>'
        23: from bin/rails:4:in `require'
        22: from /usr/local/bundle/gems/railties-6.0.2.2/lib/rails/commands.rb:18:in `<top (required)>'
        21: from /usr/local/bundle/gems/railties-6.0.2.2/lib/rails/command.rb:46:in `invoke'
        20: from /usr/local/bundle/gems/railties-6.0.2.2/lib/rails/command/base.rb:69:in `perform'
        19: from /usr/local/bundle/gems/thor-1.0.1/lib/thor.rb:392:in `dispatch'
        18: from /usr/local/bundle/gems/thor-1.0.1/lib/thor/invocation.rb:127:in `invoke_command'
        17: from /usr/local/bundle/gems/thor-1.0.1/lib/thor/command.rb:27:in `run'
        16: from /usr/local/bundle/gems/railties-6.0.2.2/lib/rails/commands/server/server_command.rb:138:in `perform'
        15: from /usr/local/bundle/gems/railties-6.0.2.2/lib/rails/commands/server/server_command.rb:138:in `tap'
        14: from /usr/local/bundle/gems/railties-6.0.2.2/lib/rails/commands/server/server_command.rb:147:in `block in perform'
        13: from /usr/local/bundle/gems/railties-6.0.2.2/lib/rails/commands/server/server_command.rb:39:in `start'
        12: from /usr/local/bundle/gems/rack-2.2.2/lib/rack/server.rb:327:in `start'
        11: from /usr/local/bundle/gems/rack-2.2.2/lib/rack/handler/webrick.rb:38:in `run'
        10: from /usr/local/bundle/gems/rack-2.2.2/lib/rack/handler/webrick.rb:38:in `new'
         9: from /usr/local/lib/ruby/2.7.0/webrick/httpserver.rb:47:in `initialize'
         8: from /usr/local/lib/ruby/2.7.0/webrick/server.rb:108:in `initialize'
         7: from /usr/local/lib/ruby/2.7.0/webrick/server.rb:127:in `listen'
         6: from /usr/local/lib/ruby/2.7.0/webrick/utils.rb:65:in `create_listeners'
         5: from /usr/local/lib/ruby/2.7.0/socket.rb:763:in `tcp_server_sockets'
         4: from /usr/local/lib/ruby/2.7.0/socket.rb:227:in `foreach'
         3: from /usr/local/lib/ruby/2.7.0/socket.rb:227:in `each'
         2: from /usr/local/lib/ruby/2.7.0/socket.rb:765:in `block in tcp_server_sockets'
         1: from /usr/local/lib/ruby/2.7.0/socket.rb:201:in `listen'
/usr/local/lib/ruby/2.7.0/socket.rb:201:in `bind': Cannot assign requested address - bind(2) for [::1]:3000 (Errno::EADDRNOTAVAIL)

What is all this?!!

If we take a closer look at the error output, we see that the problem is that the dev server is trying to bind to port 3000… but apparently that port is already in use.

1: from /usr/local/lib/ruby/2.7.0/socket.rb:201:in `listen'
/usr/local/lib/ruby/2.7.0/socket.rb:201:in `bind': Cannot assign requested address - bind(2) for [::1]:3000 (Errno::EADDRNOTAVAIL)

OK, what do we do now? We need to track down the offending process, and terminate it. But how?

I’ve seen and used a few different techniques for this.

Some people just do a ps aux and poke through output looking for possible culprits.

# ps aux
...

We can sometimes find the problem process this way, but it’s tedious. What if we could just ask the operating system which process is using port 3000?

As it turns out we can.

But first we need to install a utility that might not already be on our system.

This is an Debian-based linux machine, so I’m using apt to install it.

$ sudo apt install lsof

The lsof utility’s name is short for “LiSt Open Files”.

Let’s try it out!

lsof
...lots of output...

Look at all those open files!

OK, but what does this have to do with finding out which program is using a port??

Well, as it turns out, lsof uses a rather loose definition of “file”. Because it’s written for UNIX-like operating systems where in theory “everything is a file”, lsof also considers I/O resources such as networking sockets to be files.

However, we don’t want to search through the entire output of lsof any more than we want to search through the output of ps.

Instead, let’s ask lsof specifically about internet sockets, using the -i flag.

# lsof -i
COMMAND     PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
node        111 root   18u  IPv6  24664      0t0  TCP *:43951 (LISTEN)
node        111 root   19u  IPv6  24332      0t0  TCP localhost:43951->localhost:60576 (ESTABLISHED)
node        139 root   18u  IPv4  24331      0t0  TCP localhost:60576->localhost:43951 (ESTABLISHED)
node        154 root   18u  IPv4  24840      0t0  TCP localhost:60578->localhost:43951 (ESTABLISHED)
node        186 root   19u  IPv6  24338      0t0  TCP localhost:43951->localhost:60578 (ESTABLISHED)
http-serv 22752 root   20u  IPv4  61898      0t0  TCP *:3000 (LISTEN)

This is a much more manageable list, and we can quickly scan it for the process that’s listening on port 3000.

But there’s an even more precise way to use lsof to find the process responsible for holding onto a port.

Since we know the port number we care about,

we can tell lsof that’s the only one we’re interested in, by putting :3000 after the -i flag.

# lsof -i :3000
COMMAND     PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
http-serv 22752 root   20u  IPv4  61898      0t0  TCP *:3000 (LISTEN)

And there’s our suspect: a process called http-serv.

…or, is it???

One thing to be mindful of when using lsof is that by default it truncates longer command names.

We can tell it to show the whole command name with the rather opaquely-named +c0 flag.

# lsof -i :3000 +c0
COMMAND       PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
http-server 22752 root   20u  IPv4  61898      0t0  TCP *:3000 (LISTEN)

If you’re wondering why it’s +c0… well, for some flags lsof differentiates between + flags to turn an option on, and - flags to turn it off. The c options is short for “command”. The flag sets the maximum characters to show, and the special value for putting no limit on it is… zero. Because obviously.

OK, now that we know the full name and process ID of the process that’s getting in our way, we can terminate it with a command like kill or, in this case, pkill.

# pkill http-server

And when we check again… yep, no more process holding onto port 3000.

# lsof -i :3000 +c0

And sure enough, now we can start our development server!

# rails s
=> Booting WEBrick
=> Rails 6.0.2.2 application starting in development http://localhost:3000
=> Run `rails server --help` for more startup options
[2020-04-17 19:49:50] INFO  WEBrick 1.6.0
[2020-04-17 19:49:50] INFO  ruby 2.7.0 (2019-12-25) [x86_64-linux]
[2020-04-17 19:49:50] INFO  WEBrick::HTTPServer#start: pid=40515 port=3000

So there you go: next time you have a server complain that it can’t claim the port it wants, you can use lsof to track down the process currently listening on that port. Happy hacking!

Responses