nc -zv host port is the fastest way to answer the question that actually matters during an outage: is the port open. Not "does the host reply to ping", not "is there a route", but does a TCP connection to that specific service complete. One command, one line of output, and it tells you which of three completely different problems you have.
This guide covers netcat as a network engineer uses it: port checks, banner grabs, moving files without a server, and listening for traffic that is supposed to be arriving but is not. Every capture is real output from a Debian 13 host inside a Cisco Modeling Labs topology, talking to an nginx server two hops away and a Linux container three hops away over OSPF-routed IOS XE. It is part of the Linux networking commands cluster.
Find out which netcat you have, first
This is not pedantry. There are three common implementations, their flags differ, and copying a command off the internet that assumes a different one is a genuinely common way to waste twenty minutes. Debian and Ubuntu use the alternatives system, so nc can be any of them:
j@llmbits:~$ readlink -f $(command -v nc)
/usr/bin/nc.openbsd
j@llmbits:~$ update-alternatives --list nc
/bin/nc.openbsd
/bin/nc.traditional
/usr/bin/ncat
j@llmbits:~$ nc -h 2>&1 | head -2
OpenBSD netcat (Debian patchlevel 1.229-1)
usage: nc [-46CDdFhklNnrStUuvZz] [-I length] [-i interval] [-M ttl]Everything below uses the OpenBSD version unless noted, because it is the default on Debian and Ubuntu and the one you will meet most often.
netcat-openbsd. The Debian and Ubuntu default. Supports port ranges, -N to half-close on EOF, UNIX sockets and proxying. No -e, deliberately.-e for command execution, which is why it is often absent from hardened builds. Fewer options, widest compatibility with old scripts.--ssl, access control with --allow, connection brokering and --exec. Rejects port ranges and multiple ports. Different output text.The output difference alone will catch you. OpenBSD says Connection to 10.77.2.10 80 port [tcp/http] succeeded!, Ncat says Ncat: Connected to 10.77.2.10:80., and any script grepping for one will silently fail on the other.
The port check
-z means "scan without sending data" and -v makes it say what happened. That combination is ninety percent of netcat usage in the field:
j@llmbits:~$ nc -zv 10.77.2.10 80
Connection to 10.77.2.10 80 port [tcp/http] succeeded!
j@llmbits:~$ nc -zv 10.77.2.10 8080
nc: connect to 10.77.2.10 port 8080 (tcp) failed: Connection refusedOpenBSD netcat also takes several ports at once, and a range, which Ncat will not do:
j@llmbits:~$ nc -zv -w 2 10.77.3.10 8000 8001 8443 9999
Connection to 10.77.3.10 8000 port [tcp/*] succeeded!
Connection to 10.77.3.10 8001 port [tcp/*] succeeded!
Connection to 10.77.3.10 8443 port [tcp/*] succeeded!
Connection to 10.77.3.10 9999 port [tcp/*] succeeded!
j@llmbits:~$ nc -zv -w 1 10.77.3.10 7999-8002
nc: connect to 10.77.3.10 port 7999 (tcp) failed: Connection refused
Connection to 10.77.3.10 8000 port [tcp/*] succeeded!
Connection to 10.77.3.10 8001 port [tcp/*] succeeded!
nc: connect to 10.77.3.10 port 8002 (tcp) failed: Connection refusedThis is a port check, not a port scan. For anything above a handful of ports use nmap, which parallelizes, times out sensibly and identifies services. Netcat is for the single port you already suspect.
Open, refused and filtered are three different tickets
This is the part worth internalizing. To produce a genuinely filtered result, R2 in the lab got an inbound ACL denying TCP 8000 to SRV1, with no ip unreachables on the interface so it drops silently, the way a real firewall does:
R2(config)# ip access-list extended FILTER-DEMO
R2(config-ext-nacl)# deny tcp any host 10.77.3.10 eq 8000
R2(config-ext-nacl)# permit ip any any
R2(config)# interface Ethernet0/0
R2(config-if)# no ip unreachables
R2(config-if)# ip access-group FILTER-DEMO inThree probes from the Debian host, against the same server, one second apart:
j@llmbits:~$ nc -zv -w 3 10.77.3.10 8001
Connection to 10.77.3.10 8001 port [tcp/*] succeeded!
j@llmbits:~$ nc -zv -w 3 10.77.3.10 9998
nc: connect to 10.77.3.10 port 9998 (tcp) failed: Connection refused
j@llmbits:~$ time nc -zv -w 5 10.77.3.10 8000
nc: connect to 10.77.3.10 port 8000 (tcp) timed out: Operation now in progress
real 0m5.012s
user 0m0.006s
sys 0m0.001sAnd the router's side of that third probe:
R2# show ip access-lists FILTER-DEMO
Extended IP access list FILTER-DEMO
10 permit tcp any host 10.77.3.10 eq 22
20 deny tcp any host 10.77.3.10 eq 8000 (5 matches)
30 permit ip any any (7 matches)Read the timings, not just the words:
ss -tulpn on the server.
-w window. Something between you and the server is dropping the SYN silently. That is a firewall or an ACL, and it is yours to find.
j@llmbits:~$ nc -zv -w 2 10.77.9.9 80
nc: connect to 10.77.9.9 port 80 (tcp) failed: No route to hostAlways set -w. Without it, a filtered port hangs on the kernel's default SYN retry schedule, which is over two minutes on Linux, and your check appears to have crashed.
Banner grabbing, and hand-written requests
Once the socket opens, netcat is a raw pipe. Anything you feed its stdin goes on the wire, and whatever comes back lands on stdout. That makes it the shortest path to "what is actually running on this port":
j@llmbits:~$ printf 'HEAD / HTTP/1.0\r\n\r\n' | nc -w 3 10.77.2.10 80
HTTP/1.1 200 OK
Server: nginx/1.29.8
Date: Wed, 19 Aug 2026 15:06:40 GMT
Content-Type: text/html
Content-Length: 896
Last-Modified: Tue, 07 Apr 2026 11:37:12 GMT
Connection: close
ETag: "69d4ec68-380"
Accept-Ranges: bytesThe \r\n matters. HTTP requires CRLF line endings, and a bare newline from echo will hang against strict servers. Use printf, not echo.
The same trick works against SMTP, POP3, IMAP, Redis and anything else that speaks a text protocol. It is also how you confirm a load balancer is talking to the backend you think it is.
Moving a file with no server software
Netcat's other real use is transferring a file between two hosts when neither has scp, HTTP or anything else available. One side listens, the other connects. On SRV1, three hops away:
root@ea5bda805e02:/tmp# nc -l -p 9999 > /tmp/recv.cfg &And from the Debian host:
j@llmbits:~$ cat r9.cfg
hostname R9
interface Loopback0
ip address 9.9.9.9 255.255.255.255
j@llmbits:~$ nc -N -w 5 10.77.3.10 9999 < r9.cfg; echo "nc exit=$?"
nc exit=0Back on SRV1:
root@ea5bda805e02:/tmp# ls -l /tmp/recv.cfg; cat /tmp/recv.cfg
-rw-r--r-- 1 root root 68 Aug 19 15:09 /tmp/recv.cfg
hostname R9
interface Loopback0
ip address 9.9.9.9 255.255.255.255Two details that trip people up. -N tells netcat to shut down the write side of the socket when stdin hits EOF, which is what signals the listener that the file is finished; without it the sender sits there until a timeout. And a plain nc -l handles exactly one connection and then exits. Earlier in this session a port check against 9999 connected and closed, which killed the listener before the real transfer arrived. Use -k if you want it to keep listening.
The reverse direction works identically. Listening on the Debian host and sending from the container:
j@llmbits:~$ nc -l -p 9100 > from-srv1.txt &
root@ea5bda805e02:/tmp# echo "SRV1 says hi at $(date -u +%T)" | nc -N -w 3 10.77.0.100 9100
j@llmbits:~$ cat from-srv1.txt
SRV1 says hi at 15:09:58None of this is encrypted or authenticated. It is fine inside a lab or across a management VLAN you control, and it is not fine across anything else. For that, use scp or an SSH tunnel.
Get the Linux Networking Field Reference - 10 pages, free
Every command in this cluster on ten printable pages: iproute2, sockets, DNS, capture, monitors and all three firewall front ends. Includes a ten-symptom troubleshooting decision tree and annotated real lab output. Free for PingLabz members, just sign up with your email.
Listening as a diagnostic
A listener is also a way to prove that traffic which should be arriving actually arrives. Stand up a fake service and see whether the client, the firewall rule or the NAT translation you just changed does what you expect. Here is netcat answering a single HTTP request with a hand-written response:
j@llmbits:~$ printf "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 24\r\nConnection: close\r\n\r\nhello from netcat on VM\n" | nc -l -p 8088And the container three hops away, fetching it:
root@ea5bda805e02:/tmp# curl -sS -i http://10.77.0.100:8088/
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 24
Connection: close
hello from netcat on VMThat is a complete round trip through three routers, proving the return path, any ACLs in the way and the client's own routing, using a service that took one line to create.
UDP, and why it lies to you
UDP has no handshake, so there is nothing for netcat to succeed or fail at. -zu will frequently report success against a port where nothing is listening, because the only thing that would tell you otherwise is an ICMP port-unreachable that a firewall probably ate.
The honest way to test UDP is to send something and confirm it arrived on the far end. Listener on SRV1:
root@ea5bda805e02:/tmp# nc -u -l -p 9998 > /tmp/udp.txt &Sender on the Debian host:
j@llmbits:~$ echo 'udp probe from 10.77.0.100' | nc -u -w 2 10.77.3.10 9998 && echo 'datagram sent'
datagram sentAnd the proof, on the receiver:
root@ea5bda805e02:/tmp# cat /tmp/udp.txt
udp probe from 10.77.0.100"Datagram sent" from the client means nothing on its own. The file on the far end means everything. Treat every UDP test this way, or pair it with tcpdump on the receiver.
Source address and source port
When an ACL matches on source, you need to control what your probe looks like. -s picks the source address and -p picks the source port:
j@llmbits:~$ nc -zv -p 12345 -s 10.77.0.100 10.77.2.10 80
Connection to 10.77.2.10 80 port [tcp/http] succeeded!On a multi-homed host, -s is how you verify that the correct interface is being used rather than trusting the routing table to pick what you expected. -p is how you test a rule that permits a specific source port, which shows up in older firewall configurations and in some DNS and NTP policies.
FAQ
Why does nc -e not work on my system?
Because you are running the OpenBSD version, which removed it on purpose. Executing a program and wiring it to a socket is exactly the primitive a reverse shell needs. If you legitimately need it, ncat --exec or socat with an EXEC: address does the same thing, and socat does it with far more control.
When should I use nmap instead?
Any time you are checking more than a few ports, want service and version detection, or need timing control. Netcat probes serially and reports what the kernel told it. nmap is the right tool for scanning.
Why does my listener die after one connection?
That is the default. A single accepted connection, then exit. Add -k to keep listening. Be aware that a port check from anyone, including your own monitoring, counts as that one connection.
Is nc -z a port scan?
Technically yes, it completes a full TCP handshake per port, which is a connect scan and will show up in any server's logs. On networks you do not own, that has the same legal and policy weight as running nmap. Scan gear you are responsible for.
How do I check a port when netcat is not installed?
Bash can do it alone: timeout 2 bash -c '</dev/tcp/10.77.2.10/80' && echo open. It uses bash's built-in /dev/tcp pseudo-device, works on any bash that was not compiled with it disabled, and needs nothing installed. curl -v telnet://host:port is another fallback.
Can netcat talk to an HTTPS port?
Not usefully; it will open the socket but has no TLS. Use ncat --ssl, openssl s_client -connect host:443, or socat with an OPENSSL address.
Key takeaways
nc -zv host portis the port check. Always add-w, or a filtered port hangs for over two minutes on Linux defaults.- Succeeded, refused, timed out and no route to host are four different problems with four different owners. The timing tells you as much as the text.
- Check which implementation you have. OpenBSD netcat takes port ranges and has no
-e; Ncat has TLS and--execbut rejects ranges; the two print different text that will break your grep. - Use
printfwith explicit\r\nfor hand-written HTTP, notecho. - File transfer needs
-Non the sender so the listener sees the end of the stream, and a plain listener serves exactly one connection. - UDP success from the sender means nothing. Confirm arrival on the receiver every time.
- Netcat is plaintext with no authentication. Management VLAN and lab only.
Next in this cluster: socat, which is netcat with the safety off and a much larger address vocabulary, and SSH tunneling for doing the same things safely across untrusted networks. The full toolset is indexed on the Linux networking commands pillar.