Routing
Ingress provides flexible routing capabilities to match requests and route them to appropriate backend services. You can match requests by hostname and path, with support for exact, regex, and wildcard matching.
Host Matching
Host matching is the primary way to route requests. Ingress supports three types of host matching: exact, regex, and wildcard.
Automatic host_type (default)
If you omit host_type or set host_type: auto, Ingress picks the matcher at compile time (when the process starts or config reloads) from the host string:
- If
hostcontains regexp metacharacters( ) [ ] ^ $ | + ? \→ treat as regex - Else if
hostcontains*→ treat as wildcard - Else → exact
Regex is detected before *, so full-regex hosts such as ^.*\.example\.com$ are not treated as wildcards.
The resolved type is stored on the rule as host_type for the rest of the runtime (service name captures, error handling, etc.). Use host_type: exact when you must match host as a literal string even if it contains characters that look like a pattern.
Examples without explicit host_type:
rules:
# Compiled as regex (parentheses, \w, etc.)
- host: ^([a-z0-9-]+)\.inlets\.example\.com$
backend:
service:
name: inlets
port: 8080
# Compiled as wildcard
- host: '*.api.example.com'
backend:
service:
name: api-gateway
port: 8080
# Compiled as exact
- host: idp.example.com
backend:
service:
name: idp
port: 443Exact Matching
Exact matching matches the hostname literally. With automatic host_type, a plain hostname (no regexp metacharacters and no *) is treated as exact:
rules:
- host: example.com
backend:
service:
name: backend-service
port: 8080This will match requests with Host: example.com exactly.
Regex Matching
Regex matching allows you to use regular expressions to match hostnames. You can set host_type: regex explicitly, or omit host_type when the pattern contains regexp metacharacters so it is inferred automatically.
rules:
- host: ^t-(\w+).example.work
host_type: regex
backend:
service:
name: task.$1.svc
port: 8080In this example, $1 refers to the first capture group in the host regex pattern. A request to t-myapp.example.work will be routed to task.myapp.svc.
Note: In Go’s regexp engine, \w is [0-9A-Za-z_] and does not include -. Subdomains with hyphens (e.g. my-app.example.work) need a pattern that allows hyphens, such as ^t-([a-zA-Z0-9-]+).example.work, not only (\w+).
Service Name Capture Templates
When host_type: regex is used, you can compose service names with scoped capture templates (advanced usage):
${host.<index>}: capture groups from the host regex${path.<index>}: capture groups from the matched path regex
rules:
- host: ^t-(\w+)-(dev|prod).example.work$
host_type: regex
backend:
service:
name: task.${host.1}.${host.2}.svc
port: 8080
paths:
- path: ^/api/v1/([^/]+)/([^/]+)$
backend:
service:
name: ${path.2}.${path.1}.${host.2}.${host.1}.svc
port: 8080Compatibility notes:
- Legacy
$1,$2, ... inservice.nameare the preferred baseline and remain fully supported for host-regex captures. - Path rewrite rules keep using the rewrite syntax with
$1,$2, ... (for example^/api/(.*):/v2/$1).
Wildcard Matching
Wildcard matching uses * as a wildcard character. You can set host_type: wildcard explicitly, or omit host_type when the host contains * and no regexp metacharacters (see Automatic host_type).
rules:
- host: '*.example.work'
host_type: wildcard
backend:
service:
name: wildcard-service
port: 8080This will match any subdomain of example.work, such as app.example.work, api.example.work, etc.
Path-Based Routing
You can define path-based routing rules within a host rule. Paths are matched using regex patterns:
rules:
- host: example.com
backend:
service:
name: default-service
port: 8080
paths:
- path: /api
backend:
service:
name: api-service
port: 8080
- path: /admin
backend:
service:
name: admin-service
port: 8080Path matching uses regex patterns. The path /api will match /api, /api/, /api/users, etc.
Path Matching Priority
Paths are matched in the order they are defined. The first matching path will be used. If no path matches, the host-level backend will be used.
Disabling rules
Set enabled: false on a rule to take it out of matching without deleting it. Disabled rules are skipped in order (the next matching enabled rule wins). Omitted enabled or enabled: true keeps the rule active. Rule indices in the config file stay fixed so rules[].waf, rules[].rate_limit, and admin route rows remain aligned.
rules:
- host: legacy.example.com
enabled: false
backend:
redirect:
url: https://old.example.com
- host: legacy.example.com
backend:
service:
name: current-upstream
port: 8080Runnable sample: examples/basic/rule-disabled.yaml.
Request Rewriting
You can rewrite request paths, headers, and query parameters when routing to backend services.
Path Rewriting
Rewrite request paths using regex patterns:
rules:
- host: example.com
backend:
service:
name: backend-service
port: 8080
request:
path:
rewrites:
- ^/api/v1/(.*):/api/v2/$1
- ^/old:/newThe rewrite format is pattern:replacement, where pattern is a regex and replacement is the new path. Capture groups can be referenced using $1, $2, etc.
Host Header Rewriting
The outbound Host header is controlled by service.request.host.rewrite (optional override) and backend.service.mode (legacy backend.mode) when rewrite is omitted. See Request and Response Rewriting for full detail and fallback behavior.
Example with explicit rewrite:
rules:
- host: example.com
backend:
service:
name: backend-service
port: 8080
request:
host:
rewrite: truePreferred for a third-party HTTPS origin:
rules:
- host: mirror.example.com
backend:
service:
mode: external
protocol: https
name: upstream.example.orgHeader Modification
Add or modify request headers:
rules:
- host: example.com
backend:
service:
name: backend-service
port: 8080
request:
headers:
X-Forwarded-Proto: https
X-Custom-Header: valueQuery Parameter Modification
Add or modify query parameters:
rules:
- host: example.com
backend:
service:
name: backend-service
port: 8080
request:
query:
api_key: secret-key
version: v2Redirects
Use backend.redirect instead of proxying. backend.type is optional: when only redirect is configured on that backend, Ingress infers redirect automatically—you normally omit backend.type. Valid explicit values are service, handler, and redirect; only the matching block may be populated. If service, handler, and redirect blocks together look ambiguous while type is omitted, ingress validate fails until you set backend.type explicitly.
These two rules behave the same after inference—compare type: redirect with omission:
rules:
- host: old-explicit.example.com
backend:
type: redirect
redirect:
url: https://new.example.com
permanent: true
- host: old-inferred.example.com
backend:
redirect:
url: https://new.example.com
permanent: trueRunnable twin-host sample: examples/ssl-tls/route-redirect.yaml.
Fields:
url: Target URL. If it does not start withhttp://orhttps://, Ingress treats the value as a host (optional port) and builds the full URL with the incoming request’s scheme, original path, and query string. Fullhttp:///https://URLs without an explicit path (host only, or trailing/) also preserve the request path and query—use a path inurl(e.g.https://new.example.com/landing) when every request should go to the same destination.duration:temporary(default) orpermanent— whether the redirect is meant to be cached long-term (SEO / canonical move).preserve_request: Whentrue, uses 307 / 308 so clients keep the original HTTP method and body. Whenfalse(default), uses 302 / 301, where browsers may rewrite non-GET requests to GET.permanent/with_origin_method_and_body: Legacy booleans; still accepted. Preferdurationandpreserve_request. Validation fails whenduration: temporaryconflicts withpermanent: true.
| duration | preserve_request | Status |
|---|---|---|
| temporary | false | 302 |
| permanent | false | 301 |
| temporary | true | 307 |
| permanent | true | 308 |
rules:
- host: old.example.com
backend:
redirect:
url: https://new.example.com
duration: permanent
preserve_request: falseCapture templates in url follow the same rules as service.name: ${host.N} and ${path.N} from regex captures; for regex/wildcard hosts you can also use legacy $1-style substitution from the host pattern. Path captures apply when the redirect is chosen from a matched paths[].path entry.
rules:
- host: '^bigscreen-([^.]+)\.ys\.example\.com$'
host_type: regex
backend:
type: redirect
redirect:
url: https://bigscreen-$1.other.example.comFor host-level redirect combined with path-specific proxies or path-only redirects, examples/redirect/capture-and-mixed.yaml mixes explicit backend.type on some backends with omission on others so you can compare styles in one file. Host-level redirect preserves the request path and query for whole-site moves; matched paths[] backends take precedence (proxy, handler, or path-only redirect).
Path-level redirect
paths[].path uses the same Go regexp matching as service and handler paths (compiled once at startup with an implicit leading ^). Redirect backends on paths support:
- Prefix-style patterns — e.g.
/legacy/matches/legacy/release-notes. - Capture groups — use
${path.N}inredirect.url(same asservice.name). - Exact paths — add a
$suffix, e.g./promo$matches only/promo.
rules:
- host: redirect.example.com
backend:
redirect:
url: https://new.example.com # fallback: whole-site redirect, preserves path
paths:
- path: /go/([^/]+)$
backend:
redirect:
url: https://seg.${path.1}.example.com/app
- path: /promo$
backend:
redirect:
url: https://campaign.example.com/specialRunnable sample: examples/redirect/path-regex.yaml. See also Redirect examples.
Forced HTTP→HTTPS uses https.redirect_from_http (including optional with_origin_method_and_body); see the SSL/TLS guide.
Handler Backend
Path backends can answer from backend.handler instead of proxying. backend.type is optional: when only handler is configured, Ingress infers handler. The snippet below keeps type: handler on the first path only so it contrasts with the paths that omit backend.type.
Runnable samples for every handler type (including file_server, templates, and script) live under examples/handler/ — see the Handler examples page.
Use handler.type to choose one of:
static_response(default)file_servertemplatesscript
rules:
- host: handler.example.com
backend:
service:
name: api-service
port: 8080
paths:
- path: /custom/handler/json
backend:
type: handler
handler:
type: static_response
status_code: 200
headers:
Content-Type: application/json
body: |
{"message":"Hello, World!"}
- path: /custom/handler/files
backend:
handler:
type: file_server
root_dir: /app/public
index_file: index.html
- path: /custom/handler/templates
backend:
handler:
type: templates
root_dir: /app/templates
- path: /custom/handler/script/js
backend:
handler:
type: script
engine: javascript
script: |
ctx.response.status_code = 200
ctx.type = "application/json"
ctx.body = JSON.stringify({ method: ctx.method, path: ctx.path })
ctx.setHeader("X-Handler-Engine", "javascript")
- path: /custom/handler/script/go
backend:
handler:
type: script
engine: go
script: |
ctx.SetHeader("X-Handler-Engine", "go")
ctx.String(200, "%s %s", ctx.Method, ctx.Path)backend.type: optional—Ingress infersservice,handler, orredirectfrom which block is configured when unambiguous; setbackend.typeexplicitly only wheningress validatereports ambiguitybackend.service.mode:internal(default) orexternal—defaultHosttoward the upstream whenservice.request.host.rewriteis omitted (externalalignsHosttoservice.name; see Rewriting). Legacybackend.modeis accepted when it matches.handler.type:static_response(default),file_server,templates, orscript- when
handler.type=static_response:status_code,headers,body - when
handler.type=file_server:root_dir,index_file - when
handler.type=templates:root_dir - when
handler.type=script:engine,scriptengine=javascript: powered bygoja;ctxincludes:ctx.request/ctx.response- aliases:
ctx.method,ctx.path,ctx.headers - response aliases:
ctx.status(ctx.response.status_code),ctx.type(ctx.response.content_type),ctx.body(ctx.response.body) - methods:
ctx.setHeader(key, value)andctx.response.setHeader(key, value)
engine=go: powered byyaegi;ctxis the original*zoox.Context(e.g.ctx.SetHeader(...),ctx.String(...),ctx.Fetch())
Fallback Service
If no rule matches a request, the fallback service is used:
fallback:
service:
name: fallback-service
port: 8080The fallback service is useful for handling unmatched requests or providing a default backend. Unless you set fallback.service.request.host.rewrite, the upstream Host aligns to the fallback service (see Rewriting).
Routing Examples
Multiple Services on Same Host
rules:
- host: example.com
backend:
service:
name: web-service
port: 8080
paths:
- path: /api
backend:
service:
name: api-service
port: 8081
- path: /admin
backend:
service:
name: admin-service
port: 8082Regex Host with Path Rewriting
rules:
- host: ^t-(\w+).example.work
host_type: regex
backend:
service:
name: task.$1.svc
port: 8080
paths:
- path: /api/v1/([^/]+)
backend:
service:
name: $1.example.work
port: 8080
request:
path:
rewrites:
- ^/api/v1/([^/]+):/api/v1/task/$1Wildcard Host Matching
rules:
- host: '*.example.work'
host_type: wildcard
backend:
service:
name: wildcard-service
port: 8080This matches any subdomain of example.work and routes to the same backend service.
How matching is built (precompilation)
Ingress does not compile regular expressions on every request for host and path rules.
- When the process starts or when configuration is reloaded,
prepare()builds an internal router index (core/compile.go): for each enabled rule, the effectivehost_typeis resolved (including automatic inference when omitted orauto), then eachhostthat is regex or wildcard and eachpaths[].pathpattern is compiled once with Go’sregexppackage. Rules withenabled: falseare skipped for matching but keep their config index. - Rule order in your config is preserved. Matching walks rules in order; the first matching host rule wins, and within a host the first matching path wins (same semantics as before this optimization).
- If any pattern is invalid (e.g. bad regex in
hostorpath), startup orReloadfails with an error. You must fix the configuration before Ingress accepts traffic. This replaces the older behavior where some invalid patterns might only surface on the first matching request.
The per-request proxy path still uses the precompiled index. Separately, if caching is enabled, per-host routing decisions may be stored under a key shaped like match.host:v2:<hostname> (see Caching) until cache.ttl expires.
Best Practices
- Order matters: Place more specific rules before general ones
- Use exact matching when possible: Plain hostnames infer as exact and are faster than regex or wildcard matching
- Omit or set
autoforhost_typewhen convenient: Regex- or wildcard-lookinghostvalues are inferred at compile time; use explicithost_typewhen you need to override (for exampleexactfor a literal host that contains*or parentheses) - Test regex patterns: Ensure your regex patterns match as expected; invalid patterns fail at startup or reload
- Use path routing: Organize routes by path for better maintainability
- Set up fallback: Always configure a fallback service for unmatched requests