Skip to content

netcat

nmap answers "which of these 1000 ports are open?" Most of the time on a live incident, the question is much smaller than that: can this machine, right now, open a TCP connection to that one host and that one port? Running a port scanner against a production database to answer that is overkill, and on a network you don't own it's the kind of thing that generates a security ticket.

netcat — the command is nc — is the small tool for the small question. It opens a socket, connects it to your terminal, and gets out of the way. Nothing about the protocol, no parsing, no interpretation: bytes in, bytes out.

Which netcat you have matters

Three different programs answer to nc, and they don't share a flag set. Ubuntu and Debian install netcat-openbsd by default, which is what this article uses:

nc -h 2>&1 | head -3
OpenBSD netcat (Debian patchlevel 1.219-1)
usage: nc [-46CDdFhklNnrStUuvZz] [-I length] [-i interval] [-M ttl]
      [-m minttl] [-O length] [-P proxy_username] [-p source_port]

nc -h writes its usage to standard error, hence the 2>&1. If the first line says Ncat you have Nmap's rewrite, which is a superset with different long options; if it says nothing recognisable you may have the ancient GNU version, where -z behaves differently. Check before pasting someone else's command.

Testing one port

nc -zv db-01.internal 5432
Connection to db-01.internal (10.20.0.31) 5432 port [tcp/postgresql] succeeded!
  • -z means "just connect, don't send data" — netcat completes the TCP handshake, reports the result, and closes.
  • -v is what makes it print that line at all; without it, success is silent and you'd be reading $? instead.

The failures are where the value is, because netcat's three failure messages are three genuinely different network conditions:

nc: connect to 10.20.0.31 port 5432 (tcp) failed: Connection refused

The packet arrived. A host at that address answered — with a TCP reset, because nothing is listening on that port. Routing works, no firewall dropped it silently. Either the service is down or it's bound to a different address.

nc: connect to 10.20.0.31 port 5432 (tcp) failed: Connection timed out

Nothing came back at all. The SYN went out and vanished. Something dropped it without replying — a firewall configured to DROP rather than REJECT, a security group, or a host that isn't there. Note the wait: with no -w, netcat sits through the kernel's full TCP retry sequence, which on Linux is over two minutes. Cap it:

nc -zv -w 3 10.20.0.31 5432
nc: connect to 10.20.0.31 port 5432 (tcp) failed: Connection timed out

Same verdict in three seconds instead of two minutes, which matters when you're testing twelve hosts.

nc: getaddrinfo for host "db-01.internal" port 5432: Name or service not known

Not a network problem at all. The name never resolved, so no packet was ever sent — a distinction worth making out loud before anyone opens a firewall ticket.

A small range works too, which is enough for "is the whole service down or just one port":

nc -zv -w 2 10.20.0.31 5432 6379 9200 2>&1
nc: connect to 10.20.0.31 port 5432 (tcp) failed: Connection refused
Connection to 10.20.0.31 (10.20.0.31) 6379 port [tcp/*] succeeded!
nc: connect to 10.20.0.31 port 9200 (tcp) failed: Connection timed out

Read that as three separate answers: Postgres is down but the host is healthy, Redis is fine, and something is silently filtering 9200.

Speaking a protocol by hand

Drop -z and netcat becomes a raw terminal on the socket. Every text-based protocol this course has covered can be driven manually, and doing it once teaches more than reading the RFC:

printf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' | nc example.com 80
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1256
Connection: close
Date: Mon, 03 Aug 2026 10:14:22 GMT

<!doctype html>
...

The \r\n line endings are not optional — HTTP requires carriage-return/line-feed, and a request sent with bare newlines is a common reason a hand-typed request hangs. The blank line at the end (\r\n\r\n) is what tells the server the headers are finished.

This is also the fastest way to see what a service claims to be before deciding whether to trust it:

nc -w 3 mail.example.com 25
220 mail.example.com ESMTP Postfix (Ubuntu)

That greeting is SMTP's, and it arrived without you sending anything. Press Ctrl+C to close. A banner that advertises the exact daemon and distribution is information you'd rather not hand to strangers — which is a configuration decision, not a netcat problem, but netcat is how you discover it.

Only probe hosts you're responsible for

Connecting to a port to see what answers is trivially logged, and on infrastructure you don't own or have written permission to test, a sweep of ports looks exactly like reconnaissance because it is. Keep this to your own lab, your own servers, and systems covered by an authorised engagement.

The listener side, and one thing to never do

-l makes netcat listen instead of connect, which turns it into a disposable server for testing whether traffic reaches a host at all:

nc -l 9000

Leave that running on the destination, then from the source machine:

echo "reachability test" | nc -w 3 <destination-ip> 9000

The text appears in the listener's terminal. That single round trip proves routing, firewall rules, and NAT all work on that port — before anyone has installed the real application. When it doesn't appear, you've localised the problem to the network with certainty, which is a much stronger statement than "the app doesn't work."

File transfer works the same way and is genuinely handy on a stripped-down host with no scp:

nc -l 9000 > received.tar.gz
nc -w 3 <destination-ip> 9000 < archive.tar.gz

Do not use netcat's shell-execution feature

Some netcat builds carry a flag that runs a program and wires its input and output to the socket. Pointing it at a shell publishes an unauthenticated, unencrypted root-capable command prompt to anyone who can reach the port — it is the classic backdoor, and it will be found by automated scanners within minutes on a public address. netcat-openbsd on Debian ships without that flag for exactly this reason. If you need a remote shell, use SSH; if you need an encrypted tunnel for a test, use SSH port forwarding.

Everything netcat sends is plaintext regardless. Never pipe credentials, keys, or customer data through it, even inside a private network.

Where netcat stops being the right tool

It has no idea what a protocol is. It won't do TLS, so pointing it at port 443 gets you a connection and then silence — you need openssl s_client or curl -v there. It doesn't retry, doesn't follow redirects, and doesn't tell you why a connection was refused, only that it was.

What it does give you is certainty about the one thing underneath all of those: whether a TCP connection between these two machines on this port is possible at all. That's the first question in most network investigations, and answering it in three seconds with -zv -w 3 is a habit worth building.

Practice

  1. Run nc -zv -w 2 against a port you know is closed on your own machine, then against a port you know is filtered by a firewall rule (a cloud VM with a restrictive security group is ideal). Write down the exact message each produced and what it tells you about who saw the packet.
  2. Fetch a page by hand with printf ... | nc, then repeat it with bare \n line endings instead of \r\n and observe the difference.
  3. Set up the listener/sender pair across two machines on port 9000, confirm it works, then block the port and repeat — predicting in advance whether the sender will report refused or timed out.

That last exercise is the one to sit with. Predicting which of the two failures you'll get, before running the command, is a good test of whether the difference between a reset and a silent drop has actually landed.

There's a question underneath all of this that netcat skips past, though. Testing a port assumes the host is there to answer at all — and when the connection times out, the very next thing worth knowing is whether that machine is reachable by any means whatsoever, which is a smaller and older question with a tool of its own.

Sources