cloudflare/cloudflared · error

expected at least 2 Cloudflare Regions regions, but SRV only

Error message

expected at least 2 Cloudflare Regions regions, but SRV only returned %v

What it means

ResolveEdge discovers Cloudflare edge addresses via SRV lookup and builds a Regions object that requires at least two addresses — one for each of the two logical edge regions (region1/region2) used for redundant connections. When EdgeDiscovery returns fewer than 2 addresses, cloudflared cannot construct redundant region connections, so this error is thrown reporting how many addresses the SRV lookup actually returned. The count is embedded in the message, e.g. 'SRV only returned 1'.

Source

Thrown at edgediscovery/allregions/regions.go:28

// Regions stores Cloudflare edge network IPs, partitioned into two regions.
// This is NOT thread-safe. Users of this package should use it with a lock.
type Regions struct {
	region1 Region
	region2 Region
}

// ------------------------------------
// Constructors
// ------------------------------------

// ResolveEdge resolves the Cloudflare edge, returning all regions discovered.
func ResolveEdge(log *zerolog.Logger, region string, overrideIPVersion ConfigIPVersion) (*Regions, error) {
	edgeAddrs, err := EdgeDiscovery(log, RegionalServiceName(region))
	if err != nil {
		return nil, err
	}
	if len(edgeAddrs) < 2 {
		return nil, fmt.Errorf("expected at least 2 Cloudflare Regions regions, but SRV only returned %v", len(edgeAddrs))
	}
	return &Regions{
		region1: NewRegion(edgeAddrs[0], overrideIPVersion),
		region2: NewRegion(edgeAddrs[1], overrideIPVersion),
	}, nil
}

// StaticEdge creates a list of edge addresses from the list of hostnames.
// Mainly used for testing connectivity.
func StaticEdge(hostnames []string, log *zerolog.Logger) (*Regions, error) {
	resolved := ResolveAddrs(hostnames, log)
	if len(resolved) == 0 {
		return nil, fmt.Errorf("failed to resolve any edge address")
	}
	return NewNoResolve(resolved), nil
}

// NewNoResolve doesn't resolve the edge. Instead it just uses the given addresses.

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Retry — edge discovery is re-run on each connection attempt, and partial DNS answers are usually transient.
  2. Check the resolved count in the message: if it says 1, run 'nslookup -type=SRV <region service name>' yourself to confirm what your resolver returns and fix the resolver/VPN.
  3. Remove the --region restriction if set, so discovery can find addresses across all regions.
  4. Pin edge addresses explicitly with --edge <ip:port> list to bypass SRV-based region discovery entirely.

Example fix

// before
// cloudflared tunnel --region us run <tunnel>   // SRV only returned 1
// after — drop the region pin or pin the edge manually:
// cloudflared tunnel --edge 198.41.192.7:7844,198.41.200.13:7844 run <tunnel>
Defensive patterns

Strategy: fallback

Validate before calling

func canDiscoverEnoughEdgeAddrs(service string) bool {
    _, addrs, err := net.LookupSRV("", "", service)
    if err != nil {
        return false
    }
    n := 0
    for _, srv := range addrs {
        if ips, err := net.LookupIP(srv.Target); err == nil {
            n += len(ips)
        }
    }
    return n >= 2
}

Try / catch

regions, err := allregions.ResolveEdge(log, region, ipVersion)
if err != nil {
    if strings.Contains(err.Error(), "SRV only returned") {
        logger.Warn().Err(err).Msg("insufficient edge addresses; falling back to explicit --edge list")
        return useStaticEdgeAddrs() // fallback path
    }
    return err
}

Prevention

When it happens

Trigger: Calling ResolveEdge (used when establishing tunnel connections) where EdgeDiscovery(RegionalServiceName(region)) succeeds but returns fewer than 2 EdgeAddr values — e.g. only one SRV target resolved, or repeated SRV targets collapsed to one address, or partial DNS failures dropped addresses.

Common situations: Degraded corporate DNS/VPN returning partial answers for Cloudflare SRV records; a region override (--region us) reducing discoverable addresses; network middleboxes stripping AAAA (or A) records so most targets fail resolution; transient DNS outages returning only one usable edge address.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


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