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 ruleView on GitHub (pinned to 2253eeeb25)
Solutions
- Append a catch-all rule as the last ingress entry, e.g. `- service: http_status:404` (no hostname/path)
- Remove hostname/path from the last rule or reorder so filtered rules come before the catch-all
- Run `cloudflared tunnel ingress validate` to catch rule ordering issues before running
- 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
- Always end the ingress list with a hostname-less, path-less catch-all rule
- Run `cloudflared tunnel ingress validate` before running the tunnel
- When generating rules programmatically, append the catch-all last
- Preview routing with `cloudflared tunnel ingress rule <url>` to confirm coverage
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
- too many addresses provided, max: 10
- No configuration file was found. Please create one, or use t
- ErrNoIngressRules
- ErrNoIngressRulesCLI
- access.TeamName cannot be blank when access.audTags are pres
AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06).
Data as JSON: /api/errors/0b40e3b28a1732fe.
Report an issue: GitHub.