The Challenge: Proxy with Only Netcat
A developer on DEV Community (source: proxy_pass with netcat) tackled an iximiuz Labs challenge: forward HTTP traffic from port 6000 to a server on port 5000 using nothing but netcat, bash, and Linux primitives. No nginx, no socat.
First Attempt: Simple Pipe Fails
The obvious solution: nc -l -p 6000 | nc 127.0.0.1 5000. This pipes the listening netcat's output into a second netcat that connects to the backend. But the response never returns to the client. The second netcat writes the server's response to stdout (the terminal), not back to the first netcat. Pipes are unidirectional; the return path is missing.
Closing the Loop with a Named Pipe
To send data back, the developer used a FIFO (named pipe):
mkfifo /tmp/nc
nc -l -p 6000 < /tmp/nc | nc 127.0.0.1 5000 > /tmp/nc
The first netcat reads from the FIFO as stdin, and the second netcat writes its stdout to the FIFO. This creates a bidirectional flow: the pipe carries the request forward, the FIFO carries the response back.
The trick works because netcat's core loop reads from stdin and writes to the socket, and reads from the socket and writes to stdout. This is not true for most daemons (nginx, PostgreSQL) which ignore stdin/stdout for client data.
One Successful Transaction, Then Death
With verbose output, the setup completes one request-response cycle:
listening on [any] 6000 ...
localhost [127.0.0.1] 5000 (?) open
connect to [127.0.0.1] from localhost [127.0.0.1] 56200
sent 78, rcvd 1092
sent 1092, rcvd 78
But then the pipeline dies. Even with -k on the first netcat, only one transaction works. Why?
SIGPIPE: The Silent Killer
When the backend server closes the connection (HTTP Connection: close), the second netcat sees EOF, finishes writing to the FIFO, and exits. This closes the read end of the pipe (|). The next time the first netcat tries to write to stdout (the pipe write end), the kernel sends SIGPIPE, killing it. The default SIGPIPE handler terminates the process.
The asymmetry: if the reader of a pipe dies, the writer gets SIGPIPE on next write. If the writer dies, the reader gets EOF (graceful). So -k can't save the first netcat from the broken pipe.
The Infinite Loop Fix
Instead of fighting the single-shot nature of the pipeline, the developer embraced it:
mkfifo /tmp/nc
while true; do
nc -l -p 6000 < /tmp/nc | nc 127.0.0.1 5000 > /tmp/nc
done
Drop -k. Each iteration of the loop creates a fresh pipeline: new listen, new backend connection. After one transaction, both netcat processes die, the FIFO is reopened, and the loop restarts. There's a tiny window where port 6000 isn't listening, but for a lab exercise it's acceptable.
Key Takeaways
- Netcat bridges stdin/stdout with a socket bidirectionally, unlike typical daemons.
- FIFOs solve the topology problem of connecting non-adjacent processes in a pipeline.
- SIGPIPE kills the writer when the pipe reader exits; ignoring it doesn't help if the reader is gone.
- An infinite loop is a pragmatic solution for single-shot pipelines.
The full exploration, including wrong turns and debugging, took the developer over two days. It's a deep dive into process plumbing that most developers never think about.
Further Reading
man 7 pipefor pipe and FIFO internalsman 1 ncfor netcat options- Viacheslav Biriukov's deep dive into pipe internals with code




