cloudflare/cloudflared · error
ErrTooManyActiveFlows
ErrTooManyActiveFlows
Error message
too many active flows
What it means
ErrTooManyActiveFlows is returned by flow.Limiter.Acquire when the number of currently active flows already exceeds the configured maximum. It is the flow rate-limiting signal: proxies (ProxyTCP, handleDataStream) and UDP datagram handling return or match this error to reject new flows when capacity is exhausted.
Source
Thrown at flow/limiter.go:13
package flow
import (
"errors"
"sync"
)
const (
unlimitedActiveFlows = 0
)
var (
ErrTooManyActiveFlows = errors.New("too many active flows")
)
type Limiter interface {
// Acquire tries to acquire a free slot for a flow, if the value of flows is already above
// the maximum it returns ErrTooManyActiveFlows.
Acquire(flowType string) error
// Release releases a slot for a flow.
Release()
// SetLimit allows to hot swap the limit value of the limiter.
SetLimit(uint64)
}
type flowLimiter struct {
limiterLock sync.Mutex
activeFlowsCounter uint64
maxActiveFlows uint64
unlimited bool
}View on GitHub (pinned to 2253eeeb25)
Solutions
- Increase the flow limit configured on the Limiter if capacity should be higher
- Ensure flows are properly released (Release called on completion/error) so slots are not leaked
- Implement client-side backoff/retry with jitter when acquiring a flow fails with this error
- Check http2 response meta header cfdFlowRateLimited to confirm server-side rate limiting is the cause
Example fix
// before
if err := limiter.Acquire("tcp"); err != nil {
return err
}
// after
if err := limiter.Acquire("tcp"); err != nil {
if errors.Is(err, cfdflow.ErrTooManyActiveFlows) {
time.Sleep(backoffWithJitter())
return retryAcquire(limiter, "tcp")
}
return err
} Defensive patterns
Strategy: retry
Validate before calling
// no pre-check possible for concurrent limiter state; rely on the error
// optionally check current usage if the limiter exposes it
// if limiter.Active() >= limiter.Max() { backoff() } Type guard
func isFlowLimited(err error) bool { return errors.Is(err, cfdflow.ErrTooManyActiveFlows) } Try / catch
if err := limiter.Acquire("tcp"); err != nil {
if errors.Is(err, cfdflow.ErrTooManyActiveFlows) {
return retryWithBackoff(ctx)
}
return err
} Prevention
- Always release flow slots when a flow completes or errors
- Size the flow limit to expected concurrency
- Add jittered backoff on Acquire failures
- Watch for the cfdFlowRateLimited response meta header to detect server-side limiting
When it happens
Trigger: Acquiring a new flow slot via flow.Limiter.Acquire(flowType) when active flows >= max; ProxyTCP when the remote CfTraceID triggers flow rate limiting (connection/connection_test.go:112); handleDataStream for new UDP/datagram-v2 sessions over the limit; checked in http2.go:339 via errors.Is to tag responses as flow-rate-limited.
Common situations: Tunnels under high connection churn (many concurrent UDP/TCP flows) hitting the configured flow cap; misconfigured or intentionally low flow limits; clients opening many short-lived flows faster than they are released; load tests tripping the limiter.
Related errors
AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06).
Data as JSON: /api/errors/c436c188fd4c41e4.
Report an issue: GitHub.