joewalnes/websocketd · error
500 Internal Server Error
Error message
500 Internal Server Error
What it means
This is the generic catch-all response sent by serveWebSocket (libwebsocketd/http.go:110) when NewWebsocketdHandler returns an error that is NOT ErrScriptNotFound. The server logs 'INTERNAL ERROR' with the underlying cause at access-log level and writes an HTTP 500 with the plain body '500 Internal Server Error'. It means the WebSocket upgrade request could not be turned into a handler for a reason other than 'the script does not exist' — e.g. the script path was found but is not executable, permission was denied, or command configuration is invalid. The specific cause is only visible in the server log, not in the response body.
Source
Thrown at libwebsocketd/http.go:132
if !isWebSocketUpgrade(req) {
return false
}
if h.noteForkCreated() != nil {
log.Error("http", "Max of possible forks already active, upgrade rejected")
http.Error(w, "429 Too Many Requests", http.StatusTooManyRequests)
return true
}
defer h.noteForkCompleted()
handler, err := NewWebsocketdHandler(h, req, log)
if err != nil {
if err == ErrScriptNotFound {
log.Access("session", "NOT FOUND: %s", err)
http.Error(w, "404 Not Found", 404)
} else {
log.Access("session", "INTERNAL ERROR: %s", err)
http.Error(w, "500 Internal Server Error", 500)
}
return true
}
var headers http.Header
if len(h.Config.Headers)+len(h.Config.HeadersWs) > 0 {
headers = http.Header(make(map[string][]string))
pushHeaders(headers, h.Config.Headers)
pushHeaders(headers, h.Config.HeadersWs)
}
upgrader := &websocket.Upgrader{
HandshakeTimeout: h.Config.HandshakeTimeout,
CheckOrigin: func(r *http.Request) bool {
return checkOrigin(req, h.Config, log) == nil
},
}
conn, err := upgrader.Upgrade(w, req, headers)View on GitHub (pinned to 7a8683dc7f)
Solutions
- Check the websocketd server log for the 'session INTERNAL ERROR: <err>' line — it contains the real underlying error (e.g. permission denied).
- Make the script executable: chmod +x /path/to/script, and verify ownership/readability by the user running websocketd.
- Verify the --command or scriptdir entry actually points to an executable file (not a directory) reachable under the server's PATH.
- Test the script locally the way websocketd runs it (e.g. run it manually reading stdin/writing stdout) to rule out interpreter/shebang problems.
- If the file truly does not exist, expect a 404 instead — a 500 here means the failure mode is environmental (permissions, exec, config).
Example fix
// before: script present but not executable -> 500 $ ls -l handler.sh -rw-r--r-- 1 user user handler.sh // after: make it executable $ chmod +x handler.sh $ websocketd --port=8080 ./handler.sh
Defensive patterns
Strategy: validation
Validate before calling
// Before pointing websocketd at the script, verify it is an executable regular file
func scriptRunnable(path string) error {
fi, err := os.Stat(path)
if err != nil {
return fmt.Errorf("script missing: %w", err)
}
if fi.IsDir() {
return fmt.Errorf("%s is a directory, not a script", path)
}
if fi.Mode()&0o111 == 0 {
return fmt.Errorf("%s is not executable (chmod +x)", path)
}
return nil
} Try / catch
// 500 carries no detail; correlate with server logs. Watch the access log for the real cause:
// grep 'session INTERNAL ERROR' websocketd.log
// On the client, treat any non-101 WebSocket handshake response as fatal:
conn, resp, err := websocket.DefaultDialer.Dial(wsURL, nil)
if err != nil {
if resp != nil && resp.StatusCode == http.StatusInternalServerError {
log.Fatalf("server failed to start script (500); check server logs: %v", err)
}
return err
} Prevention
- Always chmod +x scripts before deployment and verify with ls -l or a CI check.
- Smoke-test the exact --command/scriptdir path with the same user websocketd runs as (systemd User=, container USER).
- Keep script interpreter shebangs absolute and present in the deployment image.
- Monitor server logs for 'session INTERNAL ERROR' lines and alert on them.
When it happens
Trigger: A WebSocket upgrade request hits a server configured with --command or --scriptdir, noteForkCreated succeeds, but NewWebsocketdHandler fails with an error other than ErrScriptNotFound: e.g. the resolved script file exists but lacks the executable bit (permission denied), the script path is a directory, the configured --command binary cannot be found/executed by the OS, or constructing the handler from a malformed URL/request path fails in an unexpected way.
Common situations: Deploying a script without 'chmod +x'; uploading a script owned by a user the websocketd process cannot execute; pointing --command at a binary that is absent at runtime (PATH differs under systemd/containers); a scriptdir entry that is a directory rather than a file; SELinux/AppArmor denying exec; typos in the script path that resolve to something existing but invalid.
Understand the failure class
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
Related errors
- script not found
- too many forks active
- --maxframesize must not be negative; use 0 for unlimited
- your %s '%s' is not pointing to an accessible directory
- could not resolve script for path %q
AI-assisted analysis of joewalnes/websocketd@7a8683dc7f (2026-09-03).
Data as JSON: /api/errors/482d032f2c446792.
Report an issue: GitHub.