510

In the old days, we used telnet to see if a port on a remote host was open: telnet hostname port would attempt to connect to any port on any host and give you access to the raw TCP stream.

These days, the systems I work on do not have telnet installed (for security reasons), and all outbound connections to all hosts are blocked by default. Over time, it's easy to lose track of which ports are open to which hosts.

Is there another way to test if a port on a remote system is open – using a Linux system with a limited number of packages installed, and telnet is not available?

Steve HHH
  • 7,430

16 Answers16

570

Nice and verbose! From the man pages.
Single port:

nc -zv 127.0.0.1 80

Multiple ports:

nc -zv 127.0.0.1 22 80 8080

Range of ports:

nc -zv 127.0.0.1 20-30
Subhranath Chunder
  • 5,801
  • 1
  • 13
  • 2
432

Bash has been able to access TCP and UDP ports for a while. From the man page:

/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 a TCP connection to the corresponding socket.
/dev/udp/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 a UDP connection to the corresponding socket.

So you could use something like this:

xenon-lornix:~> cat < /dev/tcp/127.0.0.1/22
SSH-2.0-OpenSSH_6.2p2 Debian-6
^C pressed here

Taa Daa!

lornix
  • 11,909
133

Netcat is a useful tool:

nc 127.0.0.1 123 &> /dev/null; echo $?

Will output 0 if port 123 is open, and 1 if it's closed.

thnee
  • 1,930
  • 1
  • 12
  • 8
94

The simplest method, without making use of another tool, such as socat, is as described in @lornix's answer above. This is just to add an actual example of how one would make use of the psuedo-device /dev/tcp/... within Bash if you wanted to, say, test if another server had a given port accessible via the command line.

Examples

Say I have a host on my network named skinner.

$ (echo > /dev/tcp/skinner/22) >/dev/null 2>&1 \
    && echo "It's up" || echo "It's down"
It's up

$ (echo > /dev/tcp/skinner/222) >/dev/null 2>&1 && \
    echo "It's up" || echo "It's down"
It's down

The reason you want to wrap the echo > /dev/... in parentheses like this, (echo > /dev/...) is because if you don't, then with tests of connections that are down, you'll get these types of messages showing up.

$ (echo > /dev/tcp/skinner/223) && echo hi
bash: connect: Connection refused
bash: /dev/tcp/skinner/223: Connection refused

These can't simply be redirected to /dev/null since they're coming from the attempt to write out data to the device /dev/tcp. So we capture all that output within a sub-command, i.e. (...cmds...) and redirect the output of the sub-command.

slm
  • 10,859
66

I found that curl may get the job done in a similar way to telnet, and curl will even tell you which protocol the listener expects.

Construct an HTTP URI from the hostname and port as the first argument to curl. If curl can connect, it will report a protocol mismatch and exit (if the listener isn't a web service). If curl cannot connect, it will time out.

For example, port 5672 on host 10.0.0.99 is either closed or blocked by a firewall:

$ curl http://10.0.0.99:5672
curl: (7) couldn't connect to host

However, from a different system, port 5672 on host 10.0.0.99 can be reached, and appears to be running an AMQP listener.

$ curl http://10.0.0.99:5672
curl: (56) Failure when receiving data from the peer
AMQP

It's important to distinguish between the different messages: the first failure was because curl could not connect to the port. The second failure is a success test, though curl expected an HTTP listener instead of an AMQP listener.

Steve HHH
  • 7,430
22

Here is one-liner:

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

using Bash syntax explained in @lornix answer.

For more info, check: Advanced Bash-Scripting Guide: Chapter 29. /dev and /proc.

kenorb
  • 26,615
15
[admin@automation-server 1.2.2]# nc -v -z -w2 192.168.193.173 6443
nc: connect to 192.168.193.173 port 6443 (tcp) failed: Connection refused

[admin@automation-server 1.2.2]# nc -v -z -w2 192.168.194.4 6443
Connection to 192.168.194.4 6443 port [tcp/sun-sr-https] succeeded!

Hope it solves your problem :)

14

Combining the answers from @kenorb and @Azukikuru you could test port open/closed/firewalled.

timeout 1 bash -c '</dev/tcp/127.0.0.1/22 && echo Port is open || echo Port is closed' || echo Connection timeout

Another approach with curl for reaching any port

curl telnet://127.0.0.1:22
12

