# socat: Forwarding Unix Sockets Over TCP

LLMS index: [llms.txt](/en/llms.txt)

---

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:

```bash
apt install socat
```

On RHEL/CentOS:

```bash
yum install socat
# or
dnf install socat
```

Alpine:

```bash
apk add socat
```

Verify:

```bash
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:

```bash
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:

```bash
curl http://localhost:2375/version
```

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

```bash
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:

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

After starting, enter raw HTTP requests. Example session:

```bash
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:

```bash
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 `/`:

```bash
/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:

```bash
# 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:

```bash
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.

| Flag | Description |
|------|-------------|
| `readtimeout=SECONDS` | Read timeout |
| `writetimeout=SECONDS` | Write timeout |
| `timeout=SECONDS` | Timeout for both operations |

Example with a general timeout:

```bash
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:

```bash
# 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:

```bash
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:

```bash
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**

```bash
# 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:

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

Firewall:

```bash
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:

```bash
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:

```bash
# 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:

```ini
[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
```

```bash
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.
