Skip to content

socat: Forwarding Unix Sockets Over TCP

Sometimes you need to reach a Unix socket from a host where that socket doesn’t physically exist. SSH tunnels won’t help — they only work with TCP ports. socat solves this: it opens a TCP listener and forwards connections to a Unix socket, and the client just connects over the network.

Installation

The package is available in every major distribution. On Debian/Ubuntu:

apt install socat

On RHEL/CentOS:

yum install socat
# or
dnf install socat

Alpine:

apk add socat

Verify:

socat -V
# socat version 1.7.4.4

Basic Forwarding: TCP-LISTEN + UNIX-CONNECT

Server side. Listen on a TCP port and redirect traffic to a Unix socket on connection:

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

Flags:

  • TCP-LISTEN:2375 — opens port 2375
  • fork — spawns a child process for each connection; without it socat accepts one connection and exits

On the client side, work as usual — for example, curl the Docker API:

curl http://localhost:2375/version

If the client is on a remote host, specify the server IP:

curl http://192.168.1.100:2375/version
Warning

Docker listens on the local socket by default. Exposing TCP-LISTEN externally without TLS or firewall is a risk. Restrict the bind to an interface: TCP-LISTEN:2375,bind=127.0.0.1.

Stop the forward — Ctrl+C or kill by PID.

Client Test via STDIO

To quickly verify socket availability or send a manual command, use STDIO on the client side:

socat STDIO UNIX-CONNECT:/var/run/docker.sock

After starting, enter raw HTTP requests. Example session:

socat STDIO UNIX-CONNECT:/var/run/docker.sock
GET /version HTTP/1.0

HTTP/1.1 200 OK
Content-Type: application/json
{"ApiVersion":"1.45","Version":"24.0.7"...}

Exit — Ctrl+D or Ctrl+C. This is handy for debugging APIs without curl and without setting environment variables.

For a TCP connection over the network, the client runs symmetrically:

socat STDIO TCP:192.168.1.100:2375

Abstract vs Filesystem Sockets

Unix sockets come in two types. The difference matters for socat operation.

Filesystem sockets — bound to the filesystem. Path starts with /:

/var/run/docker.sock
/run/user/1000/pulse/runtime/native
/tmp/mysql.sock

Abstract sockets — live in kernel memory, have no filesystem representation. Path starts with \0 or @ (ASCII zero and at-sign). Docker in rootless mode uses these:

# Displaying abstract socket in ls
ls -la /run/user/1000/docker.sock
# srwxr-xr-x 1 user user 0 Jan 15 10:00 /run/user/1000/docker.sock

# Actual path in kernel starts with \0
# In socat, write:
socat TCP-LISTEN:2375,fork UNIX-CONNECT:@/docker.sock

Check socket type:

ss -x | grep docker
# u_str  LISTEN  0  4096  /run/user/1000/docker.sock  12345  * 0

# If path starts with @ — abstract

In socat syntax:

  • @/path/to/socket — abstract socket
  • /path/to/socket — filesystem socket
Note

Abstract sockets are invisible to processes without namespace access. This is an advantage for isolation but complicates forwarding between containers.

Timeout Flags

By default, socat waits forever. For automation and scripts, you need timeouts.

FlagDescription
readtimeout=SECONDSRead timeout
writetimeout=SECONDSWrite timeout
timeout=SECONDSTimeout for both operations

Example with a general timeout:

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

Connection closes after 30 seconds of inactivity.

Separate timeouts for client and server:

# Server waits 10 sec for write, client waits 5 sec for read
socat TCP-LISTEN:2375,forever,writewait=10 UNIX-CONNECT:/var/run/docker.sock,readtimeout=5

In cron scripts or systemd units, set a timeout or the process will hang on network disruption:

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

For infinite waiting without fork, forever works, but in production combine it with system limits:

socat TCP-LISTEN:2375,reuseaddr,timeout=0 UNIX-CONNECT:/var/run/docker.sock
Tip

reuseaddr lets you quickly restart socat without “Address already in use” errors.

Common Errors

Permission denied accessing the socket

# Check permissions
ls -la /var/run/docker.sock
# srw-rw---- 1 root docker

# Add user to the group
usermod -aG docker username

Connection refused

Verify socat is running and listening on the port:

ss -tlnp | grep 2375
# LISTEN 0 5 *:2375 *:*  users:(("socat",pid=1234))

Firewall:

iptables -L -n | grep 2375
# ACCEPT  tcp  --  0.0.0.0/0  0.0.0.0/0  tcp dpt:2375

One request — and socat dies

Missing fork. Each socat instance handles one connection and exits. Add the flag:

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

Abstract socket not found

Ensure the correct prefix. Docker in rootless uses @, but this is ASCII 0:

# Shows actual path in kernel
cat /proc/$(pgrep dockerd)/net/unix | grep docker

In socat, write @/docker.sock, not @@/docker.sock.

Systemd Service for Persistent Forwarding

For a permanent forward, wrap it in a systemd unit:

[Unit]
Description=socat Docker socket forwarder
After=network.target

[Service]
ExecStart=/usr/bin/socat TCP-LISTEN:2375,fork,reuseaddr,timeout=60 UNIX-CONNECT:/var/run/docker.sock
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
systemctl enable socat-docker-forward
systemctl start socat-docker-forward

Don’t forget to restrict the bind to an interface if you don’t want the port exposed externally.

In Closing

socat is the Unix way for transparent forwarding of anything to anywhere. For Unix sockets over TCP, two processes and a minute of configuration are enough. Keep timeouts in mind, don’t forget fork, and don’t expose ports without authentication on public networks.