Skip to content

Routers

Connecting Requests to Services

routers

A router is in charge of connecting incoming requests to the services that can handle them. In the process, routers may use pieces of middleware to update the request, or act before forwarding the request to the service.

Configuration Example

Requests /foo are Handled by service-foo -- Using the File Provider
## Dynamic configuration
http:
  routers:
    my-router:
      rule: "Path(`/foo`)"
      service: service-foo
## Dynamic configuration
[http.routers]
  [http.routers.my-router]
    rule = "Path(`/foo`)"
    service = "service-foo"
Forwarding all (non-tls) requests on port 3306 to a database service

Dynamic Configuration

## Dynamic configuration
tcp:
  routers:
    to-database:
      entryPoints:
        - "mysql"
      # Catch every request (only available rule for non-tls routers. See below.)
      rule: "HostSNI(`*`)"
      service: database
## Dynamic configuration
[tcp]
  [tcp.routers]
    [tcp.routers.to-database]
      entryPoints = ["mysql"]
      # Catch every request (only available rule for non-tls routers. See below.)
      rule = "HostSNI(`*`)"
      service = "database"

Static Configuration

## Static configuration
entryPoints:
  web:
    address: ":80"
  mysql:
    address: ":3306"
## Static configuration
[entryPoints]
  [entryPoints.web]
    address = ":80"
  [entryPoints.mysql]
    address = ":3306"
## Static configuration
--entryPoints.web.address=:80
--entryPoints.mysql.address=:3306

Configuring HTTP Routers

The character @ is not authorized in the router name

EntryPoints

If not specified, HTTP routers will accept requests from all EntryPoints in the list of default EntryPoints. If you want to limit the router scope to a set of entry points, set the entryPoints option.

Listens to Every EntryPoint

Dynamic Configuration

## Dynamic configuration
http:
  routers:
    Router-1:
      # By default, routers listen to every EntryPoints.
      rule: "Host(`example.com`)"
      service: "service-1"
## Dynamic configuration
[http.routers]
  [http.routers.Router-1]
    # By default, routers listen to every EntryPoints.
    rule = "Host(`example.com`)"
    service = "service-1"

Static Configuration

## Static configuration
entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"
  other:
    address: ":9090"
## Static configuration
[entryPoints]
  [entryPoints.web]
    address = ":80"
  [entryPoints.websecure]
    address = ":443"
  [entryPoints.other]
    address = ":9090"
## Static configuration
--entryPoints.web.address=:80
--entryPoints.websecure.address=:443
--entryPoints.other.address=:9090
Listens to Specific EntryPoints

Dynamic Configuration

## Dynamic configuration
http:
  routers:
    Router-1:
      # won't listen to entry point web
      entryPoints:
        - "websecure"
        - "other"
      rule: "Host(`example.com`)"
      service: "service-1"
## Dynamic configuration
[http.routers]
  [http.routers.Router-1]
    # won't listen to entry point web
    entryPoints = ["websecure", "other"]
    rule = "Host(`example.com`)"
    service = "service-1"

Static Configuration

## Static configuration
entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"
  other:
    address: ":9090"
## Static configuration
[entryPoints]
  [entryPoints.web]
    address = ":80"
  [entryPoints.websecure]
    address = ":443"
  [entryPoints.other]
    address = ":9090"
## Static configuration
--entryPoints.web.address=:80
--entryPoints.websecure.address=:443
--entryPoints.other.address=:9090

Rule

Rules are a set of matchers configured with values, that determine if a particular request matches specific criteria. If the rule is verified, the router becomes active, calls middlewares, and then forwards the request to the service.

The table below lists all the available matchers:

Rule Description
Header(`key`, `value`) Matches requests containing a header named key set to value.
HeaderRegexp(`key`, `regexp`) Matches requests containing a header named key matching regexp.
Host(`domain`) Matches requests host set to domain.
HostRegexp(`regexp`) Matches requests host matching regexp.
Method(`method`) Matches requests method set to method.
Path(`path`) Matches requests path set to path.
PathPrefix(`prefix`) Matches requests path prefix set to prefix.
PathRegexp(`regexp`) Matches request path using regexp.
Query(`key`, `value`) Matches requests query parameters named key set to value.
QueryRegexp(`key`, `regexp`) Matches requests query parameters named key matching regexp.
ClientIP(`ip`) Matches requests client IP using ip. It accepts IPv4, IPv6 and CIDR formats.

Backticks or Quotes?

