{"record":{"id":"0b40e3b28a1732fe","repo":"cloudflare/cloudflared","slug":"the-last-ingress-rule-must-match-all-urls-i-e-it","errorCode":null,"errorMessage":"The last ingress rule must match all URLs (i.e. it should not have a hostname or path filter)","messagePattern":"The last ingress rule must match all URLs \\(i\\.e\\. it should not have a hostname or path filter\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ingress/ingress.go","lineNumber":24,"sourceCode":"\t\"net/url\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/rs/zerolog\"\n\t\"github.com/urfave/cli/v2\"\n\t\"golang.org/x/net/idna\"\n\n\t\"github.com/cloudflare/cloudflared/config\"\n\t\"github.com/cloudflare/cloudflared/ingress/middleware\"\n\t\"github.com/cloudflare/cloudflared/ipaccess\"\n)\n\nvar (\n\tErrNoIngressRules             = errors.New(\"The config file doesn't contain any ingress rules\")\n\tErrNoIngressRulesCLI          = 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\")\n\terrLastRuleNotCatchAll        = errors.New(\"The last ingress rule must match all URLs (i.e. it should not have a hostname or path filter)\")\n\terrBadWildcard                = errors.New(\"Hostname patterns can have at most one wildcard character (\\\"*\\\") and it can only be used for subdomains, e.g. \\\"*.example.com\\\"\")\n\terrHostnameContainsPort       = errors.New(\"Hostname cannot contain a port\")\n\tErrURLIncompatibleWithIngress = errors.New(\"You can't set the --url flag (or $TUNNEL_URL) when using multiple-origin ingress rules\")\n)\n\nconst (\n\tServiceBastion     = \"bastion\"\n\tServiceSocksProxy  = \"socks-proxy\"\n\tServiceWarpRouting = \"warp-routing\"\n)\n\n// FindMatchingRule returns the index of the Ingress Rule which matches the given\n// hostname and path. This function assumes the last rule matches everything,\n// which is the case if the rules were instantiated via the ingress#Validate method.\n//\n// Negative index rule signifies local cloudflared rules (not-user defined).\nfunc (ing Ingress) FindMatchingRule(hostname, path string) (*Rule, int) {\n\t// The hostname might contain port. We only want to compare the host part with the rule","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/ingress/ingress.go#L6-L42","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (config.yml)\ningress:\n  - hostname: app.example.com\n    service: http://localhost:8080\n  - hostname: api.example.com\n    service: http://localhost:9090\n// after (config.yml)\ningress:\n  - hostname: app.example.com\n    service: http://localhost:8080\n  - hostname: api.example.com\n    service: http://localhost:9090\n  - service: http_status:404","handlingStrategy":"validation","validationCode":"rules := cfg.Ingress\nif len(rules) > 0 {\n    last := rules[len(rules)-1]\n    if last.Hostname != \"\" || last.Path != \"\" {\n        return errors.New(\"last ingress rule must be a catch-all (no hostname/path)\")\n    }\n}","typeGuard":"func endsWithCatchAll(rules []config.IngressRule) bool {\n    if len(rules) == 0 { return false }\n    last := rules[len(rules)-1]\n    return last.Hostname == \"\" && last.Path == \"\"\n}","tryCatchPattern":"if _, err := ingress.ParseIngress(conf); errors.Is(err, errLastRuleNotCatchAll) {\n    return fmt.Errorf(\"append `- service: http_status:404` as the final rule: %w\", err)\n}","preventionTips":["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"],"tags":["ingress","validation","configuration"],"backgroundTag":"invalid-config-value","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}