Netcat

Netcat (nc) reads and writes raw bytes over TCP and UDP, so it answers “can I reach this port, and what does it say back?” with nothing else in the way. Reach for it to confirm an Nmap hit, grab a banner, hand-type a protocol, move a file between two lab hosts, or catch a connection you are expecting. Anything heavier is a job for curl, nmap, or socat.

Mental model: nc [options] HOST PORT connects out; nc -l PORT waits for a connection. Almost everything is one of those two.

New to netcat? Core concepts
  • Two modes: nc HOST PORT dials out (client); nc -l PORT waits (listener). Almost everything is one of these.
  • TCP vs UDP: TCP handshakes, so open/closed is honest; UDP (-u) has no handshake, so silence never means “closed”.
  • Port state: open (something is listening), closed (RST, refused), filtered (dropped, so it hangs until -w fires).
  • Zero-I/O scan (-z): connect only, send no bytes, just to learn open vs closed.
  • Banner: the line a service sends first on connect (SSH, FTP, SMTP), often enough to fingerprint the daemon.
  • It is a pipe: nc glues stdin and stdout to the socket, so whatever you pipe in goes out the wire and replies land back in your terminal.
  • Variants: OpenBSD (Kali’s default), GNU/traditional, Ncat, Busybox; -e, -k, -q, and -N differ between them.
When to reach for netcat (and when not)

Reach for it to confirm an Nmap hit on a single port, grab a banner, hand-type a text protocol (SMTP, FTP, HTTP), move a file between two lab hosts, or catch a connection you are expecting during an authorized exercise, anywhere you want raw bytes over a socket with nothing in between.

Reach for something else when you need to scan more than a handful of ports (nmap), speak real HTTP with cookies, redirects, or TLS (curl), build relays, port forwards, or TLS tunnels (socat), add encryption or access control to a listener (Ncat), or move files for real rather than in a lab (scp, rsync).

Install, update, verify#

sudo apt install -y netcat-openbsd                 # Debian / Ubuntu / Kali (preferred)
sudo dnf install -y nmap-ncat                       # Fedora / RHEL (gives ncat + nc)
sudo apt install --only-upgrade -y netcat-openbsd   # update

command -v nc          # confirm it is on PATH
nc -h 2>&1 | head      # variant + supported flags (nc prints help to stderr)
  • No reliable --version. The help banner (nc -h) tells you which variant you have, which matters more than the version number.
  • macOS ships BSD nc by default; brew install netcat gets the GNU build instead.
  • Flags differ across builds. -e, -k, and -q vary by variant (OpenBSD, GNU/traditional, Ncat, Busybox), so confirm with nc -h before trusting a tutorial.

Flags you’ll actually use#

Skim these first; the cheat sheet below is these flags combined.

FlagDoesFlagDoes
-lListen for a connection-w NTimeout / idle cutoff after N seconds
-vVerbose (print connect status)-zZero-I/O scan (connect, send nothing)
-nNo DNS, numeric addresses only-kKeep listening after a client leaves
-uUDP instead of TCP-q NQuit N seconds after stdin EOF
-p NSource / listen port-NShut the socket down on EOF (OpenBSD)
-4 / -6Force IPv4 / IPv6-CSend CRLF line endings (OpenBSD)
-s ADDRSet the source address-UUse a Unix-domain socket path
-x / -XProxy address / proxy protocol-bAllow UDP broadcast
-dDo not read stdin (listen only)-eRun a program (Ncat/traditional, not OpenBSD)

Cheat sheet#

Every block starts simple and adds one flag at a time, so you can stop at the line that does the job.

# Build up a probe: stop at the line you need
nc TARGET_IP PORT                  # 1. connect, relay stdin <-> socket
nc -v TARGET_IP PORT               # 2. + print connect status
nc -nv TARGET_IP PORT              # 3. + skip DNS (raw IP, faster)
nc -nvz TARGET_IP PORT             # 4. + zero-I/O: open/closed only, no data
nc -nvz -w3 TARGET_IP PORT         # 5. + bound it so a filtered port cannot hang