To set the value of a rule, use backticks ` or escaped double-quotes \".

Single quotes ' are not accepted since the values are Go's String Literals.

Regexp Syntax

Matchers that accept a regexp as their value use a Go flavored syntax.

Expressing Complex Rules Using Operators and Parenthesis

The usual AND (&&) and OR (||) logical operators can be used, with the expected precedence rules, as well as parentheses.

One can invert a matcher by using the NOT (!) operator.

The following rule matches requests where:

  • either host is example.com OR,
  • host is example.org AND path is NOT /traefik
Host(`example.com`) || (Host(`example.org`) && !Path(`/traefik`))

Header and HeaderRegexp

The Header and HeaderRegexp matchers allow to match requests that contain specific header.

Examples

Match requests with a Content-Type header set to application/yaml:

Header(`Content-Type`, `application/yaml`)

Match requests with a Content-Type header set to either application/json or application/yaml:

HeaderRegexp(`Content-Type`, `^application/(json|yaml)$`)

To match headers case-insensitively, use the (?i) option:

HeaderRegexp(`Content-Type`, `(?i)^application/(json|yaml)$`)

Host and HostRegexp

The Host and HostRegexp matchers allow to match requests that are targeted to a given host.

These matchers do not support non-ASCII characters, use punycode encoded values (rfc 3492) to match such domains.

If no Host is set in the request URL (e.g., it's an IP address), these matchers will look at the Host header.

These matchers will match the request's host in lowercase.

Examples

Match requests with Host set to example.com:

Host(`example.com`)

Match requests sent to any subdomain of example.com:

HostRegexp(`^.+\.example\.com$`)

Match requests with Host set to either example.com or example.org:

HostRegexp(`^example\.(com|org)$`)

To match domains case-insensitively, use the (?i) option:

HostRegexp(`(?i)^example\.(com|org)$`)

Method

The Method matchers allows to match requests sent with the given method.

Example

Match OPTIONS requests:

Method(`OPTIONS`)

Path, PathPrefix, and PathRegexp

These matchers allow matching requests based on their URL path.

For exact matches, use Path and its prefixed alternative PathPrefix, for regexp matches, use PathRegexp.

Path are always starting with a /, except for PathRegexp.

Examples

Match /products but neither /products/shoes nor /products/:

Path(`/products`)

Match /products as well as everything under /products, such as /products/shoes, /products/ but also /products-for-sale:

PathPrefix(`/products`)

Match both /products/shoes and /products/socks with and ID like /products/shoes/57:

PathRegexp(`^/products/(shoes|socks)/[0-9]+$`)

Match requests with a path ending in either .jpeg, .jpg or .png:

PathRegexp(`\.(jpeg|jpg|png)$`)

Match /products as well as everything under /products, such as /products/shoes, /products/ but also /products-for-sale, case-insensitively:

PathRegexp(`(?i)^/products`)

Query and QueryRegexp

The Query and QueryRegexp matchers allow to match requests based on query parameters.

Examples

Match requests with a mobile query parameter set to true, such as in /search?mobile=true:

Query(`mobile`, `true`)

To match requests with a query parameter mobile that has no value, such as in /search?mobile, use:

Query(`mobile`)

Match requests with a mobile query parameter set to either true or yes:

QueryRegexp(`mobile`, `^(true|yes)$`)

Match requests with a mobile query parameter set to any value (including the empty value):

QueryRegexp(`mobile`, `^.*$`)

To match query parameters case-insensitively, use the (?i) option:

QueryRegexp(`mobile`, `(?i)^(true|yes)$`)

ClientIP

The ClientIP matcher allows matching requests sent from the given client IP.

It only matches the request client IP and does not use the X-Forwarded-For header for matching.

Examples

Match requests coming from a given IP:

ClientIP(`10.76.105.11`)
ClientIP(`::1`)

Match requests coming from a given subnet:

ClientIP(`192.168.1.0/24`)
ClientIP(`fe80::/10`)

Priority

To avoid path overlap, routes are sorted, by default, in descending order using rules length. The priority is directly equal to the length of the rule, and so the longest length has the highest priority.

A value of 0 for the priority is ignored: priority = 0 means that the default rules length sorting is used.

Maximum Value

Traefik reserves a range of priorities for its internal routers, the maximum user-defined router priority value is:

  • (MaxInt32 - 1000) for 32-bit platforms,
  • (MaxInt64 - 1000) for 64-bit platforms.
How default priorities are computed
## Dynamic configuration
http:
  routers:
    Router-1:
      rule: "HostRegexp(`[a-z]+\.traefik\.com`)"
      # ...
    Router-2:
      rule: "Host(`foobar.traefik.com`)"
      # ...
## Dynamic configuration
[http.routers]
  [http.routers.Router-1]
    rule = "HostRegexp(`[a-z]+\\.traefik\\.com`)"
    # ...
  [http.routers.Router-2]
    rule = "Host(`foobar.traefik.com`)"
    # ...

In this case, all requests with host foobar.traefik.com will be routed through Router-1 instead of Router-2.

Name Rule Priority
Router-1 HostRegexp(`[a-z]+\.traefik\.com`) 34
Router-2 Host(`foobar.traefik.com`) 26

The previous table shows that Router-1 has a higher priority than Router-2.

To solve this issue, the priority must be set.

Set priorities -- using the File Provider
## Dynamic configuration
http:
  routers:
    Router-1:
      rule: "HostRegexp(`[a-z]+\\.traefik\\.com`)"
      entryPoints:
      - "web"
      service: service-1
      priority: 1
    Router-2:
      rule: "Host(`foobar.traefik.com`)"
      entryPoints:
      - "web"
      priority: 2
      service: service-2
## Dynamic configuration
[http.routers]
  [http.routers.Router-1]
    rule = "HostRegexp(`[a-z]+\\.traefik\\.com`)"
    entryPoints = ["web"]
    service = "service-1"
    priority = 1
  [http.routers.Router-2]
    rule = "Host(`foobar.traefik.com`)"
    entryPoints = ["web"]
    priority = 2
    service = "service-2"

In this configuration, the priority is configured to allow Router-2 to handle requests with the foobar.traefik.com host.

RuleSyntax

Optional, Default=""

In Traefik v3 a new rule syntax has been introduced (migration guide). ruleSyntax option allows to configure the rule syntax to be used for parsing the rule on a per-router basis. This allows to have heterogeneous router configurations and ease migration.

The default value of the ruleSyntax option is inherited from the defaultRuleSyntax option in the static configuration. By default, the defaultRuleSyntax static option is v3, meaning that the default rule syntax is also v3.

Set rule syntax -- using the File Provider
## Dynamic configuration
http:
  routers:
    Router-v3:
      rule: HostRegexp(`[a-z]+\\.traefik\\.com`)
      ruleSyntax: v3
    Router-v2:
      rule: HostRegexp(`{subdomain:[a-z]+}.traefik.com`)
      ruleSyntax: v2
## Dynamic configuration
[http.routers]
  [http.routers.Router-v3]
    rule = "HostRegexp(`[a-z]+\\.traefik\\.com`)"
    ruleSyntax = v3
  [http.routers.Router-v2]
    rule = "HostRegexp(`{subdomain:[a-z]+}.traefik.com`)"
    ruleSyntax = v2
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: test.route
  namespace: default

spec:
  routes:
    # route v3
    - match: HostRegexp(`[a-z]+\\.traefik\\.com`)
      syntax: v3
      kind: Rule

    # route v2
    - match: HostRegexp(`{subdomain:[a-z]+}.traefik.com`)
      syntax: v2
      kind: Rule

In this configuration, the ruleSyntax is configured to allow Router-v2 to use v2 syntax, while for Router-v3 it is configured to use v3 syntax.

Middlewares

You can attach a list of middlewares to each HTTP router. The middlewares will take effect only if the rule matches, and before forwarding the request to the service.

The character @ is not authorized in the middleware name.

Middlewares order

Middlewares are applied in the same order as their declaration in router.

With a middleware -- using the File Provider
## Dynamic configuration
http:
  routers:
    my-router:
      rule: "Path(`/foo`)"
      # declared elsewhere
      middlewares:
      - authentication
      service: service-foo
## Dynamic configuration
[http.routers]
  [http.routers.my-router]
    rule = "Path(`/foo`)"
    # declared elsewhere
    middlewares = ["authentication"]
    service = "service-foo"

Service

Each request must eventually be handled by a service, which is why each router definition should include a service target, which is basically where the request will be passed along to.

In general, a service assigned to a router should have been defined, but there are exceptions for label-based providers. See the specific docker documentation.

The character @ is not authorized in the service name.

HTTP routers can only target HTTP services (not TCP services).

TLS

General

When a TLS section is specified, it instructs Traefik that the current router is dedicated to HTTPS requests only (and that the router should ignore HTTP (non TLS) requests). Traefik will terminate the SSL connections (meaning that it will send decrypted data to the services).

Configuring the router to accept HTTPS requests only
## Dynamic configuration
http:
  routers:
    Router-1:
      rule: "Host(`foo-domain`) && Path(`/foo-path/`)"
      service: service-id
      # will terminate the TLS request
      tls: {}
## Dynamic configuration
[http.routers]
  [http.routers.Router-1]
    rule = "Host(`foo-domain`) && Path(`/foo-path/`)"
    service = "service-id"
    # will terminate the TLS request
    [http.routers.Router-1.tls]

Routers for HTTP & HTTPS

If you need to define the same route for both HTTP and HTTPS requests, you will need to define two different routers: one with the tls section, one without.

HTTP & HTTPS routes
## Dynamic configuration
http:
  routers:
    my-https-router:
      rule: "Host(`foo-domain`) && Path(`/foo-path/`)"
      service: service-id
      # will terminate the TLS request
      tls: {}

    my-http-router:
      rule: "Host(`foo-domain`) && Path(`/foo-path/`)"
      service: service-id
## Dynamic configuration
[http.routers]
  [http.routers.my-https-router]
    rule = "Host(`foo-domain`) && Path(`/foo-path/`)"
    service = "service-id"
    # will terminate the TLS request
    [http.routers.my-https-router.tls]

  [http.routers.my-http-router]
    rule = "Host(`foo-domain`) && Path(`/foo-path/`)"
    service = "service-id"

options

The options field enables fine-grained control of the TLS parameters. It refers to a TLS Options and will be applied only if a Host rule is defined.

Server Name Association

Even though one might get the impression that a TLS options reference is mapped to a router, or a router rule, one should realize that it is actually mapped only to the host name found in the Host part of the rule. Of course, there could also be several Host parts in a rule, in which case the TLS options reference would be mapped to as many host names.

Another thing to keep in mind is: the TLS option is picked from the mapping mentioned above and based on the server name provided during the TLS handshake, and it all happens before routing actually occurs.

Domain Fronting

In the case of domain fronting, if the TLS options associated with the Host Header and the SNI are different then Traefik will respond with a status code 421.

Configuring the TLS options
## Dynamic configuration
http:
  routers:
    Router-1:
      rule: "Host(`foo-domain`) && Path(`/foo-path/`)"
      service: service-id
      # will terminate the TLS request
      tls:
        options: foo

tls:
  options:
    foo:
      minVersion: VersionTLS12
      cipherSuites:
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
## Dynamic configuration
[http.routers]
  [http.routers.Router-1]
    rule = "Host(`foo-domain`) && Path(`/foo-path/`)"
    service = "service-id"
    # will terminate the TLS request
    [http.routers.Router-1.tls]
      options = "foo"

[tls.options]
  [tls.options.foo]
    minVersion = "VersionTLS12"
    cipherSuites = [
      "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
      "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
      "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
      "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
      "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    ]

Conflicting TLS Options

Since a TLS options reference is mapped to a host name, if a configuration introduces a situation where the same host name (from a Host rule) gets matched with two TLS options references, a conflict occurs, such as in the example below:

## Dynamic configuration
http:
  routers:
    routerfoo:
      rule: "Host(`snitest.com`) && Path(`/foo`)"
      tls:
        options: foo

    routerbar:
      rule: "Host(`snitest.com`) && Path(`/bar`)"
      tls:
        options: bar
## Dynamic configuration
[http.routers]
  [http.routers.routerfoo]
    rule = "Host(`snitest.com`) && Path(`/foo`)"
    [http.routers.routerfoo.tls]
      options = "foo"

[http.routers]
  [http.routers.routerbar]
    rule = "Host(`snitest.com`) && Path(`/bar`)"
    [http.routers.routerbar.tls]
      options = "bar"

If that happens, both mappings are discarded, and the host name (snitest.com in this case) for these routers gets associated with the default TLS options instead.

certResolver

If certResolver is defined, Traefik will try to generate certificates based on routers Host & HostSNI rules.

## Dynamic configuration
http:
  routers:
    routerfoo:
      rule: "Host(`snitest.com`) && Path(`/foo`)"
      tls:
        certResolver: foo
## Dynamic configuration
[http.routers]
  [http.routers.routerfoo]
    rule = "Host(`snitest.com`) && Path(`/foo`)"
    [http.routers.routerfoo.tls]
      certResolver = "foo"

Multiple Hosts in a Rule

The rule Host(`test1.example.com`) || Host(`test2.example.com`) will request a certificate with the main domain test1.example.com and SAN test2.example.com.

domains

You can set SANs (alternative domains) for each main domain. Every domain must have A/AAAA records pointing to Traefik. Each domain & SAN will lead to a certificate request.

## Dynamic configuration
http:
  routers:
    routerbar:
      rule: "Host(`snitest.com`) && Path(`/bar`)"
      tls:
        certResolver: "bar"
        domains:
          - main: "snitest.com"
            sans:
              - "*.snitest.com"
## Dynamic configuration
[http.routers]
  [http.routers.routerbar]
    rule = "Host(`snitest.com`) && Path(`/bar`)"
    [http.routers.routerbar.tls]
      certResolver = "bar"
      [[http.routers.routerbar.tls.domains]]
        main = "snitest.com"
        sans = ["*.snitest.com"]

ACME v2 supports wildcard certificates. As described in Let's Encrypt's post wildcard certificates can only be generated through a DNS-01 challenge.

Most likely the root domain should receive a certificate too, so it needs to be specified as SAN and 2 DNS-01 challenges are executed. In this case the generated DNS TXT record for both domains is the same. Even though this behavior is DNS RFC compliant, it can lead to problems as all DNS providers keep DNS records cached for a given time (TTL) and this TTL can be greater than the challenge timeout making the DNS-01 challenge fail.

The Traefik ACME client library lego supports some but not all DNS providers to work around this issue. The supported provider table indicates if they allow generating certificates for a wildcard domain and its root domain.

Wildcard certificates can only be verified through a DNS-01 challenge.

Double Wildcard Certificates

It is not possible to request a double wildcard certificate for a domain (for example *.*.local.com).

Observability

The Observability section defines a per router behavior regarding access-logs, metrics or tracing.

The default router observability configuration is inherited from the attached EntryPoints and can be configured with the observability options. However, a router defining its own observability configuration will opt-out from these defaults.

Note that to enable router-level observability, you must first enable access-logs, tracing, and/or metrics.

AddInternals option

By default, and for any type of signals (access-logs, metrics and tracing), Traefik disables observability for internal resources. The observability options described below cannot interfere with the AddInternals ones, and will be ignored.

For instance, if a router exposes the api@internal service and metrics.AddInternals is false, it will never produces metrics, even if the router observability configuration enables metrics.

accessLogs

Optional

The accessLogs option controls whether the router will produce access-logs.

Disable access-logs for a router using the File Provider
## Dynamic configuration
http:
  routers:
    my-router:
      rule: "Path(`/foo`)"
      service: service-foo
      observability:
        accessLogs: false
## Dynamic configuration
[http.routers]
  [http.routers.my-router]
    rule = "Path(`/foo`)"
    service = "service-foo"
    [http.routers.my-router.observability]
      accessLogs = false

metrics

Optional

The metrics option controls whether the router will produce metrics.

Metrics layers

When metrics layers are not enabled with the addEntryPointsLabels, addRoutersLabels and/or addServicesLabels options, enabling metrics for a router will not enable them.

Disable metrics for a router using the File Provider
## Dynamic configuration
http:
  routers:
    my-router:
      rule: "Path(`/foo`)"
      service: service-foo
      observability:
        metrics: false
## Dynamic configuration
[http.routers]
  [http.routers.my-router]
    rule = "Path(`/foo`)"
    service = "service-foo"
    [http.routers.my-router.observability]
      metrics = false

tracing

Optional

The tracing option controls whether the router will produce traces.

Disable tracing for a router using the File Provider
## Dynamic configuration
http:
  routers:
    my-router:
      rule: "Path(`/foo`)"
      service: service-foo
      observability:
        tracing: false
## Dynamic configuration
[http.routers]
  [http.routers.my-router]
    rule = "Path(`/foo`)"
    service = "service-foo"
    [http.routers.my-router.observability]
      tracing = false

Configuring TCP Routers

The character @ is not authorized in the router name

General

If both HTTP routers and TCP routers listen to the same EntryPoint, the TCP routers will apply before the HTTP routers. If no matching route is found for the TCP routers, then the HTTP routers will take over.

EntryPoints

If not specified, TCP routers will accept requests from all EntryPoints in the list of default EntryPoints. If you want to limit the router scope to a set of entry points, set the entry points option.

How to handle Server First protocols?

To correctly handle a request, Traefik needs to wait for the first few bytes to arrive before it can decide what to do with it.

For protocols where the server is expected to send first, such as SMTP, if no specific setup is in place, we could end up in a situation where both sides are waiting for data and the connection appears to have hanged.

The only way that Traefik can deal with such a case, is to make sure that on the concerned entry point, there is no TLS router whatsoever (neither TCP nor HTTP), and there is at least one non-TLS TCP router that leads to the server in question.

Listens to Every Entry Point

Dynamic Configuration

## Dynamic configuration

tcp:
  routers:
    Router-1:
      # By default, routers listen to every EntryPoints.
      rule: "HostSNI(`example.com`)"
      service: "service-1"
      # will route TLS requests (and ignore non tls requests)
      tls: {}
## Dynamic configuration

[tcp.routers]
  [tcp.routers.Router-1]
    # By default, routers listen to every EntryPoints.
    rule = "HostSNI(`example.com`)"
    service = "service-1"
    # will route TLS requests (and ignore non tls requests)
    [tcp.routers.Router-1.tls]

Static Configuration

## Static configuration

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"
  other:
    address: ":9090"
## Static configuration

[entryPoints]
  [entryPoints.web]
    address = ":80"
  [entryPoints.websecure]
    address = ":443"
  [entryPoints.other]
    address = ":9090"
## Static configuration
--entryPoints.web.address=:80
--entryPoints.websecure.address=:443
--entryPoints.other.address=:9090
Listens to Specific EntryPoints

Dynamic Configuration

## Dynamic configuration
tcp:
  routers:
    Router-1:
      # won't listen to entry point web
      entryPoints:
        - "websecure"
        - "other"
      rule: "HostSNI(`example.com`)"
      service: "service-1"
      # will route TLS requests (and ignore non tls requests)
      tls: {}
## Dynamic configuration
[tcp.routers]
  [tcp.routers.Router-1]
    # won't listen to entry point web
    entryPoints = ["websecure", "other"]
    rule = "HostSNI(`example.com`)"
    service = "service-1"
    # will route TLS requests (and ignore non tls requests)
    [tcp.routers.Router-1.tls]

Static Configuration

## Static configuration

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"
  other:
    address: ":9090"
## Static configuration

[entryPoints]
  [entryPoints.web]
    address = ":80"
  [entryPoints.websecure]
    address = ":443"
  [entryPoints.other]
    address = ":9090"
## Static configuration
--entryPoints.web.address=:80
--entryPoints.websecure.address=:443
--entryPoints.other.address=:9090

Rule

Rules are a set of matchers configured with values, that determine if a particular connection matches specific criteria. If the rule is verified, the router becomes active, calls middlewares, and then forwards the request to the service.

The table below lists all the available matchers:

Rule Description
HostSNI(`domain`) Checks if the connection's Server Name Indication is equal to domain.
HostSNIRegexp(`regexp`) Checks if the connection's Server Name Indication matches regexp.
ClientIP(`ip`) Checks if the connection's client IP correspond to ip. It accepts IPv4, IPv6 and CIDR formats.
ALPN(`protocol`) Checks if the connection's ALPN protocol equals protocol.

Backticks or Quotes?

To set the value of a rule, use backticks ` or escaped double-quotes \".

