ginuerzh/gost · error
bad request
Error message
bad request
What it means
h2Listener.upgrade rejects an HTTP/2 request whose RequestURI does not match the listener's configured path (l.path != ""). The request reaches the handler but is not on the endpoint the listener was bound to, so it is answered with 400 Bad Request and this error.
Source
Thrown at http2.go:792
select {
case l.connChan <- conn:
default:
conn.Close()
log.Logf("[http2] %s - %s: connection queue is full", conn.RemoteAddr(), conn.LocalAddr())
}
<-conn.closed // NOTE: we need to wait for streaming end, or the connection will be closed
}
func (l *h2Listener) upgrade(w http.ResponseWriter, r *http.Request) (*http2Conn, error) {
if l.path == "" && r.Method != http.MethodConnect {
w.WriteHeader(http.StatusMethodNotAllowed)
return nil, errors.New("method not allowed")
}
if l.path != "" && r.RequestURI != l.path {
w.WriteHeader(http.StatusBadRequest)
return nil, errors.New("bad request")
}
w.WriteHeader(http.StatusOK)
if fw, ok := w.(http.Flusher); ok {
fw.Flush() // write header to client
}
remoteAddr, _ := net.ResolveTCPAddr("tcp", r.RemoteAddr)
if remoteAddr == nil {
remoteAddr = &net.TCPAddr{
IP: net.IPv4zero,
Port: 0,
}
}
conn := &http2Conn{
r: r.Body,
w: flushWriter{w},
localAddr: l.Listener.Addr(),View on GitHub (pinned to a33fdbf4c9)
Solutions
- Make the client dial URI exactly match the path configured on the h2Listener (including leading slash and no extra segments).
- Remove the path restriction from the listener if it should accept any request.
- Log r.RequestURI server-side to compare against the configured l.path and correct the mismatch.
- If using HTTP/2 CONNECT tunneling, do not bind the listener to a path, since CONNECT requests may not carry the expected URI.
Example fix
// before (mismatched paths)
server: NewH2Listener(host, WithPath("/tunnel"))
client: dial("https://host/ws")
// after
server: NewH2Listener(host, WithPath("/tunnel"))
client: dial("https://host/tunnel") Defensive patterns
Strategy: validation
Validate before calling
// client side: URI must match the server listener path exactly
if serverPath != "" && reqURI != serverPath {
return fmt.Errorf("request URI %q does not match configured h2 path %q", reqURI, serverPath)
} Try / catch
conn, err := dialer.Dial(ctx, network, addr)
if err != nil {
if strings.Contains(err.Error(), "bad request") {
return fmt.Errorf("check h2 listener path vs dial URI: %w", err)
}
return err
} Prevention
- Store the h2 path in shared config used by both client and server.
- Watch for base-URL joins that add/drop path segments.
- Avoid binding a path-restricted listener if you also accept raw CONNECT traffic.
When it happens
Trigger: Client dials an h2Listener created with a specific path (e.g. "/tunnel") but sends the request to a different URI, or sends a CONNECT (empty RequestURI in HTTP/2) to a path-bound listener.
Common situations: Client/server path configuration mismatch (one side configured with "/ws", other with "/tunnel"); base-URL handling on the client stripping or adding path segments; HTTP/2 CONNECT requests which carry :path differently than the literal configured path.
Related errors
AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02).
Data as JSON: /api/errors/7e81e59f4a90b930.
Report an issue: GitHub.