# Scan a small range (use nmap for anything bigger)
nc -nvz -w2 TARGET_IP 20-25        # TCP range, one line per port
nc -nuvz -w2 TARGET_IP 53          # UDP probe (ambiguous by design)

# Grab a banner
nc -nv -w3 TARGET_IP 22            # read what a service says first

# Speak a protocol by hand
printf 'GET / HTTP/1.1\r\nHost: DOMAIN\r\nConnection: close\r\n\r\n' \
  | nc -nv TARGET_IP 80            # raw HTTP request + full response
nc -nv TARGET_IP 25                # interactive: type EHLO test, VRFY root

# Listen (lab)
nc -lvnp 9000                      # one-shot listener on a high port
nc -lvnkp 9000                     # -k: stay up across connections
nc -lvnp 9000 127.0.0.1            # bind the listener to one interface

# Move data (lab only, plaintext)
nc -lvnp 9000 > out.bin            # receiver saves to a file
nc -nv -q1 LISTENER_IP 9000 < file # sender, quits 1s after EOF
nc -lvnp 8000 < file.iso           # serve one file to the first client
nc -lvnp 9000 | tar xvf -          # receive + untar a whole directory
tar cf - dir/ | nc -nv -N HOST 9000  # send that directory stream

# Through a proxy (OpenBSD nc)
nc -nv -x 127.0.0.1:9050 -X 5 TARGET_IP 22   # dial via a SOCKS5 proxy (Tor)

# Encrypted / controlled listener (Ncat, not OpenBSD nc)
ncat --ssl -lvnp 9000              # TLS-wrapped listener
ncat --ssl -nv HOST 9000           # TLS client

# Save + echo
nc -nv -w3 TARGET_IP PORT 2>&1 | tee notes/nc.txt

Command breakdowns#

Each block below explains one situation, the exact command, and what to expect back.

Check whether a TCP port is open#

nc -nvz -w3 TARGET_IP 443

-z connects without sending data; you get one line: succeeded! (open) or Connection refused (closed). -w3 stops it hanging on a filtered port.

Grab a service banner#

nc -nv -w3 TARGET_IP 22

Services that speak first (SSH, FTP, SMTP, POP3) announce themselves on connect. The -w3 guarantees the command returns even if nothing arrives.

Scan a small range of ports#

nc -nvz -w2 TARGET_IP 20-25

One line per port. Fine for a handful; use nmap for anything larger, since a loop of nc calls is slower and noisier.

Send a raw HTTP request without curl#

printf 'GET / HTTP/1.1\r\nHost: DOMAIN\r\nConnection: close\r\n\r\n' \
  | nc -nv TARGET_IP 80

Prints response headers and body verbatim. Connection: close makes the server hang up so nc exits instead of waiting for more.

Talk to a line protocol by hand#

nc -nv TARGET_IP 25

Connect and type commands yourself: EHLO test, VRFY root on SMTP, USER and PASS on FTP. The fastest way to see how a text protocol actually behaves.

Start a lab listener and see what connects#

nc -lvnp 9000

Prints a connection line, then relays anything the client sends. Two of these (one -l, one connecting) is a plaintext chat between hosts. Use Ctrl-C to stop.

Keep a listener alive across connections#

nc -lvnkp 9000

-k keeps nc listening after each client disconnects instead of exiting on the first. Handy for a reusable lab endpoint (OpenBSD nc and Ncat).

Probe a UDP port#

nc -nuvz -w3 TARGET_IP 53

-u switches to UDP. The result needs care: with no handshake, silence is ambiguous (see the output table and gotchas below).

Transfer a file between two lab hosts#

# Receiver first:
nc -lvnp 9000 > received.bin
# Sender, on the other lab host:
nc -nv -q1 LISTENER_IP 9000 < file.bin

-q1 makes the sender quit one second after stdin closes so the transfer actually ends. Plaintext on the wire, so lab use only; reach for scp or rsync for anything real.

Send an entire directory#

# Receiver:
nc -lvnp 9000 | tar xvf -
# Sender:
tar cf - dir/ | nc -nv -N LISTENER_IP 9000

tar turns the tree into one stream, nc carries it, and -N shuts the socket on EOF so the receiver’s tar sees a clean end. Preserves the directory layout a single-file copy would flatten.

