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

  1. Increase the flow limit configured on the Limiter if capacity should be higher
  2. Ensure flows are properly released (Release called on completion/error) so slots are not leaked
  3. Implement client-side backoff/retry with jitter when acquiring a flow fails with this error
  4. 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

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.