Single quotes ' are not accepted since the values are Go's String Literals.

Regexp Syntax

Matchers that accept a regexp as their value use a Go flavored syntax.

Expressing Complex Rules Using Operators and Parenthesis

The usual AND (&&) and OR (||) logical operators can be used, with the expected precedence rules, as well as parentheses.

One can invert a matcher by using the NOT (!) operator.

The following rule matches connections where:

  • either Server Name Indication is example.com OR,
  • Server Name Indication is example.org AND ALPN protocol is NOT h2
HostSNI(`example.com`) || (HostSNI(`example.org`) && !ALPN(`h2`))

HostSNI and HostSNIRegexp

HostSNI and HostSNIRegexp matchers allow to match connections targeted to a given domain.

These matchers do not support non-ASCII characters, use punycode encoded values (rfc 3492) to match such domains.

HostSNI & TLS

It is important to note that the Server Name Indication is an extension of the TLS protocol. Hence, only TLS routers will be able to specify a domain name with that rule. However, there is one special use case for HostSNI with non-TLS routers: when one wants a non-TLS router that matches all (non-TLS) requests, one should use the specific HostSNI(`*`) syntax.

Examples

Match all connections:

HostSNI(`*`)
HostSNIRegexp(`^.*$`)

Match TCP connections sent to example.com:

