Test if remote TCP port is open from a shell script

555,452

Solution 1

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

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

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

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

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: How to decrease TCP connect() system call timeout?

Share:
555,452
Yanick Girouard
Author by

Yanick Girouard

Updated on September 15, 2020

Comments

  • Yanick Girouard
    Yanick Girouard over 3 years

    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

  • Peter
    Peter about 10 years
    Is /dev/tcp available on systems other than Linux? What about Macs in particular?
  • onlynone
    onlynone about 10 years
    The /dev/tcp thing is a feature of bash, so yes. However it looks like macs don't have timeout...
  • jolestar
    jolestar about 9 years
    centos7 default use nmap netcat and has not -z option.
  • David Dossot
    David Dossot over 8 years
    Works great for flavours of nc that don't support the -w flag.
  • Andrew
    Andrew about 8 years
    thanks - also works for versions of nc without -z support - works on RHEL/CentOS 7, for example
  • Graham
    Graham about 8 years
    @onlynone - Looks like timeout also doesn't exist in FreeBSD, and on my old Ubuntu box it's an installable package, but isn't there by default. Would be great if there was a way to do this in bash alone, without third party tools like timeout or netcat.
  • onlynone
    onlynone about 8 years
    Just wanted to mention that timeout appears to be part of GNU coreutils, and can be installed on macs with homebrew: brew install coreutils. It'll then be available as gtimeout.
  • totoro
    totoro about 8 years
    It doesn't tell me that my port 80 is open.
  • Wildcard
    Wildcard about 8 years
    Hey, this is great. What version of bash was this introduced in, does anyone know? (How could I find out?)
  • Justin Dennahower
    Justin Dennahower almost 8 years
    This doesn't work in RHEL/Centos. For those distros you need to: nc -vn <host> <port>
  • Asclepius
    Asclepius over 7 years
    FWIW, I have completely overhauled my answer with an example, separately applicable to both RHEL 6 and RHEL 7.
  • glerYbo
    glerYbo over 7 years
    @A-B-B This is related to firewall configuration of the server which doesn't give any response to prevent any SYN flood attacks. Running telnet canyouseeme.org 81 also hangs. This is controlled by your timeout limits which probably are hardcoded in bash. See: decrease TCP connect() system call timeout.
  • Asclepius
    Asclepius over 7 years
    Although this approach is good, -w is necessary, failing which the command when used in a script can hang for over ten seconds. I have written an answer which includes -w.
  • Wildcard
    Wildcard over 7 years
    @A-B-B, thanks for the info, but I was talking about the special /dev/tcp handling, and wondering when that was introduced to bash. :)
  • Asclepius
    Asclepius over 7 years
    For parametrization, it now seems to be necessary to specify $SERVER and $PORT with braces, at least as ${SERVER} and ${PORT}.
  • Asclepius
    Asclepius over 7 years
    @Wildcard A bash change log suggests bash-2.04-devel, although support for host names may have been added in bash-2.05-alpha1.
  • std''OrgnlDave
    std''OrgnlDave about 7 years
    +1 this is great because nc comes standard with alpine linux and ubuntu both. probably others. no need to install extra when you're remoted in and can't. thanks for this! I might add, nc host port -w 2 && echo it works
  • ipeacocks
    ipeacocks about 7 years
    timeout 2 bash -c "</dev/tcp/canyouseeme.org/81"; echo $?is great point!
  • Celdor
    Celdor almost 7 years
    @JustinDennahower In nc -vn <host> <port>, -n does not resolve a domain name. I think people should also drop the n option.
  • Yevgen Taradayko
    Yevgen Taradayko almost 7 years
    nc: invalid option -- 'z'
  • Alessio Gaeta
    Alessio Gaeta almost 7 years
    @YevgenyTaradayko maybe you are using nmap netcat. See jolestar comment above.
  • Doktor J
    Doktor J over 6 years
    on Mac at least, you may need to add -G# to set a connection timeout separate from/in addition to the -w# timeout, which basically functions as a read timeout.
  • Damien Bezborodow
    Damien Bezborodow over 6 years
    @jolestar You can manually upgrade Ncat on Centos 7 to get the -z option. You may want to consider: unix.stackexchange.com/questions/393762/…
  • Michele
    Michele over 6 years
    Is <host> the server ip address? If I'm running nc -z -v -w5 <host> <port> on the server that I'm opening ports on, do I need to provide host in nc command?
  • Alessio Gaeta
    Alessio Gaeta over 6 years
    @Michele, yes, it's the host you want to test. It may well be localhost.
  • EFreak
    EFreak over 6 years
    I prefer nc -vz localhost 3306. It gives a more verbose output. Tried it on mac only.
  • Sanya Snex
    Sanya Snex about 6 years
    add 2>&1 to not echo status result_test=$(nc -w 2 $ip_addreess 80 </dev/null 2>&1 ; echo $?)
  • mc0e
    mc0e about 6 years
    @A-B-B I think you mean it hangs for a long time if the port is not responding, as for instance when filtered, or nothing is on the IP. A closed port does not cause a delay if the port actively refuses the connection. For many purposes this is quite acceptable.
  • Asclepius
    Asclepius about 6 years
    This technique was already posted before this answer in a prior answer by kenorb. Anyway, the bigger point is that it however hangs for a long time if the port is not responding. For example, try </dev/tcp/canyouseeme.org/80 and then </dev/tcp/canyouseeme.org/81.
  • josch
    josch over 5 years
    This does not work with the netcat-openbsd nor the netcat-traditional implementation on Debian. The command will just block and not return.
  • Brandon Rhodes
    Brandon Rhodes over 5 years
    @josch Maybe the host is unreachable so no answer is coming back either way about whether the port is open? You might want to use ping to do a basic connectivity check first if you're dealing with hosts that might not be up or responding.
  • josch
    josch over 5 years
    @BrandonRhodes you can reproduce my observation using python3 -m http.server 8000 and then nc 127.0.0.1 8000 < /dev/null. The latter command stalls. Clearly, 127.0.0.1 is reachable.
  • Brandon Rhodes
    Brandon Rhodes over 5 years
    @josch Thanks for a test case! On my Ubuntu laptop, I did indeed require an additional option -N to get it not to hang; I've updated the answer.
  • josch
    josch over 5 years
    @BrandonRhodes you might want to mention that the -N option is not available in all versions of netcat either. You are probably using the netcat-openbsd package but the netcat-traditional package doesn't have this option.
  • Brandon Rhodes
    Brandon Rhodes over 5 years
    @josch I checked, and it's indeed the openbsd package! I updated the answer to mention that.
  • Norman Bai
    Norman Bai over 5 years
    On my debian8 nc -vz works while nc -z returns nothing.
  • Alessio Gaeta
    Alessio Gaeta over 5 years
    @NormanBai, that's expected, and described in the answer. Without -v you should manually check the exit code returned (e.g. with echo $?). The non verbose form is useful for scripting.
  • LeonanCarvalho
    LeonanCarvalho about 5 years
    How can I do a IF with that instruction?
  • Gert van den Berg
    Gert van den Berg over 4 years
    nmap's grepable output might also be useful -oG - to send it to stdout. (the grep would need a bit of modification)
  • Shahin Khaled
    Shahin Khaled over 4 years
    Thanks! It makes my life easy for /etc/rc.local