cloudflare/cloudflared · error

failed to accept QUIC stream: %w

Error message

failed to accept QUIC stream: %w

What it means

acceptStream loops calling q.conn.AcceptStream(ctx) to receive streams the edge opens. If AcceptStream errors for any reason other than an intentional context cancellation or a stopped control stream, the error is wrapped as `failed to accept QUIC stream` and returned, ending the Serve errgroup and triggering reconnection.

Source

Thrown at connection/quic_connection.go:163

// serveControlStream will serve the RPC; blocking until the control plane is done.
func (q *quicConnection) serveControlStream(ctx context.Context, controlStream *quic.Stream) error {
	return q.controlStreamHandler.ServeControlStream(ctx, controlStream, q.connOptions.ConnectionOptions(), q.orchestrator)
}

// Close the connection with no errors specified.
func (q *quicConnection) Close() {
	_ = q.conn.CloseWithError(0, "")
}

func (q *quicConnection) acceptStream(ctx context.Context) error {
	for {
		quicStream, err := q.conn.AcceptStream(ctx)
		if err != nil {
			// context.Canceled is usually a user ctrl+c. We don't want to log an error here as it's intentional.
			if errors.Is(err, context.Canceled) || q.controlStreamHandler.IsStopped() {
				return nil
			}
			return fmt.Errorf("failed to accept QUIC stream: %w", err)
		}
		go q.runStream(quicStream)
	}
}

func (q *quicConnection) runStream(quicStream *quic.Stream) {
	ctx := quicStream.Context()
	stream := cfdquic.NewSafeStreamCloser(quicStream, q.streamWriteTimeout, q.logger)
	defer func() { _ = stream.Close() }()

	// we are going to fuse readers/writers from stream <- cloudflared -> origin, and we want to guarantee that
	// code executed in the code path of handleStream don't trigger an earlier close to the downstream write stream.
	// So, we wrap the stream with a no-op write closer and only this method can actually close write side of the stream.
	// A call to close will simulate a close to the read-side, which will fail subsequent reads.
	noCloseStream := &nopCloserReadWriter{ReadWriteCloser: stream}
	ss := rpcquic.NewCloudflaredServer(q.handleDataStream, q.datagramHandler, q, q.rpcTimeout)
	if err := ss.Serve(ctx, noCloseStream); err != nil {
		q.logger.Debug().Err(err).Msg("Failed to handle QUIC stream")

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. This is expected on reconnect cycles — verify the supervisor re-establishes the tunnel; otherwise restart cloudflared
  2. Check firewall/NAT settings for UDP 443 idle timeouts; keep QUIC sessions alive or use --protocol http2 over TCP
  3. Check network stability between the host and the nearest Cloudflare edge
  4. Upgrade cloudflared if errors recur immediately after handshake
Defensive patterns

Strategy: retry

Try / catch

if err != nil && strings.Contains(err.Error(), "failed to accept QUIC stream") {
    // supervisor should reconnect; log and rely on backoff
    log.Warn().Err(err).Msg("QUIC stream accept failed; reconnecting")
}

Prevention

When it happens

Trigger: AcceptStream returns a transport error: the QUIC connection was closed/reset by the edge, idle timeout expired, network dropped, or a QUIC protocol violation — while ctx is still live and the control stream hasn't been stopped.

Common situations: Long-lived idle tunnels killed by NAT/firewall UDP timeouts, edge-side connection resets during deployments, mobile/network switching, or abrupt tunnel termination from the Cloudflare dashboard.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/9c84fd4979828eac. Report an issue: GitHub.