joewalnes/websocketd · error

socket %s is already in use by a running server

Error message

socket %s is already in use by a running server

What it means

Before serving on a Unix socket path, websocketd checks an existing socket file by dialing it. If the connect succeeds within the 1s probe timeout, a live server is still listening; starting a second instance would orphan the first, so startup is refused with this error instead of silently stealing the socket.

Source

Thrown at main.go:146

// timeout only guards against a pathological listener that accepts nothing.
const unixSocketProbeTimeout = time.Second

// serveUnixSocket removes a stale socket file left behind by an unclean
// shutdown (if any) and then serves on it. It only ever removes a path that
// is actually a socket, never an arbitrary file that happens to be there.
//
// A socket file at the path may equally belong to a server that is still
// running, so removing it unconditionally is not safe: unlinking a live
// server's socket and binding a new one in its place leaves that process
// running but permanently unreachable, with no error on either side. Probing
// first tells the two apart — a successful dial means someone is listening, a
// refused connection means the file is stale. Refusing to start on a live
// socket matches what a TCP listener already does when its port is taken.
func serveUnixSocket(path string, config *Config, log *libwebsocketd.LogScope) error {
	if info, err := os.Stat(path); err == nil && info.Mode()&os.ModeSocket != 0 {
		if conn, err := net.DialTimeout("unix", path, unixSocketProbeTimeout); err == nil {
			conn.Close()
			return fmt.Errorf("socket %s is already in use by a running server", path)
		}
		if err := os.Remove(path); err != nil {
			return fmt.Errorf("failed to remove stale socket %s: %w", path, err)
		}
	}
	return serve("unix", path, config, log)
}

// redirectAddress returns addr with its port replaced by redirPort. IPv6
// literals must be split with net.SplitHostPort (which understands brackets);
// splitting on the first colon lands inside "[::1]:port" and produced a
// malformed listener address that failed to bind — and, being a listener
// error, killed every other listener too.
func redirectAddress(addr string, redirPort int) (string, error) {
	host, _, err := net.SplitHostPort(addr)
	if err != nil {
		return "", fmt.Errorf("cannot derive redirect address from %q: %w", addr, err)
	}

View on GitHub (pinned to 7a8683dc7f)

Solutions

  1. Stop the existing process: find it with `lsof /run/app.sock` or `ss -x | grep app.sock`, then kill/restart it properly.
  2. If the old instance should have died, ensure your supervisor waits for process exit before restarting.
  3. Use a distinct socket path per instance (e.g. include a PID or environment name).
  4. Note this is deliberate protection: a stale socket is removed automatically; only a live listener triggers this error, so don't force-delete the file — that would strand the running server.

Example fix

# before (fails while old server still runs)
websocketd --unixsocket=/run/app.sock ./handler
# after
systemctl stop websocketd-old && websocketd --unixsocket=/run/app.sock ./handler
Defensive patterns

Strategy: try-catch

Validate before calling

#!/bin/sh
if lsof "${SOCKET_PATH}" >/dev/null 2>&1; then echo "socket in use; stopping old instance"; pkill -f "unixsocket=${SOCKET_PATH}" || exit 1; fi

Try / catch

out, err := exec.Command("websocketd", "--unixsocket", sock, ...).CombinedOutput()
if err != nil && strings.Contains(string(out), "already in use by a running server") {
    stopExistingInstance(sock) // systemd stop / pkill, then retry once
    err = exec.Command("websocketd", "--unixsocket", sock, ...).Run()
}

Prevention

When it happens

Trigger: Running `websocketd --unixsocket=/run/app.sock ...` (via intArg/main) while another websocketd (or any AF_UNIX listener) already owns that socket path and accepts connections.

Common situations: Duplicate systemd unit instance; forgetting a previous dev instance in another terminal; supervisor restarting the app before the old process exits; running the same command in two containers sharing a host-mounted socket directory.

Related errors


AI-assisted analysis of joewalnes/websocketd@7a8683dc7f (2026-09-03). Data as JSON: /api/errors/009d1f8db49a1ed0. Report an issue: GitHub.