# websocketd: turning any STDIN/STDOUT program into a WebSocket server

> websocketd wraps an existing command line program and serves it over WebSockets, one process per connection. It is a good fit for scripts that already stream lines of text, and the wrong tool for anything that needs shared state or binary framing.

**joewalnes/websocketd** — Turn any program that uses STDIN/STDOUT into a WebSocket server. Like inetd, but for WebSockets. 

- Repository: https://github.com/joewalnes/websocketd
- Website: http://websocketd.com/
- Stars: 17,471 · Forks: 1,008
- Language: Go
- License: BSD-2-Clause
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/joewalnes-websocketd

## What websocketd actually removes from your stack

The project describes itself as a small command line tool that wraps an existing command line interface program and allows it to be accessed via a WebSocket. The claim that matters is narrower than it sounds: as long as you can write an executable program that reads STDIN and writes STDOUT, you can build a WebSocket server, in any language, with no networking libraries necessary. That is the whole value proposition. If your program already prints lines, you do not write a socket handler, a connection loop, or a framing layer.

The audience is people who have a working command line tool and want it behind a browser. The README lists Bash, Python, Ruby, Perl, C, Go, PHP, Java, Clojure, Scala, Groovy, Expect, Awk, VBScript, PowerShell, Haskell, Lua and R as viable, and the examples directory in the repository carries subdirectories for bash, csharp, fsharp, haskell, java, lua, nodejs, perl, php, powershell, python, qjs, ruby, rust, swift, windows-jscript and windows-vbscript. That breadth is the point: the tool does not care what produced the bytes.

The framing is inetd-like. websocketd owns the listening socket and the process lifecycle; your program owns only its own input and output.

## One process per connection, and newline as the message boundary

The mechanism is documented plainly. On startup websocketd starts a WebSocket server on a specified port and listens for connections. On a connection it forks the appropriate process, and disconnects the process when the WebSocket connection closes, and vice versa. That is a per-connection process model, not a shared worker pool. Each client gets its own instance of your script, with its own memory and its own lifetime.

The data flow is line oriented in both directions. Any message sent from the WebSocket client is piped to the process's STDIN followed by a newline. Any text the process prints to STDOUT is sent as a WebSocket message whenever a newline is encountered. So a newline is the message delimiter, and partial lines sit in a buffer until one arrives. A program that writes a large block without newlines will not deliver anything until the newline shows up or the buffer is flushed by termination.

Teardown is process-group wide. When a client disconnects, the command is shut down, and so is any child process it spawned. The README notes that scripts which deliberately spawn survivors should start them in their own session with setsid. That is a real design decision with a real consequence: a script that backgrounds a long-running helper will lose it on disconnect unless it detaches explicitly.

Two details sit on top of the core loop. STDERR can optionally be forwarded to clients with --passstderr, tagged alongside STDOUT as JSON so a client can tell the two apart. And the server side script can read details about the WebSocket HTTP request (remote host, query parameters, cookies, path) through standard CGI environment variables. The README warns that SERVER_NAME and SERVER_PORT mirror the request's Host header, virtual-host style, as in net/http/cgi, so a client that controls its Host header controls them. Do not make access decisions on them.

## Installing websocketd and running a first script

On macOS the README gives a Homebrew path. For other operating systems it points to the download page for Linux, macOS and Windows, and the project's stated install story is a single executable with no installer, no package manager and no external libraries.

```bash
brew install websocketd
```

To see the model end to end, take the README's counting example. The script prints a number, sleeps a second, and repeats ten times, then exits. It runs identically on the command line and behind the server, which is the property the README emphasizes: no modifications required.

```bash
#!/bin/bash
for ((COUNT = 1; COUNT <= 10; COUNT++)); do
  echo $COUNT
  sleep 1
done
```

Make it executable and check it standalone before involving the network. You should see the numbers 1 through 10, one per second, on your terminal.

```bash
chmod +x count.sh
./count.sh
```

Now serve it. The port flag is --port and the command is the script path. The server starts, listens, and forks count.sh each time a client connects.

```bash
websocketd --port=8080 ./count.sh
```

A browser page can connect with a plain WebSocket object. The README's example logs CONNECT on open, DISCONNECT on close, and each message body as it arrives, and notes that it works even when opened directly from disk with a file:// URL.

```html
<script>
  const ws = new WebSocket('ws://localhost:8080/');
  ws.onopen = () => console.log('CONNECT');
  ws.onclose = () => console.log('DISCONNECT');
  ws.onmessage = (event) => console.log('MESSAGE: ' + event.data);
</script>
```

If you would rather not write client code yet, websocketd ships a built-in developer console behind --devconsole. Connect, send a frame, and read back what the script wrote to stdout, with a frame list beside a detail pane showing opcode, size, and pretty, raw or hex payload views.

## Where the STDIN/STDOUT contract breaks down

The newline rule is the sharpest limitation. A program whose output is not line delimited, or whose protocol depends on byte offsets or length prefixes, does not fit. You can wrap it, but you will be inventing an encoding inside your script and decoding it on the client, at which point websocketd is only saving you the socket setup.

Binary data is the same problem stated differently. The documented path is text printed to STDOUT, split on newlines. There is no documented binary frame mode in the README.