I was struggling for a whole day because none of these answers seemed to work for me. The problem is that the most recent version of nc no longer has the -z flag, whereas direct access via TCP (as according to @lornix and @slm) fails when the host is not reachable. I eventually found this page, where I finally found not one but two working examples:

  1. nc -w1 127.0.0.1 22 </dev/null

    (the -w flag takes care of the timeout, and the </dev/null replaces the -z flag)

  2. timeout 1 bash -c '(echo > /dev/tcp/127.0.0.1/22) >/dev/null 2>&1'

    (the timeout command takes care of the timeout, and the rest is from @slm)

Then, simply use && and/or || (or even $?) to extract the result. Hopefully, somebody will find this information useful.

Azukikuru
  • 121
11

If you have curl installed:

curl -v telnet://$host:$port/$path
groo
  • 211
6

Here's a function that will pick one of the methods depending on what's installed on your system:

# Check_port <address> <port> 
check_port() {
if [ "$(which nc)" != "" ]; then 
    tool=nc
elif [ "$(which curl)" != "" ]; then
     tool=curl
elif [ "$(which telnet)" != "" ]; then
     tool=telnet
elif [ -e /dev/tcp ]; then
      if [ "$(which gtimeout)" != "" ]; then  
       tool=gtimeout
      elif [ "$(which timeout)" != "" ]; then  
       tool=timeout
      else
       tool=devtcp
      fi
fi
echo "Using $tool to test access to $1:$2"
case $tool in
nc) nc -v -G 5 -z -w2 $1 $2 ;;
curl) curl --connect-timeout 10 http://$1:$2 ;;
telnet) telnet $1 $2 ;;
gtimeout)  gtimeout 1 bash -c "</dev/tcp/${1}/${2} && echo Port is open || echo Port is closed" || echo Connection timeout ;;
timeout)  timeout 1 bash -c "</dev/tcp/${1}/${2} && echo Port is open || echo Port is closed" || echo Connection timeout ;;
devtcp)  </dev/tcp/${1}/${2} && echo Port is open || echo Port is closed ;;
*) echo "no tools available to test $1 port $2";;
esac

}
export check_port
3

It shouldn't be available on your box, but try with nmap.

peperunas
  • 1,321
1

for reference, expanding on @peperunas' answer:

the way to use nmap to test, is:

nmap -p 22 127.0.0.1

(example above uses localhost for demonstration purposes)

1

Most Linux systems do come installed with Python3 these days, you can use a script like this:

import socket

def check_conn(): conn_data = [ ('xyz.abc.int', '10.20.200.10', 443), ('abc.xyz.int', '10.20.200.12', 8080) ]

for name, host, port in conn_data:
    s = socket.socket()
    try:
        s.settimeout(2)
        s.connect((host, port))
    except Exception as e:
        print(f&quot;ERROR → {name} {host}:{port} → {e}&quot;)
        s.close()
    else:
        print(f&quot;OK    → {name} {host}:{port}&quot;)
        s.close()


if name == 'main': check_conn()

  1. Save the script in a file like conn_test.py
  2. Then run the script like python3 conn_test.py

You can further modify the script to externalize the host/port data.

Saikat
  • 467
0

If you've to test more than on system you may use our test tool dda-serverspec (https://github.com/DomainDrivenArchitecture/dda-serverspec-crate) for such tasks. You may define your expectation

{:netcat [{:host "mywebserver.com" :port "443"}
          {:host "telnet mywebserver.com" :port "80"}
          {:host "telnet mywebserver.com" :port "8443"}]}

and test these expectation either against localhost or against remote hosts (connect by ssh). For remote tests you've to define a targets:

{:existing [{:node-name "test-vm1"
             :node-ip "35.157.19.218"}
            {:node-name "test-vm2"
             :node-ip "18.194.113.138"}]
 :provisioning-user {:login "ubuntu"}}

You may run the test with java -jar dda-serverspec.jar --targets targets.edn serverspec.edn

Under the hood we're using netcat as proprosed above ...

jerger
  • 1
0

@justsomeguy nmap accepts a port range, quickly outputting each open port found, with protocol and closed port count! Edit: Adding suggested option "-Pn" gets results where "-p" didn't, BUT it goes beyond the port range specified.

for example:

nmap -p 1-1023 127.0.0.1
Nmap scan report for localhost.localdomain (127.0.0.1)
Host is up (0.0000070s latency).
Not shown: 1011 closed ports
PORT    STATE SERVICE
22/tcp  open  ssh
24/tcp  open  priv-mail
25/tcp  open  smtp
80/tcp  open  http
110/tcp open  pop3
143/tcp open  imap
443/tcp open  https
465/tcp open  smtps
587/tcp open  submission
953/tcp open  rndc
993/tcp open  imaps
995/tcp open  pop3s
Nmap done: 1 IP address (1 host up) scanned in 0.06 seconds
Toto
  • 19,304