{"record":{"id":"4307451986d4361a","repo":"cloudflare/cloudflared","slug":"failed-to-open-a-registration-control-stream-w","errorCode":null,"errorMessage":"failed to open a registration control stream: %w","messagePattern":"failed to open a registration control stream: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"connection/quic_connection.go","lineNumber":91,"sourceCode":"\t\tlogger:               logger,\n\t\torchestrator:         orchestrator,\n\t\tdatagramHandler:      datagramSessionHandler,\n\t\tcontrolStreamHandler: controlStreamHandler,\n\t\tconnOptions:          connOptions,\n\t\tconnIndex:            connIndex,\n\t\trpcTimeout:           rpcTimeout,\n\t\tstreamWriteTimeout:   streamWriteTimeout,\n\t\tgracePeriod:          gracePeriod,\n\t}\n}\n\n// Serve starts a QUIC connection that begins accepting streams.\n// Returning a nil error means cloudflared will exit for good and will not attempt to reconnect.\nfunc (q *quicConnection) Serve(ctx context.Context) error {\n\t// The edge assumes the first stream is used for the control plane\n\tcontrolStream, err := q.conn.OpenStream()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open a registration control stream: %w\", err)\n\t}\n\n\t// If either goroutine returns a non nil error, then the error group cancels the context, thus also canceling the\n\t// other goroutines. We enforce returning a not-nil error for each function started in the errgroup by logging\n\t// the error returned and returning a custom error type instead.\n\terrGroup, ctx := errgroup.WithContext(ctx)\n\n\t// Close the quic connection if any of the following routines return from the errgroup (regardless of their error)\n\t// because they are no longer processing requests for the connection.\n\tdefer q.Close()\n\n\t// Start the control stream routine\n\terrGroup.Go(func() error {\n\t\t// err is equal to nil if we exit due to unregistration. If that happens we want to wait the full\n\t\t// amount of the grace period, allowing requests to finish before we cancel the context, which will\n\t\t// make cloudflared exit.\n\t\tif err := q.serveControlStream(ctx, controlStream); err == nil {\n\t\t\tif q.gracePeriod > 0 {","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/connection/quic_connection.go#L73-L109","documentation":"quicConnection.Serve assumes the first QUIC stream opened on the connection is the registration control plane stream. If q.conn.OpenStream() fails before the connection can even start serving, the error is wrapped as `failed to open a registration control stream` and returned, terminating the connection (reconnect logic on the caller side takes over).","triggerScenarios":"OpenStream() on the established QUIC connection fails — the edge closed the connection concurrently, the connection is draining/expired, or QUIC flow-control/concurrency limits block opening a new stream.","commonSituations":"Network path interruptions right after the QUIC handshake, edge draining the connection during maintenance, overly aggressive NAT/firewall timeouts dropping long-lived UDP sessions, or quic-go version incompatibilities.","solutions":["Let cloudflared retry: this is normally transient and the supervisor reconnects automatically","Ensure UDP port 443 outbound is allowed so QUIC can establish/maintain sessions (otherwise fall back with --protocol http2)","Update cloudflared to the latest release","Check for NAT/firewall idle timeouts and enable keepalives; inspect quic-go release notes if cloudflared was recently bumped"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-flight: confirm QUIC reachability (UDP 443) from the host before starting:\n// nc -vz -u <region>.argotunnel.com 443  || fall back to http2\n","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"failed to open a registration control stream\") {\n    // transient QUIC failure: backoff and retry Serve, or switch to --protocol http2\n    time.Sleep(backoff); go retryServe(ctx)\n}","preventionTips":["Allow outbound UDP 443 so QUIC sessions aren't blocked","Set firewall/NAT idle timeouts above cloudflared's keepalive interval","Use --protocol http2 (TCP) in networks where UDP is unreliable","Keep cloudflared updated"],"tags":["quic","network","stream","connection"],"backgroundTag":"connection-refused","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}