# Methods list which effectively expands to all HTTP methods
methods:
- ALL
Regular Rule
Regular rules allow definition and as such execution of arbitrary logic required by your upstream service. This page describes the available configuration options for a regular rule in detail.
In the simplest case, a regular rule reuses mechanisms from the previously defined catalogue in its pipelines. In more complex scenarios, a rule can reconfigure parts of the mechanisms being used. The specific parts that can be reconfigured or overridden depend on the mechanism itself and are described in the mechanism-specific documentation. Reconfiguration is always limited to the particular rule’s pipeline and does not affect other rules.
Configuration
A single regular rule consists of the following properties:
id
: string (mandatory)The unique identifier of the rule. It must be unique across all rules loaded by the same Rule Provider. To ensure uniqueness, it’s recommended to include the upstream service’s name and the rule’s purpose in the id. For example,
rule:my-service:public-api
.match
: RuleMatcher (mandatory)Defines the matching criteria for a rule, with the following properties:
routes
: RouteMatcher array (mandatory)Specifies route conditions for matching the rule to incoming HTTP requests with each entry having the following properties:
path
: string (mandatory)The Path Expression describing the request path this rule should match. It supports both simple and free (named) wildcards.
path_params
: PathParameterConditions (optional)Additional conditions which must be met for the values captured by named wildcards in the path expression. All defined conditions must succeed for the request path to be considered a match. Each entry supports the following properties:
name
: string (mandatory)The name of the wildcard.
type
: string (mandatory)The type of expression used to match the captured wildcard’s value. The supported types are:
glob
: to use a glob expression to match the captured value (/
is used as a delimiter, so*
matches anything until the next/
).regex
to use a regular expression to match the captured value.
value
: string (mandatory)The actual expression based on the given
type
.
backtracking_enabled
: boolean (optional)This property is deprecated, is unused since v0.17.0, and it will be removed in a future version. Before v0.17.0, it helped control backtracking. Configuring it now triggers a warning. hosts
: HostMatcher array (optional)Defines a set of hosts to match against the HTTP
Host
header. These conditions are "OR" conditions, meaning that at least one must match for a successful match. If not defined, any host will be matched. Each entry has the following properties:type
: string (mandatory)Specifies the type of expression for matching the host, which can be one of:
exact
: Matches the host exactly.wildcard
: Matches the host using a wildcard expression (e.g.,*.example.com
matches all subdomains underexample.com
). The*
can only appear once at the start of the expression, and*
alone matches any host.glob
: Uses a glob expression that the host of the incoming request must satisfy, with.
as a delimiter (e.g.,*
matches any segment until the next.
).regex
: Uses a regular expression that the host of the incoming request must satisfy.
The glob and regex types for host expressions are deprecated and will be removed in a future version. Using them triggers a warning log. Switch to exact or wildcard types, which improve rule matching, particularly when domains are more significant than paths. |
value
: string (mandatory)The actual host expression based on the
type
.scheme
: string (optional)The expected HTTP scheme. If not specified, both http and https are accepted.
methods
: string array (optional)Specifies the allowed HTTP methods (
GET
,POST
,PATCH
, etc). If not specified, all methods are allowed. To allow all methods except specific ones, useALL
and prefix the methods to exclude with!
. For example:# Methods list consisting of all HTTP methods without `TRACE` and `OPTIONS` methods: - ALL - "!TRACE" - "!OPTIONS"
allow_encoded_slashes
: string (optional)Controls how to handle URL-encoded slashes in request paths during matching and forwarding. Options include:
off
- Reject requests with encoded slashes (%2F
). This is the default behavior.on
- Accept requests with encoded slashes decoding them to/
.no_decode
- Accept requests with encoded slashes without touching them.
Handling URL-encoded slashes may differ across the proxies in front of heimdall, heimdall, and the upstream service. Accepting requests with encoded slashes could, depending on your rules, lead to Interpretation Conflict vulnerabilities resulting in privilege escalations. forward_to
: RequestForwarder (mandatory in Proxy operation mode)Defines the destination for proxied requests when heimdall operates in proxy mode. The following properties are supported:
host
: string (mandatory)Specifies the host (and port) to which the request should be forwarded. If no
rewrite
property (see below) is defined, the original URL’s scheme, path, and other components remain unchanged. For example, if the original request ishttps://mydomain.com/api/v1/something?foo=bar&bar=baz
and this property is set tomy-backend:8080
, the forwarded request will be sent tohttps://my-backend:8080/api/v1/something?foo=bar&bar=baz
.forward_host_header
: boolean (optional)Controls whether the
Host
header is forwarded to the upstream. Defaults totrue
.Note: If a header finalizer sets the
Host
header in theexecute
pipeline, its value takes precedence over this setting.rewrite
: OriginalURLRewriter (optional)Allows modifying additional parts of the original URL before forwarding the request. If set, at least one of the following supported (middleware) properties must be defined:
scheme
: string (optional)Specifies the URL scheme to use when forwarding the request. Defaults to the scheme of the original request.
Unless heimdall is started with the --insecure-skip-upstream-tls-enforcement
flag, onlyhttps
is allowed as the scheme.strip_path_prefix
: string (optional)This middleware strips the specified prefix from the original URL path before forwarding. E.g. if the path of the original url is
/api/v1/something
and the value of this property is set to/api/v1
, the request to the upstream will have the url path set to/something
.add_path_prefix
: string (optional)This middleware is applied after the execution of the
strip_path_prefix
middleware described above. If specified, heimdall will add the specified path prefix to the path used to forward the request to the upstream service. E.g. if the path of the original URL or the path resulting after the application of thestrip_path_prefix
middleware is/something
and the value of this property is set to/my-backend
, the request to the upstream will have the URL path set to/my-backend/something
.strip_query_parameters
: string array (optional)Removes specified query parameters from the original URL before forwarding. E.g. if the query parameters part of the original URL is
foo=bar&bar=baz
and the value of this property is set to["foo"]
, the query part of the request to the upstream will be set tobar=baz
execute
: Authentication & Authorization Pipeline (mandatory)Specifies the mechanisms used for authentication, authorization, contextualization, and finalization.
on_error
: Error Pipeline (optional)Specifies error handling mechanisms if the pipeline defined by the
execute
property fails. Defaults to the error pipeline defined in the default rule if not specified.
id: rule:foo:bar
match:
routes:
- path: /some/:identifier/followed/by/**
path_params:
- name: identifier
type: glob
value: "[a-z]"
scheme: https
hosts:
- type: exact
value: my-service.local
methods:
- GET
- POST
forward_to:
host: backend-a:8080
rewrite:
scheme: https
strip_path_prefix: /api/v1
execute:
# the following just demonstrates how to make use of specific
# mechanisms in the simplest possible form
- authenticator: foo
- authorizer: bar
- contextualizer: foo
- finalizer: zab
on_error:
- error_handler: foobar
Path Expression
Path expressions are used to match the incoming requests. When specifying these, you can make use of two types of wildcards:
free wildcard, which can be defined using
*
andsingle wildcard, which can be defined using
:
Both can be named and unnamed, with named wildcards allowing accessing of the matched segments in the pipeline of the rule using the defined name as a key on the Request.URL.Captures
object. Unnamed free wildcard is defined as **
and unnamed single wildcard is defined as :*
. A named wildcard uses some identifier instead of the *
, so like *name
for free wildcard and :name
for single wildcard.
The value of the path segment, respectively path segments available via the wildcard name is decoded. E.g. if you define the to be matched path in a rule as /file/:name
, and the actual path of the request is /file/%5Bid%5D
, you’ll get [id]
when accessing the captured path segment via the name
key. Not every path encoded value is decoded though. Decoding of encoded slashes happens only if allow_encoded_slashes
was set to on
.
There are some simple rules, which must be followed while using wildcards:
One can use as many single wildcards, as needed in any segment
A segment must start with
:
or*
to define a wildcardNo segments are allowed after a free (named) wildcard
If a regular segment must start with
:
or*
, but should not be considered as a wildcard, it must be escaped with\
.
Here some path examples:
/apples/and/bananas
- Matches exactly the given path/apples/and/:something
- Matches/apples/and/bananas
,/apples/and/oranges
and alike, but not/apples/and/bananas/andmore
or/apples/or/bananas
. Since a named single wildcard is used, the actual value of the path segment matched by:something
can be accessed in the rule pipeline usingsomething
as a key./apples/:junction/:something
- Similar to above. But will also match/apples/or/bananas
in addition to/apples/and/bananas
and/apples/and/oranges
./apples/and/some:thing
- Matches exactly/apples/and/some:thing
/apples/and/some**
- Matches exactly/apples/and/some**
/apples/**
- Matches any path starting with/apples/
, like/apples/and/bananas
but not/apples/
./apples/*remainingpath
- Same as above, but uses a named free wildcard/apples/**/bananas
- Is invalid, as there is a path segment after a free wildcard/apples/\*remainingpath
- Matches exactly/apples/*remainingpath
Here is an example demonstrating the usage of a single named wildcard:
id: rule:1
match:
routes:
- path: /files/:uuid/delete
hosts:
- type: exact
value: hosty.mchostface
execute:
- authorizer: openfga_check
config:
payload: |
{
"user": "{{ .Subject.ID }}",
"relation": "can_delete",
"object": "file:{{ .Request.URL.Captures.uuid }}"
}
Rule Matching Specificity & Backtracking
The implementation ensures that rules with more specific host and path expressions are matched first — regardless of their position within a rule set. In other words, specificity takes precedence over ordering.
A more specific rule (e.g., for /foo/bar ) must be defined in the same rule set as a more generic rule (e.g., for /foo/:something ). Defining them in different rule sets will cause an error, resulting in rejection of the affected rule set. This applies in both directions. |
Once a host and path expression of a rule matches a request, any additional conditions specified in the rule’s matching criteria are evaluated. Only if these conditions are met will the rule’s pipeline be executed.
If multiple rules share the same host and path expression and all their additional conditions match, the first one listed in the rule set will be applied. In such cases, rule order does matter. |
If a rule matches on host and path but fails due to unmet conditions, the process may backtrack to a less specific rule within the same rule set. Backtracking stops when:
a less specific rule matches successfully (including all its conditions),
a less specific rule fails and
no further candidate rules remain in the given rule set
The following examples illustrate these principles:
Consider a rule set with the following rules
- id: rule1
match:
routes:
- path: /files/**
execute:
- <pipeline definition>
- id: rule2
match:
routes:
- path: /files/:team/:name
path_params:
- name: team
type: regex
value: "(team1|team2)"
methods:
- GET
execute:
- <pipeline definition>
- id: rule3
match:
routes:
- path: /files/:team/:name
path_params:
- name: team
type: regex
value: "(team1|team2)"
execute:
- <pipeline definition>
- id: rule4
match:
routes:
- path: /files/team3/:name
execute:
- <pipeline definition>
An HTTP GET request to /files/team1/document.pdf
will be matched by rule2
, as it is more specific than rule1
and satisfies both the path and method conditions. Consequently, the pipeline for rule2
will be executed.
If rule2 had appeared after rule3 in the rule set, rule3 would have matched and taken precedence — highlighting the importance of rule order when specificity and match conditions are equal. |
An HTTP POST request to /files/team1/document.pdf
will be matched by rule3
, as it is more specific than rule1
and the methods
constraints in the rule2
failed. So its pipeline is executed.
An HTTP GET request to /files/team3/document.pdf
will be matched by rule4
, which is more specific than all other rules. Its pipeline is executed.
However, even though a request to /files/team4/document.pdf
matches the path expression defined in rule2
, respectively rule3
, the regular expression (team1|team2)
used in the path_params
for the team
parameter will not match. Backtracking occurs, and the request falls back to rule1
, which does match. Thus, the pipeline of rule1
is executed.
Here’s another example:
- id: rule1
match:
routes:
- path: /foo/**
execute:
- <pipeline definition>
- id: rule2
match:
routes:
- path: /foo/bar/:name
execute:
- <pipeline definition>
In this example:
A request to
/foo/something
matchesrule1
, as its wildcard path (/foo/**
) covers any path starting with/foo
.A request to
/foo/bar/something
matchesrule2
, which targets paths with a single segment after/foo/bar
(e.g.,/foo/bar/<single-segment>
).A request to
/foo/bar/baz/something
partially matchesrule2
because it shares the/foo/bar/
prefix. However, sincerule2
requires exactly one segment after/foo/bar/
, it does not fully match. Therefore, backtracking happens andrule1
, which is configured with a broader/foo/**
pattern, matches and is executed.
Authentication & Authorization Pipeline
As described in the Concepts section, this pipeline consists of mechanisms, previously configured in the mechanisms catalogue, organized in stages as described below, with authentication stage (consisting of authenticators) being mandatory.
Authentication Stage: List of authenticator references, each using authenticator as the key, followed by the required authenticator id. Regardless of their order in the pipeline, each authenticator serves as a fallback for the preceding one if it fails.
Some authenticators rely on the same sources to obtain the subject authentication object. For example, both the
jwt
andoauth2_introspection
authenticators retrieve tokens from theAuthorization
header by default. When using such authenticators within the same pipeline, it’s best to configure the more specific ones before the more general ones to optimize performance. In this case, thejwt
authenticator is more specific since it only processes tokens in JWT format. In contrast, theoauth2_introspection
authenticator is more general - it doesn’t depend on the token format and will attempt to handle any request containing a bearer token.Authorization Stage: List of contextualizer and authorizer references in any order (optional). Can also be mixed. As with authenticators, the list definition happens using either
contextualizer
orauthorizer
as key, followed by the requiredid
. All mechanisms in this list are executed in the order, they are defined. If any of these fails, the entire pipeline fails, which leads to the execution of the error pipeline. This list is optional.Finalization Stage: List of finalizer references using
finalizers
as key, followed by the required finalizerid
. All finalizers in this list are executed in the order they are defined. If any of these fail, the entire pipeline fails, which leads to the execution of the error pipeline. This list is optional. If a default rule is configured, and nofinalizers
are configured on a specific rule level, thefinalizers
from the default rule are used. If the default rule does not have anyfinalizers
configured either, no finalization will take place.
In all cases, the used mechanism can be partially reconfigured if supported by the corresponding type. Configuration goes into the config
properties. These reconfigurations are always local to the given rule. With other words, you can adjust your rule specific pipeline as you want without any side effects.
Execution of an contextualizer
, authorizer
, or finalizer
mechanisms can optionally happen conditionally by making use of a CEL expression in an if
clause, which has access to the Subject
and the Request
objects. If the if
clause is not present, the corresponding mechanism is always executed.
# list of authenticators
# defining the authentication stage
- authenticator: foo
- authenticator: bar
config:
subject: anon
# ... any further required authenticator
# list of authorizers and contextualizers in any order
# defining the authentication stage
- contextualizer: baz
config:
cache_ttl: 0s
- authorizer: zab
- contextualizer: foo
if: Subject.ID != "anonymous"
- contextualizer: bar
- authorizer: foo
if: Request.Method == "POST"
config:
expressions:
- expression: |
// some expression logic deviating from the
// definition in the pipeline configuration.
# ... any further required authorizer or contextualizer
# list of finalizers
# defining the finalization stage
- finalizer: foo
- finalizer: bar
config:
headers:
- X-User-ID: {{ quote .ID }}
# ... any further required finalizers
This example uses
two authenticators, with authenticator named
bar
being the fallback for the authenticator namedfoo
. This fallback authenticator is obviously of type anonymous as it reconfigures the referenced prototype to useanon
for subject id.multiple contextualizers and authorizers, with first contextualizer having its cache disabled (
cache_ttl
set to 0s) and the last authorizer being of type cel as it reconfigures the referenced prototype to use a different authorization expression.two finalizers, with the second one being obviously of type header, as it defines a
X-User-ID
header set to the value of the subject id to be forwarded to the upstream service.contextualizer
foo
is only executed if the authenticated subject is not anonymous.authorizer
foo
is only executed if the request method is HTTP POST.
Error Pipeline
Compared to the Authentication & Authorization Pipeline, the error pipeline is pretty simple. It is also a list of mechanism references, but all referenced types are error handler types. Thus, each entry in this list must have error_handler
as key, followed by the ìd
of the required error handler previously defined in the mechanism catalogue.
Execution of the error handlers should happen conditionally by making use of a CEL expression in an if
clause, which has access to the Error
and the Request
objects. Otherwise, the first error handler will be executed and the error pipeline will exit.
As with the authentication & authorization pipeline, partial reconfiguration of the used mechanisms is possible if supported by the corresponding type. The overrides are always local to the given rule as well.
- error_handler: foo
if: # rule specific condition
- error_handler: bar
config:
# rule specific config
This example uses two error handlers, named foo
and bar
. bar
will only be executed if foo
's error condition does not match. bar
does also override the error handler configuration as required by the given rule.
Last updated on Aug 2, 2025