HostSNI(`example.com`)

Match TCP connections openned on any subdomain of example.com:

HostSNIRegexp(`^.+\.example\.com$`)

ClientIP

The ClientIP matcher allows matching connections opened by a client with the given IP.

Examples

Match connections opened by a given IP:

ClientIP(`10.76.105.11`)
ClientIP(`::1`)

Match connections coming from a given subnet:

ClientIP(`192.168.1.0/24`)
ClientIP(`fe80::/10`)

ALPN

The ALPN matcher allows matching connections the given protocol.

It would be a security issue to let a user-defined router catch the response to an ACME TLS challenge previously initiated by Traefik. For this reason, the ALPN matcher is not allowed to match the ACME-TLS/1 protocol, and Traefik returns an error if this is attempted.

Example

Match connections using the ALPN protocol h2:

ALPN(`h2`)

Priority

To avoid path overlap, routes are sorted, by default, in descending order using rules length. The priority is directly equal to the length of the rule, and so the longest length has the highest priority.

A value of 0 for the priority is ignored: priority = 0 means that the default rules length sorting is used.

Maximum Value

Traefik reserves a range of priorities for its internal routers, the maximum user-defined router priority value is:

  • (MaxInt32 - 1000) for 32-bit platforms,
  • (MaxInt64 - 1000) for 64-bit platforms.
