Test if remote TCP port is open from a shell script

ShellTcpPortSolarisTelnet

Shell Problem Overview


I'm looking for a quick and simple method for properly testing if a given TCP port is open on a remote server, from inside a Shell script.

I've managed to do it with the telnet command, and it works fine when the port is opened, but it doesn't seem to timeout when it's not and just hangs there...

Here's a sample:

l_TELNET=`echo "quit" | telnet $SERVER $PORT | grep "Escape character is"`
if [ "$?" -ne 0 ]; then
  echo "Connection to $SERVER on port $PORT failed"
  exit 1
else
  echo "Connection to $SERVER on port $PORT succeeded"
  exit 0
fi

I either need a better way, or a way to force telnet to timeout if it doesn't connect in under 8 seconds for example, and return something I can catch in Shell (return code, or string in stdout).

I know of the Perl method, which uses the IO::Socket::INET module and wrote a successful script that tests a port, but would rather like to avoid using Perl if possible.

Note: This is what my server is running (where I need to run this from)

SunOS 5.10 Generic_139556-08 i86pc i386 i86pc

Shell Solutions


Solution 1 - Shell

As pointed by B. Rhodes, nc (netcat) will do the job. A more compact way to use it:

nc -z <host> <port>

That way nc will only check if the port is open, exiting with 0 on success, 1 on failure.

For a quick interactive check (with a 5 seconds timeout):

nc -z -v -w5 <host> <port>

Solution 2 - Shell

It's easy enough to do with the -z and -w TIMEOUT options to nc, but not all systems have nc installed. If you have a recent enough version of bash, this will work:

# Connection successful:
$ timeout 1 bash -c 'cat < /dev/null > /dev/tcp/google.com/80'
$ echo $?
0

# Connection failure prior to the timeout
$ timeout 1 bash -c 'cat < /dev/null > /dev/tcp/sfsfdfdff.com/80'
bash: sfsfdfdff.com: Name or service not known
bash: /dev/tcp/sfsfdfdff.com/80: Invalid argument
$ echo $?
1

# Connection not established by the timeout
$ timeout 1 bash -c 'cat < /dev/null > /dev/tcp/google.com/81'
$ echo $?
124