The per-connection process model is the second constraint. Ten browser tabs means ten copies of your script. A script that reads a large file on startup pays that cost ten times. A script that holds a lock, writes to a shared file, or talks to a single-threaded backend will contend with itself. There is no built-in broadcast: one client's message goes to that client's process only. If you need fan-out, the process model fights you.

Process-group teardown is the third. It is the right default for a wrapper, and it is a trap for scripts that assume background work survives. The README's own guidance is setsid for deliberate survivors.

Finally, the CGI environment variables are convenient and not an authorization mechanism. The README is explicit about SERVER_NAME and SERVER_PORT reflecting the client's Host header. Any script that gates behaviour on those values is gating on client input.

## How websocketd differs from websocat and wstunnel

websocat and wstunnel occupy nearby search space but solve different problems, and the difference is worth stating precisely because people arrive at websocketd looking for one of the other two.

websocketd is a server that spawns a process per connection and maps newline-delimited STDOUT to WebSocket messages. The process is the unit of state, and the message boundary is a newline.

websocat is a general purpose WebSocket client and server for the command line, aimed at connecting streams and sockets to WebSocket endpoints. Where websocketd gives you a listening port and a forked script, websocat gives you a tool you compose into pipelines and point at existing endpoints. If your goal is to attach a local process to a remote WebSocket, or to relay between two sockets, websocketd is not the tool.

wstunnel is about tunnelling traffic over WebSocket, typically to get around network restrictions. It does not care about your program's STDOUT. If you want to carry arbitrary TCP or HTTP through a WebSocket connection, that is a different layer than wrapping a script.

The practical test: if you have a program that prints lines and you want browsers to read those lines, websocketd removes the most code. If you have a stream to bridge or a tunnel to build, it does not.

## Maintenance, build requirements and licence

The repository is not archived, and its last push was on 2026-09-07. That is recent activity on the repository, though the most recent tagged release listed is v0.4.1 from January 24, 2021, with v0.3.1 before it in 2019 and v0.3.0 in 2017. Releases are infrequent; the commit history is the better signal of current work.

Building from source uses the system Go toolchain. The Makefile defines a single default target that runs go build against the top-level Go files and libwebsocketd, plus a clean target that removes the websocketd binary. go.mod declares the module as github.com/joewalnes/websocketd and requires Go 1.21, with github.com/gorilla/websocket v1.5.3 as the sole direct dependency. That is a small dependency surface for a server that terminates WebSocket connections, and it keeps the upgrade cost low: a Go toolchain, one module, and go build.

The licence is BSD-2-Clause, and the source headers refer to it as a BSD-style license found in the LICENSE file. That is a permissive licence, which generally means you can redistribute and modify with the copyright notice retained, but the terms are in the LICENSE file and this is not legal advice. If you embed websocketd in a distributed product, read that file rather than a summary.

Upgrade cost is mostly about the Go toolchain floor. Go 1.21 in go.mod means anyone building from source needs a toolchain at or above that version. Users of the prebuilt binaries do not, and the README's install path is a download rather than a build.

## Conclusion

Adopt websocketd when you already have a command line program that streams newline-delimited text and you want it reachable from a browser without writing a networking layer. Skip it when connections must share state, when you need binary framing, or when the protocol is not line oriented, because the STDIN/STDOUT contract is the whole design. Before deploying, verify how your script behaves when its process group receives a teardown signal on disconnect, and decide whether a survivor process needs setsid. Check the CGI environment variables your script reads, and do not make access decisions on SERVER_NAME or SERVER_PORT, since the README states they mirror the request Host header and a client that controls that header controls them too.

## FAQ

### How do I install websocketd on macOS?

The README gives a Homebrew command, brew install websocketd. For other operating systems it points to the download page for Linux, macOS and Windows, where the install story is a single executable with no installer or package manager.

### Does websocketd require any networking libraries in my script?

No. The README states that as long as you can write an executable program that reads STDIN and writes STDOUT, you can build a WebSocket server, with no networking libraries necessary, in any of the listed languages.

### How does websocketd decide where one WebSocket message ends?

A newline is the boundary. The README says any text the process prints to STDOUT is sent as a WebSocket message whenever a newline is encountered, and any message from the client is piped to the process's STDIN followed by a newline.

### What happens to child processes when a websocketd client disconnects?

The README states that when a client disconnects, the command is shut down, and so is any child process it spawned, because teardown signals the whole process group. Scripts that deliberately spawn survivors should start them in their own session with setsid.

### Can I see STDERR output from my script in the WebSocket client?

Yes, optionally. The README documents --passstderr, which forwards STDERR to WebSocket clients tagged alongside STDOUT as JSON so a client can tell the two apart.

### What are the downsides of using WebSockets with websocketd?

The documented model forks a process per connection and splits messages on newlines, so connections do not share state and there is no built-in broadcast. Programs that are not line oriented, or that emit binary data, do not fit the STDIN/STDOUT contract without inventing an encoding of your own.

## Sources

- [joewalnes/websocketd on GitHub](https://github.com/joewalnes/websocketd)
- [License: BSD-2-Clause](https://github.com/joewalnes/websocketd/blob/main/LICENSE)
- [Project website](http://websocketd.com/)
- [README](https://github.com/joewalnes/websocketd/blob/main/README.md)
- [Releases](https://github.com/joewalnes/websocketd/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/joewalnes-websocketd