How default priorities are computed
## Dynamic configuration
tcp:
  routers:
    Router-1:
      rule: "ClientIP(`192.168.0.12`)"
      # ...
    Router-2:
      rule: "ClientIP(`192.168.0.0/24`)"
      # ...
## Dynamic configuration
[tcp.routers]
  [tcp.routers.Router-1]
    rule = "ClientIP(`192.168.0.12`)"
    # ...
  [tcp.routers.Router-2]
    rule = "ClientIP(`192.168.0.0/24`)"
    # ...

The table below shows that Router-2 has a higher computed priority than Router-1.

Name Rule Priority
Router-1 ClientIP(`192.168.0.12`) 24
Router-2 ClientIP(`192.168.0.0/24`) 26

Which means that requests from 192.168.0.12 would go to Router-2 even though Router-1 is intended to specifically handle them. To achieve this intention, a priority (greater than 26) should be set on Router-1.

Setting priorities -- using the File Provider
## Dynamic configuration
tcp:
  routers:
    Router-1:
      rule: "ClientIP(`192.168.0.12`)"
      entryPoints:
      - "web"
      service: service-1
      priority: 2
    Router-2:
      rule: "ClientIP(`192.168.0.0/24`)"
      entryPoints:
      - "web"
      priority: 1
      service: service-2