Serve one file to whoever connects#

nc -lvnp 8000 < file.iso

The first client to connect receives the file, then the listener exits. A zero-dependency one-shot download for a box that has nc but no web server.

Test outbound egress filtering#

nc -nvz -w3 EGRESS_HOST 443

From inside a network, connect out to a port on a host you control to learn which outbound ports the firewall actually permits. succeeded means egress is open on that port.

Dial through a SOCKS proxy#

nc -nv -x 127.0.0.1:9050 -X 5 TARGET_IP 22

-x names the proxy, -X 5 selects SOCKS5 (use -X connect for an HTTP CONNECT proxy). This is how you reach a target through Tor or a pivot without a separate tool.

Wrap a listener in TLS#

ncat --ssl -lvnp 9000        # listener
ncat --ssl -nv HOST 9000     # client

OpenBSD nc has no encryption, so switch to Ncat when the bytes must not be readable on the wire. --ssl negotiates TLS on both ends; add --ssl-cert and --ssl-key to present a real certificate.

Catch a reverse shell in an authorized lab#

nc -lvnp 9000

Only against systems you own or are explicitly authorized to test. This is the listener side that receives a connection during a sanctioned exercise. OpenBSD’s nc has no -e, so on Kali’s default build you catch shells here rather than spawn them with nc.

Reading the output#

You seeMeaningDo next
succeeded! / openPath works, something is listeningGrab a banner or speak the protocol
Connection refusedHost is up, port is closed (RST)Definite “no”, cross it off
Hangs, then times out (-w)No reply, filtered or dropped by a firewallNote it; not the same as closed
Banner text on connectService speaks first (SSH/FTP/SMTP)Clue to the service, not proof of version
Connected, no bannerService waits for the client (HTTP)Send a request to get anything back
UDP silenceAmbiguous by designInvestigate; never record as “closed”

Banner shorthand: the first line usually names the service and often its exact version. SSH-2.0-OpenSSH_9.6 marks 22/SSH, 220 ... ESMTP Postfix marks 25/SMTP, 220 (vsFTPd 3.0.5) marks 21/FTP, and +OK marks 110/POP3, so a single connect can fingerprint the daemon before you send a byte.

Troubleshooting#

ProblemCauseFix
command not foundNot installed / stale PATHReinstall; open a fresh shell
Flag not recognizedWrong variantCheck nc -h; switch to Ncat
Permission denied on -lLow port (<1024) without rootUse a high port, or sudo
Command hangs foreverNo timeout on a filtered portAdd -w3
Connection refusedNothing on that portConfirm port/service (scan with Nmap)
Connection timed outFirewall dropping packetsExpected when filtered; note and move on
Could not resolve / slowDNS failure or lookupAdd -n and use the raw IP
Listener gets no dataLocal firewall blocking itAllow the port; test on loopback first
File transfer never endsSender does not close the streamAdd -q1 (or -N) on the sender

Gotchas#

  • -e /bin/bash does not exist in OpenBSD nc (Kali’s default). It was removed for safety, so old tutorials that assume it work fail; catch shells with a listener instead, or use Ncat’s -e / --sh-exec.
  • A successful TCP connect is reachability, not health. The port is open and something answered; that is all it proves.
  • UDP gives no honest signal. No handshake means silence is not “closed” and a -z success is not “open”.
  • -w is not optional in scripts. Leave it off and one filtered port hangs the whole run.
  • Netcat is plaintext with no auth. Anything you pipe through it is readable on the wire, so never move secrets with it; use Ncat --ssl or ssh when it matters.
  • Variants split on -k, -q, -e, -N. A flag that works on one build errors on another. When in doubt, nc -h.

Pairs with#

nmap scans first, then nc confirms and pokes one port. curl takes over the moment you need real HTTP with cookies, redirects, or TLS. socat handles the relays, port forwards, and TLS tunnels nc cannot express. Ncat from the Nmap project is the upgrade when you need encryption (--ssl), access control, or a dependable -k. Netcat wins on being everywhere and instant; the alternatives win the moment you need scale, features, or encryption.

Find us elsewhere

Merch, stickers, and moreSupport the work at the Solvere Labs shop