What's happening here is that timeout will run the subcommand and kill it if it doesn't exit within the specified timeout (1 second in the above example). In this case bash is the subcommand and uses its special /dev/tcp handling to try and open a connection to the server and port specified. If bash can open the connection within the timeout, cat will just close it immediately (since it's reading from /dev/null) and exit with a status code of 0 which will propagate through bash and then timeout. If bash gets a connection failure prior to the specified timeout, then bash will exit with an exit code of 1 which timeout will also return. And if bash isn't able to establish a connection and the specified timeout expires, then timeout will kill bash and exit with a status of 124.

Solution 3 - Shell

TOC:

  • Using bash and timeout
    • Command
    • Examples
  • Using nc
    • Command
    • RHEL 6 (nc-1.84)
      • Installation
      • Examples
    • RHEL 7 (nmap-ncat-6.40)
      • Installation
      • Examples
  • Remarks

Using bash and timeout:

Note that timeout should be present with RHEL 6+, or is alternatively found in GNU coreutils 8.22. On MacOS, install it using brew install coreutils and use it as gtimeout.

Command:

$ timeout $TIMEOUT_SECONDS bash -c "</dev/tcp/${HOST}/${PORT}"; echo $?

If parametrizing the host and port, be sure to specify them as ${HOST} and ${PORT} as is above. Do not specify them merely as $HOST and $PORT, i.e. without the braces; it won't work in this case.

Example:

Success:
$ timeout 2 bash -c "</dev/tcp/canyouseeme.org/80"; echo $?
0
Failure:
$ timeout 2 bash -c "</dev/tcp/canyouseeme.org/81"; echo $?
124

If you must preserve the exit status of bash,

$ timeout --preserve-status 2 bash -c "</dev/tcp/canyouseeme.org/81"; echo $?
143

Using nc:

Note that a backward incompatible version of nc gets installed on RHEL 7.

Command:

Note that the command below is unique in that it is identical for both RHEL 6 and 7. It's just the installation and output that are different.

$ nc -w $TIMEOUT_SECONDS -v $HOST $PORT </dev/null; echo $?

RHEL 6 (nc-1.84):

Installation:
$ sudo yum install nc
Examples:
Success:
$ nc -w 2 -v canyouseeme.org 80 </dev/null; echo $?
Connection to canyouseeme.org 80 port [tcp/http] succeeded!
0
Failure:
$ nc -w 2 -v canyouseeme.org 81 </dev/null; echo $?
nc: connect to canyouseeme.org port 81 (tcp) timed out: Operation now in progress
1

If the hostname maps to multiple IPs, the above failing command will cycle through many or all of them. For example:

$ nc -w 2 -v microsoft.com 81 </dev/null; echo $?
nc: connect to microsoft.com port 81 (tcp) timed out: Operation now in progress
nc: connect to microsoft.com port 81 (tcp) timed out: Operation now in progress
nc: connect to microsoft.com port 81 (tcp) timed out: Operation now in progress
nc: connect to microsoft.com port 81 (tcp) timed out: Operation now in progress
nc: connect to microsoft.com port 81 (tcp) timed out: Operation now in progress
1

RHEL 7 (nmap-ncat-6.40):

Installation:
$ sudo yum install nmap-ncat
Examples:
Success:
$ nc -w 2 -v canyouseeme.org 80 </dev/null; echo $?
Ncat: Version 6.40 ( http://nmap.org/ncat )
Ncat: Connected to 52.202.215.126:80.
Ncat: 0 bytes sent, 0 bytes received in 0.22 seconds.
0
Failure:
$ nc -w 2 -v canyouseeme.org 81 </dev/null; echo $?
Ncat: Version 6.40 ( http://nmap.org/ncat )
Ncat: Connection timed out.
1

If the hostname maps to multiple IPs, the above failing command will cycle through many or all of them. For example:

$ nc -w 2 -v microsoft.com 81 </dev/null; echo $?
Ncat: Version 6.40 ( http://nmap.org/ncat )
Ncat: Connection to 104.43.195.251 failed: Connection timed out.
Ncat: Trying next address...
Ncat: Connection to 23.100.122.175 failed: Connection timed out.
Ncat: Trying next address...
Ncat: Connection to 23.96.52.53 failed: Connection timed out.
Ncat: Trying next address...
Ncat: Connection to 191.239.213.197 failed: Connection timed out.
Ncat: Trying next address...
Ncat: Connection timed out.
1

Remarks:

The -v (--verbose) argument and the echo $? command are of course for illustration only.

Solution 4 - Shell

With netcat you can check whether a port is open like this:

nc my.example.com 80 < /dev/null

The return value of nc will be success if the TCP port was opened, and failure (typically the return code 1) if it could not make the TCP connection.

Some versions of nc will hang when you try this, because they do not close the sending half of their socket even after receiving the end-of-file from /dev/null. On my own Ubuntu laptop (18.04), the netcat-openbsd version of netcat that I have installed offers a workaround: the -N option is necessary to get an immediate result:

nc -N my.example.com 80 < /dev/null

Solution 5 - Shell

In Bash using pseudo-device files for TCP/UDP connections is straight forward. Here is the script:

#!/usr/bin/env bash
SERVER=example.com
PORT=80
</dev/tcp/$SERVER/$PORT
if [ "$?" -ne 0 ]; then
  echo "Connection to $SERVER on port $PORT failed"
  exit 1
else
  echo "Connection to $SERVER on port $PORT succeeded"
  exit 0
fi

Testing:

$ ./test.sh 
Connection to example.com on port 80 succeeded

Here is one-liner (Bash syntax):

</dev/tcp/localhost/11211 && echo Port open. || echo Port closed.

Note that some servers can be firewall protected from SYN flood attacks, so you may experience a TCP connection timeout (~75secs). To workaround the timeout issue, try:

timeout 1 bash -c "</dev/tcp/stackoverflow.com/81" && echo Port open. || echo Port closed.

See: <https://stackoverflow.com/q/24317341/55075>

Solution 6 - Shell

I needed a more flexible solution for working on multiple git repositories so I wrote the following sh code based on 1 and 2. You can use your server address instead of gitlab.com and your port in replace of 22.

SERVER=gitlab.com
PORT=22
nc -z -v -w5 $SERVER $PORT
result1=$?

#Do whatever you want

if [  "$result1" != 0 ]; then
  echo  'port 22 is closed'
else
  echo 'port 22 is open'
fi

Solution 7 - Shell

check ports using bash

Example

$ ./test_port_bash.sh 192.168.7.7 22

the port 22 is open

Code

HOST=$1
PORT=$2
exec 3> /dev/tcp/${HOST}/${PORT}
if [ $? -eq 0 ];then echo "the port $2 is open";else echo "the port $2 is closed";fi

Solution 8 - Shell

If you're using ksh or bash they both support IO redirection to/from a socket using the /dev/tcp/IP/PORT construct. In this Korn shell example I am redirecting no-op's (:) std-in from a socket:

W$ python -m SimpleHTTPServer &
[1]     16833
Serving HTTP on 0.0.0.0 port 8000 ...
W$ : </dev/tcp/127.0.0.1/8000

The shell prints an error if the socket is not open:

W$ : </dev/tcp/127.0.0.1/8001
ksh: /dev/tcp/127.0.0.1/8001: cannot open [Connection refused]

You can therefore use this as the test in an if condition:

SERVER=127.0.0.1 PORT=8000
if (: < /dev/tcp/$SERVER/$PORT) 2>/dev/null
then
    print succeeded
else
    print failed
fi

The no-op is in a subshell so I can throw std-err away if the std-in redirection fails.

I often use /dev/tcp for checking the availability of a resource over HTTP:

W$ print arghhh > grr.html
W$ python -m SimpleHTTPServer &
[1]     16863
Serving HTTP on 0.0.0.0 port 8000 ...
W$ (print -u9 'GET /grr.html HTTP/1.0\n';cat <&9) 9<>/dev/tcp/127.0.0.1/8000
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/2.6.1
Date: Thu, 14 Feb 2013 12:56:29 GMT
Content-type: text/html
Content-Length: 7
Last-Modified: Thu, 14 Feb 2013 12:55:44 GMT

arghhh
W$ 

This one-liner opens file descriptor 9 for reading from and writing to the socket, prints the HTTP GET to the socket and uses cat to read from the socket.

Solution 9 - Shell

While an old question, I've just dealt with a variant of it, but none of the solutions here were applicable, so I found another, and am adding it for posterity. Yes, I know the OP said they were aware of this option and it didn't suit them, but for anyone following afterwards it might prove useful.

In my case, I want to test for the availability of a local apt-cacher-ng service from a docker build. That means absolutely nothing can be installed prior to the test. No nc, nmap, expect, telnet or python. perl however is present, along with the core libraries, so I used this:

perl -MIO::Socket::INET -e 'exit(! defined( IO::Socket::INET->new("172.17.42.1:3142")))'

Solution 10 - Shell

In some cases where tools like curl, telnet, nc o nmap are unavailable you still have a chance with wget

if [[ $(wget -q -t 1 --spider --dns-timeout 3 --connect-timeout 10  host:port; echo $?) -eq 0 ]]; then echo "OK"; else echo "FAIL"; fi

Solution 11 - Shell

If you want to use nc but don't have a version that support -z, try using --send-only:

nc --send-only <IP> <PORT> </dev/null

and with timeout:

nc -w 1 --send-only <IP> <PORT> </dev/null

and without DNS lookup if it's an IP:

nc -n -w 1 --send-only <IP> <PORT> </dev/null

It returns the codes as the -z based on if it can connect or not.

Solution 12 - Shell

Building on the most highly voted answer, here is a function to wait for two ports to be open, with a timeout as well. Note the two ports that mus be open, 8890 and 1111, as well as the max_attempts (1 per second).

function wait_for_server_to_boot()
{
    echo "Waiting for server to boot up..."
    attempts=0
    max_attempts=30
    while ( nc 127.0.0.1 8890 < /dev/null || nc 127.0.0.1 1111 < /dev/null )  && [[ $attempts < $max_attempts ]] ; do
        attempts=$((attempts+1))
        sleep 1;
        echo "waiting... (${attempts}/${max_attempts})"
    done
}

Solution 13 - Shell

I'm guessing that it's too late for an answer, and this might not be a good one, but here you go...

What about putting it inside of a while loop with a timer on it of some sort. I'm more of a Perl guy than Solaris, but depending on the shell you're using, you should be able to do something like:

TIME = 'date +%s' + 15
while TIME != `date +%s'
do whatever

And then just add a flag in the while loop, so that if it times out before completing, you can cite the timeout as reason for failure.

I suspect that the telnet has a timeout switch as well, but just off the top of my head, I think the above will work.

Solution 14 - Shell

I needed short script which was run in cron and hasn't output. I solve my trouble using nmap

open=`nmap -p $PORT $SERVER | grep "$PORT" | grep open`
if [ -z "$open" ]; then
  echo "Connection to $SERVER on port $PORT failed"
  exit 1
else
  echo "Connection to $SERVER on port $PORT succeeded"
  exit 0
fi

To run it You should install nmap because it is not default installed package.

Solution 15 - Shell

This uses telnet behind the scenes, and seems to work fine on mac/linux. It doesn't use netcat because of the differences between the versions on linux/mac, and this works with a default mac install.

###Example:

$ is_port_open.sh 80 google.com
OPEN

$ is_port_open.sh 8080 google.com
CLOSED

###is_port_open.sh PORT=$1 HOST=$2 TIMEOUT_IN_SEC=${3:-1} VALUE_IF_OPEN=${4:-"OPEN"} VALUE_IF_CLOSED=${5:-"CLOSED"}

function eztern()
{
  if [ "$1" == "$2" ]
  then
    echo $3
  else
    echo $4
  fi
}

# cross platform timeout util to support mac mostly
# https://gist.github.com/jaytaylor/6527607
function eztimeout() { perl -e 'alarm shift; exec @ARGV' "$@"; }

function testPort()
{
  OPTS=""

  # find out if port is open using telnet
  # by saving telnet output to temporary file
  # and looking for "Escape character" response
  # from telnet
  FILENAME="/tmp/__port_check_$(uuidgen)"
  RESULT=$(eztimeout $TIMEOUT_IN_SEC telnet $HOST $PORT &> $FILENAME; cat $FILENAME | tail -n1)
  rm -f $FILENAME;
  SUCCESS=$(eztern "$RESULT" "Escape character is '^]'." "$VALUE_IF_OPEN" "$VALUE_IF_CLOSED")

  echo "$SUCCESS"
}

testPort 

Solution 16 - Shell

nmap-ncat to test for local port that is not already in use


availabletobindon() {
  port="$1"
  nc -w 2 -i 1 localhost "$port" 2>&1 | grep -v -q 'Idle timeout expired'
  return "$?"
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionYanick GirouardView Question on Stackoverflow
Solution 1 - ShellAlessio GaetaView Answer on Stackoverflow
Solution 2 - ShellonlynoneView Answer on Stackoverflow
Solution 3 - ShellAsclepiusView Answer on Stackoverflow
Solution 4 - ShellBrandon RhodesView Answer on Stackoverflow
Solution 5 - ShellkenorbView Answer on Stackoverflow
Solution 6 - ShellAhmad YoosofanView Answer on Stackoverflow
Solution 7 - ShellkrbuView Answer on Stackoverflow
Solution 8 - ShellmerebagatelleView Answer on Stackoverflow
Solution 9 - Shellmc0eView Answer on Stackoverflow
Solution 10 - ShellPRFView Answer on Stackoverflow
Solution 11 - ShellChris MendezView Answer on Stackoverflow
Solution 12 - ShellJoão Rocha da SilvaView Answer on Stackoverflow
Solution 13 - ShellRed ThomasView Answer on Stackoverflow
Solution 14 - ShellKrystian KulaszaView Answer on Stackoverflow
Solution 15 - ShellBrad ParksView Answer on Stackoverflow
Solution 16 - ShellisleofgrayView Answer on Stackoverflow