## Dynamic configuration
[tcp.routers]
  [tcp.routers.Router-1]
    rule = "ClientIP(`192.168.0.12`)"
    entryPoints = ["web"]
    service = "service-1"
    priority = 2
  [tcp.routers.Router-2]
    rule = "ClientIP(`192.168.0.0/24`)"
    entryPoints = ["web"]
    priority = 1
    service = "service-2"

In this configuration, the priority is configured so that Router-1 will handle requests from 192.168.0.12.

RuleSyntax

Optional, Default=""

In Traefik v3 a new rule syntax has been introduced (migration guide). ruleSyntax option allows to configure the rule syntax to be used for parsing the rule on a per-router basis. This allows to have heterogeneous router configurations and ease migration.

The default value of the ruleSyntax option is inherited from the defaultRuleSyntax option in the static configuration. By default, the defaultRuleSyntax static option is v3, meaning that the default rule syntax is also v3.

Set rule syntax -- using the File Provider
## Dynamic configuration
tcp:
  routers:
    Router-v3:
      rule: ClientIP(`192.168.0.11`) || ClientIP(`192.168.0.12`)
      ruleSyntax: v3
    Router-v2:
      rule: ClientIP(`192.168.0.11`, `192.168.0.12`)
      ruleSyntax: v2
## Dynamic configuration
[tcp.routers]
  [tcp.routers.Router-v3]
    rule = "ClientIP(`192.168.0.11`) || ClientIP(`192.168.0.12`)"
    ruleSyntax = v3
  [tcp.routers.Router-v2]
    rule = "ClientIP(`192.168.0.11`, `192.168.0.12`)"
    ruleSyntax = v2
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
  name: test.route
  namespace: default

