Check if a TCP Port is Listening in Bash

By | 2021-10-14

If you are writing a startup or monitoring script, you may want a way to check if a TCP port is listening in bash. You can actually do it without calling any external programs, such as ss or netstat.

I have only done this on Linux, so if you are using another UNIX like operating system, such as FreeBSD or OSX, this may not work. I would not recommend trying this with UDP without extensive testing, as it is a connectionless protocol and will probably always show that the port is listening. When I did some limited testing with UDP on my Debian Bullseye machine, UDP always worked. That is probably not what you want.

In the REDIRECTION section of the man page, there is a section describing filenames that are treated specially. Take note of this section:

  /dev/tcp/host/port
    If host is a valid hostname or Internet address, and port is
    an integer port number or service name, bash attempts to open
    the corresponding TCP socket.

This means you can use bash to read from or write to a network connection using standard redirect operators (E.g. <, >). In order to specify the address and port, just replace host/port with the applicable combination. The host portion can be any valid network address or resolvable host name. In the examples, I will use various types including IPv4, IPv6, DNS name, and a name defined in /etc/hosts.

In our case, we just want to check if a TCP port is listening. All you need is the > redirect operator. For example:

$ > /dev/tcp/localhost/24
bash: connect: Connection refused
bash: /dev/tcp/localhost/24: Connection refused
$ echo $?
1
$ > /dev/tcp/::1/22
$ echo $?
0 

Notice how the return code of the command was 1 when it couldn’t connect 0 and when it was successful? You can place the command directly in a conditional statement:

$ if > /dev/tcp/tylersguides.com/1234; then echo success; else echo fail; fi
bash: connect: Connection refused
bash: /dev/tcp/tylersguides.com/1234: Connection refused
fail
$ if > /dev/tcp/tylersguides.com/443 2> /dev/null; then echo success; else echo fail; fi
success

If you wish to suppress the error output on failure, place 2> /dev/null at the beginning to redirect stderr to /dev/null.

$ if 2> /dev/null > /dev/tcp/tylersguides.com/1234 
then
  echo success
else
  echo fail
fi
fail

With processes that take a long time to start, such as Java applications running in an application server, you may wish to monitor the port with a while loop.

Suppose you are writing a startup script that starts a process that listens on port 7001 and you want it to give up after 5 minutes. You could use the following:

let -i t=0
let -i timeout=300
while (! 2> /dev/null > /dev/tcp/::1/7001 ) || [[ $t -lt $timeout ]]
do
  sleep 1
  t=$(($t + 1))
done
if [[ $t -ge $timeout ]]
then
  echo timeout
  exit 1
fi

If you aren’t familiar with bash scripting, let -i means a variable will be storing an integer instead of a string. The -lt and -ge are synonymous with the math operators < and >=, respectively. I.e. they compare numbers. Bash has the comparison operators < and >, but they perform lexicographic comparison rather than arithmetic comparison. Bash does not have <=, or >=. You will get a syntax error if you try them.

References