473

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
  • 6,960
  • 6
  • 29
  • 37
  • Related: [check status of one port on remote host](http://stackoverflow.com/q/1168317/55075) at SO – kenorb Dec 30 '15 at 14:50
  • I was having this same issue. The answer by @Subhranath Chunder below helped. However, I then found out that installing Telnet was a small matter of running `brew install telnet`. So I expect Linux users can do the same with `yum` and `apt-get`. – Mig82 Sep 26 '19 at 09:22
  • When "all outbound connections to all hosts are blocked by default" there will be no way to perform such a test - you are offline – Jaap Joris Vens Nov 27 '20 at 04:37

14 Answers14

522

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,321
  • 1
  • 11
  • 2
  • 2
    Perfect! This outputs a clear connection succeeded/failed message with just the one line. Note that multiple ranges don't appear to work for my ``nc`` version; only the first range is tested. – davidjb May 18 '15 at 03:42
  • 7
    This hanged when tried on [Ubuntu 14.04](https://en.wikipedia.org/wiki/List_of_Ubuntu_releases#Ubuntu_14.04_LTS_.28Trusty_Tahr.29) (Trusty Tahr) for a remote server (same LAN) for closed ports (it timed out after 127 seconds) - thus not very suitable in scripts. It did work though for a service that had a port open. Using option "-w2" could be the solution. – Peter Mortensen Jul 21 '15 at 12:36
  • 8
    Use -u option for UDP ports. – Efren Sep 21 '16 at 00:10
  • 9
    On version 6.4 of ncat -z is not recognized. I was able to do without z – smishra Apr 06 '17 at 17:18
  • 6
    You can check multiple ranges with: `nc -zv 127.0.0.1 22,80,8080,20-30,443-446` (nc Version: 1.107-4). – bobbel Jul 06 '17 at 16:55
  • 1
    I like how the man page for nc says `The nc (or netcat) utility is used for just about anything under the sun involving TCP, UDP, or UNIX-domain sockets.` – Christian Sirolli Mar 23 '18 at 21:35
  • Doesn't seem to be bundled with Centos v6 – Janac Meena Feb 05 '19 at 21:34
  • 4
    that's great, but only if nc is actually installed :P the accepted answer via bash works almost everywhere, more GNU/Linux servers have bash than nc – Petr Jul 26 '19 at 10:49
  • This should be marked as the answer. – Indika K Apr 28 '20 at 03:53
  • This works; I like it. However, I never see the output "" succeeded!"" like advertised. When I do a range, seems only the open port gets displayed. I'm on RHEL7. Just might have been a nice to have so I could grep in a script. – Marinaio Nov 09 '21 at 15:35
  • 1
    Well this works well with busybox... :) better for WSL2 Alpine – Benyamin Limanto Mar 12 '22 at 13:41
390

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!

Peter Mortensen
  • 12,090
  • 23
  • 70
  • 90
lornix
  • 11,240
  • 2
  • 30
  • 34
  • This also seems to work in [MinGW](http://en.wikipedia.org/wiki/MinGW). For instance, a remote [VNC](http://en.wikipedia.org/wiki/Virtual_Network_Computing) server on 192.168.2.100 responds with "RFB 003.008" by using "cat < /dev/tcp/192.168.2.100/5900". – Peter Mortensen Jul 21 '15 at 11:26
  • 1
    However on ports that were not open it timed out after 22 seconds (tried on [Ubuntu 14.04](https://en.wikipedia.org/wiki/List_of_Ubuntu_releases#Ubuntu_14.04_LTS_.28Trusty_Tahr.29) (Trusty Tahr) for a remote server). Interestingly, the timeout period is much shorter than the one for nc (see [thnee's answer](http://superuser.com/questions/621870/test-if-a-port-on-a-remote-system-is-reachable-without-telnet/621901#621901)). – Peter Mortensen Jul 21 '15 at 13:00
  • // , Ohhh, this is one of those days I love this site. Here's the part of the bash manual for this answer: https://www.gnu.org/software/bash/manual/bashref.html#Redirections – Nathan Basanese Jan 23 '16 at 05:31
  • @lornix, can you please tell me, why nc works on my box, but your solution not working: $ cat < /dev/tcp/10.0.xx.xx/1433 cat: -: Connection reset by peer $ nc -zv -w5 10.0.xx.xx 1433 Connection to 10.0.xx.xx 1433 port [tcp/ms-sql-s] succeeded! – Alexandr Jun 10 '16 at 07:40
  • @Alexandr, perhaps you're connecting in a different way. Your **nc** command includes the _-z_ flag, indicating 'zero-I/O-mode'. **cat** may be connecting and requesting some sort of I/O, whereas the **nc** (with _-z_) usage just strokes the port to see if it's alive. – lornix Jun 10 '16 at 08:33
  • 2
    @lornix, ok, but in this case I have to get the same result with use nc without -z option, but it still does not work: # nc -v -w5 127.0.0.1 18080 Connection to 127.0.0.1 18080 port [tcp/*] succeeded! # cat < /dev/tcp/127.0.0.1/18080 Just hangs without any result. Just want to understand when I can use "/dev/tcp/host/port" option – Alexandr Jun 14 '16 at 07:09
  • 6
    @Alexandr... actually, "hangs without any result" is pretty much expected behavior. **cat** is waiting for input. **nc** has extra smarts to enable it to sense no-data pending and stops trying. **cat** isn't _quite_ as smart. Try `cat < /dev/tcp/localhost/22`, you should get your sshd header. Evidently, your process on port 18080 waits for something to come in, before sending anything. Port 22 (**ssh**) greets you with it's version and whatnot. Try it out! – lornix Jun 14 '16 at 08:44
  • 1
    @lornix, thank you very much for explanation! Now the restriction is clear. I think using nc should be a preferred way to check ports. – Alexandr Jun 14 '16 at 14:29
  • Not working on Ubuntu 16.04.1 – Daniel Andrei Mincă Oct 12 '16 at 07:42
  • @MincăDanielAndrei Make sure you're using **bash**. No idea if this works on other shells, Ubuntu may have substituted a different shell as your default, depending on the whims of The Mark. – lornix Oct 12 '16 at 09:45
  • @lornix I'm using GNU bash, version 4.3.46(1)-release (x86_64-pc-linux-gnu). The problem is that /dev/tcp/ does not exist. I got /dev everything except /tcp and /udp . – Daniel Andrei Mincă Oct 12 '16 at 11:06
  • 1
    Bash **emulates** the /dev/tcp and /dev/udp paths when used in redirections. (From Bash Man Page v4.4.0, line 1344. Should be in similar location in yours). Those paths don't exist for other programs or even as files. So 'cat /dev/tcp/localhost/22' will error out, while 'cat < /dev/tcp/localhost/22' will show your SSH server header. – lornix Oct 13 '16 at 02:08
  • Pow, this is the way to test Ansible's wait_for command on the command line => http://docs.ansible.com/ansible/wait_for_module.html – Elijah Lynn Jun 16 '17 at 07:43
  • doesn't work under CentOS. – Scott Chu Jul 19 '17 at 04:03
  • 7
    This was incredibly helpful when working with a docker container that had nothing installed. Was able to quickly verify that the container had access to non-containerized DB via DNS. Example: cat < /dev/tcp/hostname/5432 – Michael Hobbs Oct 24 '18 at 14:09
  • Look further down in the "answers" and @sim has a great example of applying this answer. His bash fu is strong. (btw - this does work under CentOS.) – Beege Mar 17 '19 at 18:39
  • Worked for me on my RHEL7 – Marinaio Nov 09 '21 at 15:32
124

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,810
  • 1
  • 12
  • 8
  • 3
    This is a far more elegant and scriptable answer than my own. It is unfortunate for me that the security-conscious sysadmins who withheld `telnet` also withheld `nc` (though – strangely – not `curl` or `wget`). – Steve HHH Jul 19 '13 at 19:51
  • 1
    Yes that is completely arbitrary and silly. – thnee Jul 19 '13 at 20:10
  • 3
    Let the `FOR` statements begin! – Chad Harrison Jul 19 '13 at 21:37
  • 2
    This hanged when tried on [Ubuntu 14.04](https://en.wikipedia.org/wiki/List_of_Ubuntu_releases#Ubuntu_14.04_LTS_.28Trusty_Tahr.29) (Trusty Tahr) for a remote server (same LAN) for closed ports (it timed out after about 127 seconds) - thus not very suitable in scripts. It did work though for a service that had a port open, returning 0. Using option "-w2" could be the solution. – Peter Mortensen Jul 21 '15 at 13:06
  • 1
    I think `-G 2` would be more appropriate for TCP timeout – A B Aug 24 '15 at 21:45
  • @PeterMortensen Connection timeout is a generic network problem, nothing particular to netcat. Configure your desired timeout with the `-w` flag. – thnee Oct 20 '15 at 18:24
  • @thnee `-w` doesn't seem to work for connections. See [here](http://stackoverflow.com/questions/18578565/nc-netcat-on-mac-os-x-10-8-4-gets-stuck) and [here](http://savage.net.au/MSWindows/html/nc.html). Quote `-w # => Timeout after # seconds. Note that -w also sets the network inactivity timeout. This does not have any effect until standard input closes […] This works for final net reads but not for connections.` – 1.61803 Jan 14 '16 at 12:27
  • 1
    For those who don't know bash well, what does the `&> /dev/null; echo $?` do? I noticed that without that addition, the command produces no output. – connorbode Jan 14 '16 at 14:52
  • 1
    `&> /dev/null` redirects all output so it wont show up, and `echo $?` shows the resulting status code of the previous command executed. – thnee Jan 18 '16 at 08:35
  • How to use this statement for a conditional -like if- operator? if[ WhatGoesHere == 1] – Ismail Yavuz Dec 20 '16 at 13:22
  • to prevent hanging, use the -z param ie. nc -z 127.0.0.1 123 &> /dev/null; echo $? – JDOaktown Jun 05 '23 at 18:04
79

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.

Peter Mortensen
  • 12,090
  • 23
  • 70
  • 90
slm
  • 9,959
  • 10
  • 49
  • 57
63

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
  • 6,960
  • 6
  • 29
  • 37
  • 8
    If curl isn't available, wget might be. `wget -qS -O- http://ip.add.re.ss:port` should effectively do the same thing. – user Jul 19 '13 at 17:52
  • 4
    This even works with a hostname, ex. `curl myhost:22`. – 에이바 Feb 25 '14 at 16:17
  • This may be incorrect. I am havng a tomcat service running, but getting 404 error. # curl -k https://192.168.194.4:6443 Apache Tomcat/7.0.34 - Error report

    HTTP Status 404 - /


    type Status report

    message /

    description The requested resource is not available.


    Apache Tomcat/7.0.34

    – Mohammad Shahid Siddiqui Jun 02 '15 at 06:30
  • See my [post](http://stackoverflow.com/a/33457944/55075) with similar approach. – kenorb Dec 30 '15 at 14:49
18

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
  • 24,736
  • 27
  • 129
  • 199
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 :)

13

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
Miguel Ferreira
  • 231
  • 2
  • 4
  • 1
    Add -v to curl is better (curl -v telnet://127.0.0.1:22). If the server does not output anything such as web socket. You don't know which is success connection or time out. – Ironhide May 09 '22 at 02:21
9

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
  • 91
  • 1
  • 1
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
Robert Boyd
  • 61
  • 1
  • 1
  • 1
    why would you set tool, only to use it later in the case statement. isn't it simpler to check which tool is available and use it immediately in the if block? – Mark Lybarger Nov 05 '19 at 15:46
  • Good point -- I threw that together in a hurry -- it does make sense to collapse the code as you suggested. On the other hand, setting tool could be useful if there's a need to do more with it in another part of the code. – Robert Boyd May 14 '20 at 15:05
5

If you have curl installed:

curl -v telnet://$host:$port/$path
groo
  • 151
  • 1
  • 4
  • 2
    (1) I presume that you mean that the ``curl -v telnet://``, the **`:`** and the (final) `/` are literal, and `$host` and `$port` are placeholders for the name/address of the host in question and the port number in question.  What should the user use for `$path`?  (2) If the port is open, but it is not implementing HTTP, we can expect this command to fail.  So how does one distinguish between a failure because the port isn’t open and a failure because the port isn’t HTTP? … (Cont’d) – Scott - Слава Україні Oct 14 '20 at 21:13
  • 1
    (Cont’d) … (3) `curl http://` *`host`* **`:`** *`port`* has already been given as an answer (which was clear on point #1 and discussed point #2).  Are you saying that `curl -v telnet:` is superior to `curl http:`  Why?  (4) And there’s *another* answer that suggests `curl http://` *`host`* **`:`** *`port`*, and there’s even another one suggesting `curl telnet://` *`host`* **`:`** *`port`*.  What does your answer add to those earlier ones? … … … … … … … … … … … … … … … … … … … … … Please do not respond in comments; [edit] your answer to make it clearer and more complete. – Scott - Слава Україні Oct 14 '20 at 21:13
3

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

Peter Mortensen
  • 12,090
  • 23
  • 70
  • 90
peperunas
  • 1,221
  • 3
  • 12
  • 16
  • 4
    `nmap` is a good tool, but not available on these systems. Rather than download `nmap`, compile it, install it to my home directory, then copy it to all the other systems, I was hoping to find a way using existing tools available in most Linux installations. – Steve HHH Jul 19 '13 at 17:16
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)

justsomeguy
  • 357
  • 1
  • 8
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