spec:
  routes:
    # route v3
    - match: ClientIP(`192.168.0.11`) || ClientIP(`192.168.0.12`)
      syntax: v3
      kind: Rule

    # route v2
    - match: ClientIP(`192.168.0.11`, `192.168.0.12`)
      syntax: v2
      kind: Rule

In this configuration, the ruleSyntax is configured to allow Router-v2 to use v2 syntax, while for Router-v3 it is configured to use v3 syntax.

Middlewares

You can attach a list of middlewares to each TCP router. The middlewares will take effect only if the rule matches, and before connecting to the service.

The character @ is not allowed to be used in the middleware name.

Middlewares order

Middlewares are applied in the same order as their declaration in router.

With a middleware -- using the File Provider
## Dynamic configuration
[tcp.routers]
  [tcp.routers.my-router]
    rule = "HostSNI(`*`)"
    # declared elsewhere
    middlewares = ["ipallowlist"]
    service = "service-foo"
## Dynamic configuration
tcp:
  routers:
    my-router:
      rule: "HostSNI(`*`)"
      # declared elsewhere
      middlewares:
      - ipallowlist
      service: service-foo

Services

You must attach a TCP service per TCP router. Services are the target for the router.

TCP routers can only target TCP services (not HTTP services).

TLS

General

When a TLS section is specified, it instructs Traefik that the current router is dedicated to TLS requests only (and that the router should ignore non-TLS requests).

By default, a router with a TLS section will terminate the TLS connections, meaning that it will send decrypted data to the services.

Router for TLS requests
## Dynamic configuration
tcp:
  routers:
    Router-1:
      rule: "HostSNI(`foo-domain`)"
      service: service-id
      # will terminate the TLS request by default
      tls: {}
## Dynamic configuration
[tcp.routers]
  [tcp.routers.Router-1]
    rule = "HostSNI(`foo-domain`)"
    service = "service-id"
    # will terminate the TLS request by default
    [tcp.routers.Router-1.tls]
Postgres STARTTLS

Traefik supports the Postgres STARTTLS protocol, which allows TLS routing for Postgres connections.

To do so, Traefik reads the first bytes sent by a Postgres client, identifies if they correspond to the message of a STARTTLS negotiation, and, if so, acknowledges and signals the client that it can start the TLS handshake.

Please note/remember that there are subtleties inherent to STARTTLS in whether the connection ends up being a TLS one or not. These subtleties depend on the sslmode value in the client configuration (and on the server authentication rules). Therefore, it is recommended to use the require value for the sslmode.

Afterwards, the TLS handshake, and routing based on TLS, can proceed as expected.

Postgres STARTTLS with TCP TLS PassThrough routers

As mentioned above, the sslmode configuration parameter does have an impact on whether a STARTTLS session will succeed. In particular in the context of TCP TLS PassThrough, some of the values (such as allow) do not even make sense. Which is why, once more it is recommended to use the require value.

passthrough

As seen above, a TLS router will terminate the TLS connection by default. However, the passthrough option can be specified to set whether the requests should be forwarded "as is", keeping all data encrypted.

It defaults to false.

Configuring passthrough
## Dynamic configuration
tcp:
  routers:
    Router-1:
      rule: "HostSNI(`foo-domain`)"
      service: service-id
      tls:
        passthrough: true
## Dynamic configuration
[tcp.routers]
  [tcp.routers.Router-1]
    rule = "HostSNI(`foo-domain`)"
    service = "service-id"
    [tcp.routers.Router-1.tls]
      passthrough = true

options

The options field enables fine-grained control of the TLS parameters. It refers to a TLS Options and will be applied only if a HostSNI rule is defined.

Configuring the tls options

## Dynamic configuration
tcp:
  routers:
    Router-1:
      rule: "HostSNI(`foo-domain`)"
      service: service-id
      # will terminate the TLS request
      tls:
        options: foo

