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 PORTdials out (client);nc -l PORTwaits (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-wfires). - 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:
ncglues 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-Ndiffer 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
ncby default;brew install netcatgets the GNU build instead. - Flags differ across builds.
-e,-k, and-qvary by variant (OpenBSD, GNU/traditional, Ncat, Busybox), so confirm withnc -hbefore trusting a tutorial.
Flags you’ll actually use#
Skim these first; the cheat sheet below is these flags combined.
| Flag | Does | Flag | Does |
|---|---|---|---|
-l | Listen for a connection | -w N | Timeout / idle cutoff after N seconds |
-v | Verbose (print connect status) | -z | Zero-I/O scan (connect, send nothing) |
-n | No DNS, numeric addresses only | -k | Keep listening after a client leaves |
-u | UDP instead of TCP | -q N | Quit N seconds after stdin EOF |
-p N | Source / listen port | -N | Shut the socket down on EOF (OpenBSD) |
-4 / -6 | Force IPv4 / IPv6 | -C | Send CRLF line endings (OpenBSD) |
-s ADDR | Set the source address | -U | Use a Unix-domain socket path |
-x / -X | Proxy address / proxy protocol | -b | Allow UDP broadcast |
-d | Do not read stdin (listen only) | -e | Run 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 see | Meaning | Do next |
|---|---|---|
succeeded! / open | Path works, something is listening | Grab a banner or speak the protocol |
Connection refused | Host is up, port is closed (RST) | Definite “no”, cross it off |
Hangs, then times out (-w) | No reply, filtered or dropped by a firewall | Note it; not the same as closed |
| Banner text on connect | Service speaks first (SSH/FTP/SMTP) | Clue to the service, not proof of version |
| Connected, no banner | Service waits for the client (HTTP) | Send a request to get anything back |
| UDP silence | Ambiguous by design | Investigate; 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#
| Problem | Cause | Fix |
|---|---|---|
command not found | Not installed / stale PATH | Reinstall; open a fresh shell |
| Flag not recognized | Wrong variant | Check nc -h; switch to Ncat |
Permission denied on -l | Low port (<1024) without root | Use a high port, or sudo |
| Command hangs forever | No timeout on a filtered port | Add -w3 |
Connection refused | Nothing on that port | Confirm port/service (scan with Nmap) |
Connection timed out | Firewall dropping packets | Expected when filtered; note and move on |
Could not resolve / slow | DNS failure or lookup | Add -n and use the raw IP |
| Listener gets no data | Local firewall blocking it | Allow the port; test on loopback first |
| File transfer never ends | Sender does not close the stream | Add -q1 (or -N) on the sender |
Gotchas#
-e /bin/bashdoes not exist in OpenBSDnc(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
-zsuccess is not “open”. -wis 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
--sslorsshwhen 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.