joewalnes/websocketd · error
failed to chmod unix socket %s to %o: %w
Error message
failed to chmod unix socket %s to %o: %w
What it means
After binding a Unix-domain socket, websocketd applies config.SocketMode via os.Chmod when --socket-mode (a non-zero value) is set, closing the window in which the umask default exposes the socket. If the chmod syscall fails, the listener is closed and startup aborts with this wrapped error (original errno included via %w).
Source
Thrown at main.go:83
fmt.Printf("%s | %-6s | %-10s | %s | %s\n", libwebsocketd.Timestamp(), levelName, category, assocDump, escapeControls(fullMsg))
l.Mutex.Unlock()
}
// serve listens on the given network ("tcp" or "unix") and address/path and
// runs an HTTP(S) server on it, honoring the Ssl/mutual-TLS config. It blocks
// until the listener errors out.
func serve(network, address string, config *Config, log *libwebsocketd.LogScope) error {
listener, err := net.Listen(network, address)
if err != nil {
return err
}
// Pin the Unix socket's permissions when asked: the umask default can
// leave the socket connectable by other local users. Chmod immediately
// after bind so the umask-derived window is as short as it can be.
if network == "unix" && config.SocketMode != 0 {
if err := os.Chmod(address, config.SocketMode); err != nil {
listener.Close()
return fmt.Errorf("failed to chmod unix socket %s to %o: %w", address, config.SocketMode, err)
}
}
if !config.Ssl {
return (&http.Server{ReadHeaderTimeout: readHeaderTimeout}).Serve(listener)
}
if config.SslCaFile != "" {
return serveMutualTLS(listener, config.CertFile, config.KeyFile, config.SslCaFile, log)
}
server := &http.Server{ReadHeaderTimeout: readHeaderTimeout, TLSConfig: tlsConfig()}
return server.ServeTLS(listener, config.CertFile, config.KeyFile)
}
// tlsConfig returns the base TLS settings shared by all HTTPS servers. It pins
// a minimum protocol version explicitly rather than relying on the Go default,
// which has drifted across releases.
func tlsConfig() *tls.Config {
return &tls.Config{MinVersion: tls.VersionTLS12}
}View on GitHub (pinned to 7a8683dc7f)
Solutions
- Check the wrapped errno in the message and verify the process owns the socket file (bind should have created it as the running user).
- Move the socket path to a directory writable by the service user (e.g. /run/websocketd/) instead of a system-managed location.
- Remove the sandbox/mount restriction blocking chmod, or disable --socket-mode (0 = umask default) if permissions are handled elsewhere.
- Confirm no competing process (cleanup unit, another instance) is deleting the socket during startup.
Example fix
# before websocketd --unixsocket=/var/run/app.sock --socket-mode=660 ./handler # after (socket in a directory the service user controls) websocketd --unixsocket=/run/websocketd/app.sock --socket-mode=660 ./handler
Defensive patterns
Strategy: validation
Validate before calling
const fi, _ := os.Stat(socketPath)
if err := syscall.Access(filepath.Dir(socketPath), syscall.W_OK); err != nil {
log.Fatalf("cannot chmod socket %s: no write access to %s: %v", socketPath, filepath.Dir(socketPath), err)
}
_ = fi Try / catch
if err := runWebsocketd(); err != nil {
var pathErr *os.PathError
if errors.As(err, &pathErr) && strings.Contains(err.Error(), "failed to chmod unix socket") {
log.Fatalf("socket chmod failed at %s: %v — check ownership of the socket directory", pathErr.Path, pathErr.Err)
}
return err
} Prevention
- Place sockets in a directory owned by the service user (systemd RuntimeDirectory).
- Review sandbox settings (ProtectSystem, ReadOnlyPaths) that block chmod on the socket path.
- Avoid network filesystems for Unix sockets; use local tmpfs/disk.
- Smoke-test the exact socket path and mode as the same user the daemon runs as.
When it happens
Trigger: os.Chmod on the freshly bound socket path fails: the process lost write/ownership rights on the path's directory between bind and chmod, the filesystem disallows chmod (some network mounts), the socket was unlinked by another process mid-startup, or an invalid mode was requested.
Common situations: Running under systemd with restrictive sandboxing (ProtectSystem, PrivateTmp) on a socket in a protected directory; shared tmpfs/NFS where chmod isn't supported; another service's cleanup racing to delete the socket file.
Related errors
- failed to remove stale socket %s: %w
- socket %s is already in use by a running server
- your %s '%s' is not pointing to an accessible directory
- cannot derive redirect address from %q: %w
- 500 Internal Server Error
AI-assisted analysis of joewalnes/websocketd@7a8683dc7f (2026-09-03).
Data as JSON: /api/errors/b1103eca8374fca5.
Report an issue: GitHub.