tls:
  options:
    foo:
      minVersion: VersionTLS12
      cipherSuites:
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
## Dynamic configuration
[tcp.routers]
  [tcp.routers.Router-1]
    rule = "HostSNI(`foo-domain`)"
    service = "service-id"
    # will terminate the TLS request
    [tcp.routers.Router-1.tls]
      options = "foo"

[tls.options]
  [tls.options.foo]
    minVersion = "VersionTLS12"
    cipherSuites = [
      "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
      "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
      "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
      "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
      "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    ]

certResolver

See certResolver for HTTP router for more information.

## Dynamic configuration
tcp:
  routers:
    routerfoo:
      rule: "HostSNI(`snitest.com`)"
      tls:
        certResolver: foo
## Dynamic configuration
[tcp.routers]
  [tcp.routers.routerfoo]
    rule = "HostSNI(`snitest.com`)"
    [tcp.routers.routerfoo.tls]
      certResolver = "foo"

domains

See domains for HTTP router for more information.

## Dynamic configuration
tcp:
  routers:
    routerbar:
      rule: "HostSNI(`snitest.com`)"
      tls:
        certResolver: "bar"
        domains:
          - main: "snitest.com"
            sans:
              - "*.snitest.com"
## Dynamic configuration
[tcp.routers]
  [tcp.routers.routerbar]
    rule = "HostSNI(`snitest.com`)"
    [tcp.routers.routerbar.tls]
      certResolver = "bar"
      [[tcp.routers.routerbar.tls.domains]]
        main = "snitest.com"
        sans = ["*.snitest.com"]

Configuring UDP Routers

The character @ is not allowed in the router name

General

Similarly to TCP, as UDP is the transport layer, there is no concept of a request, so there is no notion of an URL path prefix to match an incoming UDP packet with. Furthermore, as there is no good TLS support at the moment for multiple hosts, there is no Host SNI notion to match against either. Therefore, there is no criterion that could be used as a rule to match incoming packets in order to route them. So UDP "routers" at this time are pretty much only load-balancers in one form or another.

Sessions and timeout

Even though UDP is connectionless (and because of that), the implementation of an UDP router in Traefik relies on what we (and a couple of other implementations) call a session. It basically means that some state is kept about an ongoing communication between a client and a backend, notably so that the proxy knows where to forward a response packet from a backend. As expected, a timeout is associated to each of these sessions, so that they get cleaned out if they go through a period of inactivity longer than a given duration. Timeout can be configured using the entryPoints.name.udp.timeout option as described under EntryPoints.

EntryPoints

If not specified, UDP routers will accept packets from all defined (UDP) EntryPoints. If one wants to limit the router scope to a set of EntryPoints, one should set the entryPoints option.

Listens to Every Entry Point

Dynamic Configuration

## Dynamic configuration

udp:
  routers:
    Router-1:
      # By default, routers listen to all UDP entrypoints
      # i.e. "other", and "streaming".
      service: "service-1"
## Dynamic configuration

[udp.routers]
  [udp.routers.Router-1]
    # By default, routers listen to all UDP entrypoints,
    # i.e. "other", and "streaming".
    service = "service-1"

Static Configuration

## Static configuration

entryPoints:
  # not used by UDP routers
  web:
    address: ":80"
  # used by UDP routers
  other:
    address: ":9090/udp"
  streaming:
    address: ":9191/udp"
## Static configuration

[entryPoints]
  # not used by UDP routers
  [entryPoints.web]
    address = ":80"
  # used by UDP routers
  [entryPoints.other]
    address = ":9090/udp"
  [entryPoints.streaming]
    address = ":9191/udp"
## Static configuration
--entryPoints.web.address=":80"
--entryPoints.other.address=":9090/udp"
--entryPoints.streaming.address=":9191/udp"
Listens to Specific EntryPoints

Dynamic Configuration

## Dynamic configuration
udp:
  routers:
    Router-1:
      # does not listen on "other" entry point
      entryPoints:
        - "streaming"
      service: "service-1"
## Dynamic configuration
[udp.routers]
  [udp.routers.Router-1]
    # does not listen on "other" entry point
    entryPoints = ["streaming"]
    service = "service-1"

Static Configuration

## Static configuration

entryPoints:
  web:
    address: ":80"
  other:
    address: ":9090/udp"
  streaming:
    address: ":9191/udp"
## Static configuration

[entryPoints]
  [entryPoints.web]
    address = ":80"
  [entryPoints.other]
    address = ":9090/udp"
  [entryPoints.streaming]
    address = ":9191/udp"
## Static configuration
--entryPoints.web.address=":80"
--entryPoints.other.address=":9090/udp"
--entryPoints.streaming.address=":9191/udp"

Services

There must be one (and only one) UDP service referenced per UDP router. Services are the target for the router.

UDP routers can only target UDP services (and not HTTP or TCP services).


Using Traefik OSS in Production?

If you are using Traefik at work, consider adding enterprise-grade API gateway capabilities or commercial support for Traefik OSS.

Adding API Gateway capabilities to Traefik OSS is fast and seamless. There's no rip and replace and all configurations remain intact. See it in action via this short video.