cloudflare/cloudflared · error

The last ingress rule must match all URLs (i.e. it should no

Error message

The last ingress rule must match all URLs (i.e. it should not have a hostname or path filter)

What it means

errLastRuleNotCatchAll is returned during ingress rule validation when the final rule in the ingress list is not a catch-all — i.e. the last rule still has a hostname or path filter. cloudflared requires the last rule to match all URLs so every request has a matching rule.

Source

Thrown at ingress/ingress.go:24

	"net/url"
	"regexp"
	"strconv"
	"strings"

	"github.com/pkg/errors"
	"github.com/rs/zerolog"
	"github.com/urfave/cli/v2"
	"golang.org/x/net/idna"

	"github.com/cloudflare/cloudflared/config"
	"github.com/cloudflare/cloudflared/ingress/middleware"
	"github.com/cloudflare/cloudflared/ipaccess"
)

var (
	ErrNoIngressRules             = errors.New("The config file doesn't contain any ingress rules")
	ErrNoIngressRulesCLI          = errors.New("No ingress rules were defined in provided config (if any) nor from the cli, cloudflared will return 503 for all incoming HTTP requests")
	errLastRuleNotCatchAll        = errors.New("The last ingress rule must match all URLs (i.e. it should not have a hostname or path filter)")
	errBadWildcard                = errors.New("Hostname patterns can have at most one wildcard character (\"*\") and it can only be used for subdomains, e.g. \"*.example.com\"")
	errHostnameContainsPort       = errors.New("Hostname cannot contain a port")
	ErrURLIncompatibleWithIngress = errors.New("You can't set the --url flag (or $TUNNEL_URL) when using multiple-origin ingress rules")
)

const (
	ServiceBastion     = "bastion"
	ServiceSocksProxy  = "socks-proxy"
	ServiceWarpRouting = "warp-routing"
)

// FindMatchingRule returns the index of the Ingress Rule which matches the given
// hostname and path. This function assumes the last rule matches everything,
// which is the case if the rules were instantiated via the ingress#Validate method.
//
// Negative index rule signifies local cloudflared rules (not-user defined).
func (ing Ingress) FindMatchingRule(hostname, path string) (*Rule, int) {
	// The hostname might contain port. We only want to compare the host part with the rule

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Append a catch-all rule as the last ingress entry, e.g. `- service: http_status:404` (no hostname/path)
  2. Remove hostname/path from the last rule or reorder so filtered rules come before the catch-all
  3. Run `cloudflared tunnel ingress validate` to catch rule ordering issues before running
  4. Use `cloudflared tunnel ingress rule <url>` to preview which rule matches a URL and confirm the last rule catches everything

Example fix

// before (config.yml)
ingress:
  - hostname: app.example.com
    service: http://localhost:8080
  - hostname: api.example.com
    service: http://localhost:9090
// after (config.yml)
ingress:
  - hostname: app.example.com
    service: http://localhost:8080
  - hostname: api.example.com
    service: http://localhost:9090
  - service: http_status:404
Defensive patterns

Strategy: validation

Validate before calling

rules := cfg.Ingress
if len(rules) > 0 {
    last := rules[len(rules)-1]
    if last.Hostname != "" || last.Path != "" {
        return errors.New("last ingress rule must be a catch-all (no hostname/path)")
    }
}

Type guard

func endsWithCatchAll(rules []config.IngressRule) bool {
    if len(rules) == 0 { return false }
    last := rules[len(rules)-1]
    return last.Hostname == "" && last.Path == ""
}

Try / catch

if _, err := ingress.ParseIngress(conf); errors.Is(err, errLastRuleNotCatchAll) {
    return fmt.Errorf("append `- service: http_status:404` as the final rule: %w", err)
}

Prevention

When it happens

Trigger: Parsing ingress rules where ruleIndex == totalRules-1 and the last rule has a hostname or path filter set (ingress.go:377); a config whose ingress list ends with e.g. `- hostname: api.example.com, service: ...` without a trailing catch-all entry.

Common situations: Users copy examples that omit the final `- service: http_status:404` rule; YAML list ordering mistakes where the catch-all was placed in the middle; programmatically generated ingress that appends filtered rules last.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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