$RodHat_
Console Tips

socat is the Swiss Army knife you keep reaching past

Published by

black flat screen computer monitor
Photo: Ladislav Sh / Unsplash

Every sysadmin knows nc. Most of them use it like a hammer: forward a port, test a connection, transfer a file if they’re feeling ambitious. Then they hit a problem that netcat can’t solve — they need TLS, or a PTY, or a Unix socket on one end and TCP on the other — and they spend an hour writing something in Python that already exists.

socat (“SOcket CAT”) is the one that already exists. It speaks everything: TCP, UDP, Unix domain sockets, raw IP, TLS (via OpenSSL), PTYs, named pipes, files, and stdin/stdout. The model is two addresses and a bidirectional byte pump between them. Once you internalize that, the syntax stops looking weird.

The address model

socat ADDRESS1 ADDRESS2

Each address is a type, a target, and options, comma-separated. The pump runs until one side closes or you kill it.

socat TCP-LISTEN:8080,fork TCP:10.0.0.5:80

TCP-LISTEN:8080 — listen on 8080. fork — handle each connection in a child process so the listener stays up. TCP:10.0.0.5:80 — connect to the target. That’s a TCP proxy in one line, and unlike the nc loop you’ll find in Stack Overflow answers, fork means concurrent connections actually work.

reuseaddr is usually worth adding so the port doesn’t sit in TIME_WAIT between restarts:

socat TCP-LISTEN:8080,fork,reuseaddr TCP:10.0.0.5:80

Wrapping an unencrypted service in TLS

You have an internal service that speaks plaintext. Something upstream expects TLS. stunnel is the old answer. socat is the answer you don’t have to install separately:

socat TCP-LISTEN:8443,fork,reuseaddr \
  OPENSSL:10.0.0.5:8080,verify=0

Terminates TLS on 8443, forwards plaintext to the backend. verify=0 skips certificate validation — fine for an internal hop, not fine for anything client-facing. Flip it around for the other direction:

socat OPENSSL-LISTEN:8443,fork,reuseaddr,cert=/etc/ssl/server.pem,key=/etc/ssl/server.key \
  TCP:127.0.0.1:8080

Now socat is the TLS terminator. PEM format, cert and key, and you have a working HTTPS front door for a service that doesn’t know TLS exists.

The certificate concatenation gotcha: some tools want cert and key in one file, some want them separate. socat uses cert= for the certificate (can be a full chain) and key= for the private key. If it complains about the cert format, your PEM has CRLF line endings from a Windows CA, and dos2unix fixes it, not socat.

Bridging Unix sockets to TCP

Docker’s daemon socket is AF_UNIX. Your monitoring tool wants TCP. Do not expose the Docker socket directly to the network, you will regret it. Expose it through socat on localhost:

socat TCP-LISTEN:2375,fork,reuseaddr UNIX-CONNECT:/var/run/docker.sock

Now DOCKER_HOST=tcp://127.0.0.1:2375 works for local tooling without touching the daemon config. The same pattern works for PostgreSQL’s Unix socket if something insists on a TCP connection string, or for any service that defaults to Unix domain and a client that doesn’t support it.

Reverse it for the other direction — you have a service that only listens on TCP but something only speaks Unix sockets. The pump goes both directions.

PTYs for serial-over-TCP

You have a serial device on a remote machine. You want to interact with it from your workstation as if it were local. The right tool for this is usually proper terminal software, but when you don’t have it:

On the machine with the device:

socat TCP-LISTEN:5000,fork,reuseaddr /dev/ttyUSB0,raw,b115200

On your workstation:

socat PTY,link=/tmp/vserial,raw TCP:remote:5000

This creates /tmp/vserial, a PTY that behaves like a local serial port. Point your application at it. Everything it sends goes over TCP to the device. Everything the device sends comes back through the PTY.

b115200 sets the baud rate. raw disables all terminal line discipline processing — without it, the kernel helpfully interprets your binary data as control characters and mangles it. link= creates a stable symlink to the PTY, which gets a kernel-assigned name like /dev/pts/3 that changes every session.

Intercepting a connection mid-stream

This is the one people don’t think of: you have two processes that talk to each other and you want to see exactly what they’re saying. No code changes, no Wireshark requiring root, no TLS to deal with.

socat -v TCP-LISTEN:5432,fork,reuseaddr TCP:localhost:5433

Move the real service to 5433. Point the client at 5432. -v dumps every byte to stderr, with direction arrows. Now you can see exactly what your ORM thinks a parameterized query looks like, what the replication protocol’s handshake contains, or whether the client is sending the auth token before or after the connection is established.

-v gives you hex + ASCII. -x gives you just hex. For binary protocols, hex is usually more useful. For text protocols, -v is fine.

Add tee=/tmp/session.log to a PIPE address if you need to save the capture rather than watch it scroll. socat does not do this natively for both streams simultaneously — for that, string together two socat instances with named pipes:

mkfifo /tmp/down /tmp/up
socat -u TCP:server:5432 PIPE:/tmp/down &
socat -u PIPE:/tmp/up TCP:server:5432 &
tee /tmp/down-log.txt < /tmp/down | your-app

At that point you’re debugging something genuinely weird and probably should be.

One-shot file transfer

The classic:

Receiver:

socat TCP-LISTEN:9999,reuseaddr - > output.tar.gz

Sender:

socat TCP:receiver:9999 - < input.tar.gz

Yes, nc does this too. The advantage of socat: you can add OPENSSL on either side without changing the rest of the command. Encrypted transfer, no SSH, no scp, no keys to manage if this is a one-time internal thing.

The address types worth memorizing

TCP-LISTEN:port      listen for a connection
TCP:host:port        connect outward
UDP-LISTEN:port      UDP listener
UNIX-LISTEN:path     listen on a Unix socket
UNIX-CONNECT:path    connect to a Unix socket
OPENSSL-LISTEN:port  TLS listener
OPENSSL:host:port    TLS client
PTY                  allocate a pseudoterminal
STDIO or -           stdin/stdout
/dev/something       a device, file, or named pipe

Options that matter on almost every address: fork (handle multiple connections), reuseaddr (don’t wait for TIME_WAIT), raw (no terminal processing), b<rate> (baud, serial only), link=<path> (stable symlink for PTY).

The documentation is in man socat and it is genuinely thorough — one of those man pages that rewards reading rather than just being a flag reference. The examples section alone is worth an hour. The last time I saw a man page that good, it was Stevens’ work.

What socat will not do

It’s a byte pump. It has no visibility into protocol semantics — it doesn’t know HTTP from Postgres from garbage, and it doesn’t care. For TLS inspection it can terminate/re-originate TLS, but it cannot do MITM on TLS the way mitmproxy can, because it has no HTTP layer to insert itself into.

And it’s not a load balancer. The fork model is one listener, one backend. Round-robin across multiple backends: use a real proxy or wire up multiple socat instances behind a real front end.

For everything else in the space between “test a connection” and “deploy nginx”: socat.