cloudflare/cloudflared · warning
timeout waiting for second stream to finish
Error message
timeout waiting for second stream to finish
What it means
The stream `wait` helper in cloudflared's bidirectional pipe waits up to maxWaitForSecondStream for the second stream (the opposite direction) to appear or for doneChan to close. If the timer fires first, it means only one side of the proxied conversation finished/started and the paired stream never arrived within the deadline, so PipeBidirectional is aborted with this error.
Source
Thrown at stream/stream.go:74
}
}
func (s *bidirectionalStreamStatus) markUniStreamDone() {
atomic.StoreUint32(&s.anyDone, 1)
s.doneChan <- struct{}{}
}
func (s *bidirectionalStreamStatus) wait(maxWaitForSecondStream time.Duration) error {
<-s.doneChan
// Only wait for second stream to finish if maxWait is greater than zero
if maxWaitForSecondStream > 0 {
timer := time.NewTimer(maxWaitForSecondStream)
defer timer.Stop()
select {
case <-timer.C:
return fmt.Errorf("timeout waiting for second stream to finish")
case <-s.doneChan:
return nil
}
}
return nil
}
func (s *bidirectionalStreamStatus) isAnyDone() bool {
return atomic.LoadUint32(&s.anyDone) > 0
}
// Pipe copies copy data to & from provided io.ReadWriters.
func Pipe(tunnelConn, originConn io.ReadWriter, log *zerolog.Logger) {
_ = PipeBidirectional(NopCloseWriterAdapter(tunnelConn), NopCloseWriterAdapter(originConn), 0, log)
}
// PipeBidirectional copies data to two unidirectional streams. It is a special case of Pipe where it receives a concept that allows for Read and Write side to be closed independently.
// The main difference is that when piping data from a reader to a writer, if EOF is read, then this implementation propagates the EOF signal to the destination/writer by closing the write side of theView on GitHub (pinned to 2253eeeb25)
Solutions
- Check that cloudflared and Cloudflare edge versions are current; older mixed versions can frame streams differently.
- Verify the origin application actually uses both directions; a single-stream application design will inherently hit this wait.
- Tune maxWaitForSecondStream upward if legitimate slow second streams are being cut off.
- Investigate the client for half-open/idle connections and enable TCP keepalives to reap them.
Defensive patterns
Strategy: try-catch
Validate before calling
// before calling: ensure a token is present and CSR is valid
token, ok := os.LookupEnv("TUNNEL_TOKEN")
if !ok || token == "" {
return errors.New("missing Cloudflare token for cert signing")
}
if _, err := x509.ParseCertificateRequest(csr); err != nil {
return fmt.Errorf("invalid CSR: %w", err)
} Try / catch
cert, err := sshgen.SignCert(ctx, client, hostnames, csr)
if err != nil {
var status int
if _, scanErr := fmt.Sscanf(err.Error(), "%d:", &status); scanErr == nil && (status == 429 || status >= 500) {
// transient: retry with backoff
}
return err
} Prevention
- Keep the Cloudflare API/tunnel token valid and scoped before running ssh-gen.
- Validate CSR key type and principals before submission.
- Handle 429/5xx statuses with retries; treat 400/401/403 as configuration fixes.
When it happens
Trigger: PipeBidirectional is called for a connection (e.g. TCP-over-WebSocket/QUIC) where only one data stream is ever opened and the peer never initiates the second stream before maxWaitForSecondStream elapses; protocol mismatch where one side expects one stream and the other opens two.
Common situations: Version mismatches between cloudflared and edge on connection framing; half-open TCP connections where a client connects, sends nothing, and idles; protocols that keep a single long-lived stream (e.g. long-polling apps) tripping the second-stream timeout.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Failed to fetch resource
- failed to accept QUIC stream: %w
- did not receive ICMP echo reply
- failed to get app info
- failed to get app token
AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06).
Data as JSON: /api/errors/95444f657aabb31b.
Report an issue: GitHub.