Repository: traefik/traefik (language: go)

Issue:
Let's encrypt TLS Challenge failing when behind a traefik TCP Router
### Welcome!

- [X] Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any.
- [X] Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any.

### What did you do?

I have multiple servers in my Network and one public static IP. The firewall is forwarding all traffic to `Server 1` on ports 80 and 443. `Server 1` has multiple TCP Router in place to send the traffic to `Server 2` and `Server 3` which look live this:

```
tcp:
    routers:
        server_2:
            entrypoints:
                - websecure
            rule: HostSNIRegexp(`^.+\.server2\.subdomain\.example\.com$`)
            tls:
                passthrough: true
            service: server_2
    services:
        server_2:
            loadBalancer:
                proxyProtocol:
                    version: 2
                servers:
                    - address: 172.16.10.12:443
```

and

```
tcp:
    routers:
        server_3:
            entrypoints:
                - websecure
            rule: HostSNIRegexp(`^.+\.server3\.subdomain\.example\.com$`)
            tls:
                passthrough: true
            service: server_3
    services:
        server_3:
            loadBalancer:
                proxyProtocol:
                    version: 2
                servers:
                    - address: 172.16.10.13:443
```

On `Server 2` and` Server 3` lets's encrypt is configures like this:

```
certificatesResolvers:
    letsencrypt-tls:
        acme:
            email: le@example.com
            storage: /letsencrypt/acme.json
            tlsChallenge: true 
```

If try to create a Certificate on `Server 2` or `Server 3` for example `www.app.server2.subdomain.example.com` and `app.server2.subdomain.example.com` it always fails for the domain with the `www` prefix. With a `error 400` code.

**Edit**
it looks like all certification request are failing not only the one with the `www` prefix, which was reported because it was the first to process.

On `Server 1` which is facing directly to the internet the problem is not present.

### What did you see instead?

---

### What version of Traefik are you using?

Version:      3.0.0
Codename:     beaufort
Go version:   go1.22.2
Built:        2024-04-29T14:25:59Z
OS/Arch:      linux/amd64

### What is your environment & configuration?

Server 1
```
log:
    level: INFO

accessLog:
    filePath: /var/log/traefik/access.log
    format: json
    bufferingSize: 100
    fields:
        defaultMode: keep
        headers:
            defaultMode: keep

api:
    dashboard: true

providers:
    docker:
        endpoint: "unix:///var/run/docker.sock"
        exposedByDefault: false
        network: external
    file:
        directory: /etc/traefik/rules
        watch: true

entryPoints:
    web:
        address: :80
        forwardedHeaders:
            trustedIPs:
                - 172.16.10.11
        http:
            middlewares:
                - crowdsec-bouncer@file
            redirections:
                entryPoint:
                    to: websecure
                    scheme: https
    websecure:
        address: :443
        forwardedHeaders:
            trustedIPs:
                - 172.16.10.11
        http:
            middlewares:
                - crowdsec-bouncer@file
        http3:
            advertisedPort: 443

certificatesResolvers:
    letsencrypt-tls:
        acme:
            email: le@example.com
            storage: /letsencrypt/acme.json
            tlsChallenge: true 

metrics:
    influxDB2:
        address: https://influxdb.sg.t-k-f.ch
        token: ***
        org: ***
        bucket: traefik
        addEntryPointsLabels: true
        addRoutersLabels: true
        addServicesLabels: true
        additionalLabels:
            host: traefik.example.com
```

Server 2
```
log:
    level: INFO

accessLog:
    filePath: /var/log/traefik/access.log
    format: json
    bufferingSize: 100
    fields:
        defaultMode: keep
        headers:
            defaultMode: keep

api:
    dashboard: true

providers:
    docker:
        endpoint: "unix:///var/run/docker.sock"
        exposedByDefault: false
        network: external
    file:
        directory: /etc/traefik/rules
        watch: true

entryPoints:
    websecure:
        address: :443
        forwardedHeaders:
            trustedIPs:
                - 172.16.10.12
        proxyProtocol:
            trustedIPs:
                - 172.16.10.11
        http:
            middlewares:
                - crowdsec-bouncer@file
        http3: true

certificatesResolvers:
    letsencrypt-tls:
        acme:
            email: le@example.com
            storage: /letsencrypt/acme.json
            tlsChallenge: true 

metrics:
    influxDB2:
        address: https://influxdb.sg.t-k-f.ch
        token: ***
        org: ***
        bucket: traefik
        addEntryPointsLabels: true
        addRoutersLabels: true
        addServicesLabels: true
        additionalLabels:
            host: traefik.example.com
```

### If applicable, please paste the log output in DEBUG level

_No response_


Discussion / hints:
This Behaviour was introduced with [v3.0.0-rc4](https://github.com/traefik/traefik/releases/tag/v3.0.0-rc4) downgrading to [v3.0.0-rc3](https://github.com/traefik/traefik/releases/tag/v3.0.0-rc3) resolves the problem
Hello @jan-thoma,

Thanks for reaching out.
We will check the merge back we have done from Traefik v2 Traefik v3.
Looks like we have the same issue for 2.11.2
Going back to 2.11.0 for the Traefik TCP router fixed the issue for us, waiting now for a fix in 2.11.x

Problem seems more to be:
"Traefik Let's Encrypt TLS Challenge fails when behind a Traefik 2.11.2 or 3.0RC4 TCP Router"
We think this was caused by #10536. TCP routers marked as passthrough should pass through all traffic to the backend service, including ACME TLS.
Is there any fix planned?
Our certificates are starting to run out.

We use Traefik as a TLS proxy for sharing Port 443 without breaking open the encryption.
For some unknown reason it was only fixed for 3.0 RC

I'm also curious why this is still not confirmed. It's a blocker.

Hopefully someone will find the time to provide a MR soon. Thank you in advance.
Hello @jan-thoma @CybotTM @acul009 @TheDevMinerTV,

As @TheDevMinerTV mentioned, this behavior is due to changes in https://github.com/traefik/traefik/pull/10536.
This is an intended behavior and was introduced to make Traefik more secure in some context (multi-tenancy).

However, we think we could introduce an option to allow the "insecure" mode.
We are also considering enforcing this only when Traefik is configured to handle ACME TLS challenges.
I'm still confirming the behavior by changing the label, while this is not really a bug.
While I understand the intent when using TLS-termination, is it really intended to block the downstream challenge verification when using tls-passthrough? I‘d feel like the entire point of the tls-passthrough would be not to interfere and make exactly these things possible.

Either way, I‘d simply be happy to have the option back. Calling it insecure doesn‘t really bother me - although I‘d be interested in the possible attack vector from this. When using passthrough the downstream target already has a valid certificate, no?

Thank you for your hard work on traefik :)
> I'm still confirming the behavior by changing the label, while this is not really a bug.

How to add ‘insecure’ option in docker labels? Could you give me a sample?

Fix patch:
diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md
index f98e1a177b..362146ac02 100644
--- a/docs/content/reference/static-configuration/cli-ref.md
+++ b/docs/content/reference/static-configuration/cli-ref.md
@@ -108,6 +108,9 @@ Entry points definition. (Default: ```false```)
 `--entrypoints.<name>.address`:  
 Entry point address.
 
+`--entrypoints.<name>.allowacmebypass`:  
+Enables handling of ACME TLS and HTTP challenges with custom routers. (Default: ```false```)
+
 `--entrypoints.<name>.forwardedheaders.insecure`:  
 Trust all forwarded headers. (Default: ```false```)
 
diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md
index d71ffe6c70..6ca27db89b 100644
--- a/docs/content/reference/static-configuration/env-ref.md
+++ b/docs/content/reference/static-configuration/env-ref.md
@@ -108,6 +108,9 @@ Entry points definition. (Default: ```false```)
 `TRAEFIK_ENTRYPOINTS_<NAME>_ADDRESS`:  
 Entry point address.
 
+`TRAEFIK_ENTRYPOINTS_<NAME>_ALLOWACMEBYPASS`:  
+Enables handling of ACME TLS and HTTP challenges with custom routers. (Default: ```false```)
+
 `TRAEFIK_ENTRYPOINTS_<NAME>_FORWARDEDHEADERS_INSECURE`:  
 Trust all forwarded headers. (Default: ```false```)
 
diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml
index 42c4e6fdf1..8f6f9510ef 100644
--- a/docs/content/reference/static-configuration/file.toml
+++ b/docs/content/reference/static-configuration/file.toml
@@ -16,6 +16,7 @@
 [entryPoints]
   [entryPoints.EntryPoint0]
     address = "foobar"
+    allowACMEByPass = true
     [entryPoints.EntryPoint0.transport]
       keepAliveMaxTime = "42s"
       keepAliveMaxRequests = 42
diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml
index abb8e05d88..09e66b5450 100644
--- a/docs/content/reference/static-configuration/file.yaml
+++ b/docs/content/reference/static-configuration/file.yaml
@@ -16,6 +16,7 @@ serversTransport:
 entryPoints:
   EntryPoint0:
     address: foobar
+    allowACMEByPass: true
     transport:
       lifeCycle:
         requestAcceptGraceTimeout: 42s
diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md
index e874764bc3..ca34161d2d 100644
--- a/docs/content/routing/entrypoints.md
+++ b/docs/content/routing/entrypoints.md
@@ -233,6 +233,35 @@ If both TCP and UDP are wanted for the same port, two entryPoints definitions ar
 
     Full details for how to specify `address` can be found in [net.Listen](https://golang.org/pkg/net/#Listen) (and [net.Dial](https://golang.org/pkg/net/#Dial)) of the doc for go.
 
+### AllowACMEByPass
+
+_Optional, Default=false_
+
+`allowACMEByPass` determines whether a user defined router can handle ACME TLS or HTTP challenges instead of the Traefik dedicated one.
+This option can be used when a Traefik instance has one or more certificate resolvers configured,
+but is also used to route challenges connections/requests to services that could also initiate their own ACME challenges.
+
+??? info "No Certificate Resolvers configured"
+
+    It is not necessary to use the `allowACMEByPass' option certificate option if no certificate resolver is defined.
+    In fact, Traefik will automatically allow ACME TLS or HTTP requests to be handled by custom routers in this case, since there can be no concurrency with its own challenge handlers.
+
+```yaml tab="File (YAML)"
+entryPoints:
+  foo:
+    allowACMEByPass: true
+```
+
+```toml tab="File (TOML)"
+[entryPoints.foo]
+  [entryPoints.foo.allowACMEByPass]
+    allowACMEByPass = true
+```
+
+```bash tab="CLI"
+--entryPoints.name.allowACMEByPass=true
+```
+
 ### HTTP/2
 
 #### `maxConcurrentStreams`
diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go
index 905d749617..8bfbc59f1a 100644
--- a/pkg/config/static/entrypoints.go
+++ b/pkg/config/static/entrypoints.go
@@ -12,6 +12,7 @@ import (
 // EntryPoint holds the entry point configuration.
 type EntryPoint struct {
 	Address          string                `description:"Entry point address." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"`
+	AllowACMEByPass  bool                  `description:"Enables handling of ACME TLS and HTTP challenges with custom routers." json:"allowACMEByPass,omitempty" toml:"allowACMEByPass,omitempty" yaml:"allowACMEByPass,omitempty"`
 	Transport        *EntryPointsTransport `description:"Configures communication between clients and Traefik." json:"transport,omitempty" toml:"transport,omitempty" yaml:"transport,omitempty" export:"true"`
 	ProxyProtocol    *ProxyProtocol        `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
 	ForwardedHeaders *ForwardedHeaders     `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty" export:"true"`
diff --git a/pkg/provider/traefik/internal.go b/pkg/provider/traefik/internal.go
index 83bfee8bc1..da44f8ff24 100644
--- a/pkg/provider/traefik/internal.go
+++ b/pkg/provider/traefik/internal.go
@@ -87,15 +87,27 @@ func (i *Provider) createConfiguration(ctx context.Context) *dynamic.Configurati
 }
 
 func (i *Provider) acme(cfg *dynamic.Configuration) {
-	var eps []string
+	allowACMEByPass := map[string]bool{}
+	for name, ep := range i.staticCfg.EntryPoints {
+		allowACMEByPass[name] = ep.AllowACMEByPass
+	}
 
+	var eps []string
+	var epsByPass []string
 	uniq := map[string]struct{}{}
 	for _, resolver := range i.staticCfg.CertificatesResolvers {
 		if resolver.ACME != nil && resolver.ACME.HTTPChallenge != nil && resolver.ACME.HTTPChallenge.EntryPoint != "" {
-			if _, ok := uniq[resolver.ACME.HTTPChallenge.EntryPoint]; !ok {
-				eps = append(eps, resolver.ACME.HTTPChallenge.EntryPoint)
-				uniq[resolver.ACME.HTTPChallenge.EntryPoint] = struct{}{}
+			if _, ok := uniq[resolver.ACME.HTTPChallenge.EntryPoint]; ok {
+				continue
 			}
+			uniq[resolver.ACME.HTTPChallenge.EntryPoint] = struct{}{}
+
+			if allowByPass, ok := allowACMEByPass[resolver.ACME.HTTPChallenge.EntryPoint]; ok && allowByPass {
+				epsByPass = append(epsByPass, resolver.ACME.HTTPChallenge.EntryPoint)
+				continue
+			}
+
+			eps = append(eps, resolver.ACME.HTTPChallenge.EntryPoint)
 		}
 	}
 
@@ -110,6 +122,17 @@ func (i *Provider) acme(cfg *dynamic.Configuration) {
 		cfg.HTTP.Routers["acme-http"] = rt
 		cfg.HTTP.Services["acme-http"] = &dynamic.Service{}
 	}
+
+	if len(epsByPass) > 0 {
+		rt := &dynamic.Router{
+			Rule:        "PathPrefix(`/.well-known/acme-challenge/`)",
+			EntryPoints: epsByPass,
+			Service:     "acme-http@internal",
+		}
+
+		cfg.HTTP.Routers["acme-http-bypass"] = rt
+		cfg.HTTP.Services["acme-http"] = &dynamic.Service{}
+	}
 }
 
 func (i *Provider) redirection(ctx context.Context, cfg *dynamic.Configuration) {
diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go
index ccb8c06bfa..0da33e10a9 100644
--- a/pkg/server/router/tcp/router.go
+++ b/pkg/server/router/tcp/router.go
@@ -21,6 +21,8 @@ const defaultBufSize = 4096
 
 // Router is a TCP router.
 type Router struct {
+	acmeTLSPassthrough bool
+
 	// Contains TCP routes.
 	muxerTCP tcpmuxer.Muxer
 	// Contains TCP TLS routes.
@@ -148,7 +150,7 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
 	}
 
 	// Handling ACME-TLS/1 challenges.
-	if slices.Contains(hello.protos, tlsalpn01.ACMETLS1Protocol) {
+	if !r.acmeTLSPassthrough && slices.Contains(hello.protos, tlsalpn01.ACMETLS1Protocol) {
 		r.acmeTLSALPNHandler().ServeTCP(r.GetConn(conn, hello.peeked))
 		return
 	}
@@ -303,6 +305,10 @@ func (r *Router) SetHTTPSHandler(handler http.Handler, config *tls.Config) {
 	r.httpsTLSConfig = config
 }
 
+func (r *Router) EnableACMETLSPassthrough() {
+	r.acmeTLSPassthrough = true
+}
+
 // Conn is a connection proxy that handles Peeked bytes.
 type Conn struct {
 	// Peeked are the bytes that have been read from Conn for the purposes of route matching,
diff --git a/pkg/server/routerfactory.go b/pkg/server/routerfactory.go
index 6b7b80ff4b..c7534ef266 100644
--- a/pkg/server/routerfactory.go
+++ b/pkg/server/routerfactory.go
@@ -21,25 +21,37 @@ import (
 
 // RouterFactory the factory of TCP/UDP routers.
 type RouterFactory struct {
-	entryPointsTCP []string
-	entryPointsUDP []string
+	entryPointsTCP  []string
+	entryPointsUDP  []string
+	allowACMEByPass map[string]bool
+
+	managerFactory *service.ManagerFactory
 
-	managerFactory  *service.ManagerFactory
 	metricsRegistry metrics.Registry
 
 	pluginBuilder middleware.PluginsBuilder
-
-	chainBuilder *middleware.ChainBuilder
-	tlsManager   *tls.Manager
+	chainBuilder  *middleware.ChainBuilder
+	tlsManager    *tls.Manager
 }
 
 // NewRouterFactory creates a new RouterFactory.
 func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager,
 	chainBuilder *middleware.ChainBuilder, pluginBuilder middleware.PluginsBuilder, metricsRegistry metrics.Registry,
 ) *RouterFactory {
+	handlesTLSChallenge := false
+	for _, resolver := range staticConfiguration.CertificatesResolvers {
+		if resolver.ACME.TLSChallenge != nil {
+			handlesTLSChallenge = true
+			break
+		}
+	}
+
+	allowACMEByPass := map[string]bool{}
 	var entryPointsTCP, entryPointsUDP []string
-	for name, cfg := range staticConfiguration.EntryPoints {
-		protocol, err := cfg.GetProtocol()
+	for name, ep := range staticConfiguration.EntryPoints {
+		allowACMEByPass[name] = ep.AllowACMEByPass || !handlesTLSChallenge
+
+		protocol, err := ep.GetProtocol()
 		if err != nil {
 			// Should never happen because Traefik should not start if protocol is invalid.
 			log.WithoutContext().Errorf("Invalid protocol: %v", err)
@@ -60,6 +72,7 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *
 		tlsManager:      tlsManager,
 		chainBuilder:    chainBuilder,
 		pluginBuilder:   pluginBuilder,
+		allowACMEByPass: allowACMEByPass,
 	}
 }
 
@@ -87,6 +100,12 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
 	rtTCPManager := tcprouter.NewManager(rtConf, svcTCPManager, middlewaresTCPBuilder, handlersNonTLS, handlersTLS, f.tlsManager)
 	routersTCP := rtTCPManager.BuildHandlers(ctx, f.entryPointsTCP)
 
+	for ep, r := range routersTCP {
+		if allowACMEByPass, ok := f.allowACMEByPass[ep]; ok && allowACMEByPass {
+			r.EnableACMETLSPassthrough()
+		}
+	}
+
 	// UDP
 	svcUDPManager := udp.NewManager(rtConf)
 	rtUDPManager := udprouter.NewManager(rtConf, svcUDPManager)
diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go
index 1fb371d081..6e30de3316 100644
--- a/pkg/server/server_entrypoint_tcp.go
+++ b/pkg/server/server_entrypoint_tcp.go
@@ -172,7 +172,10 @@ func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint, hos
 		return nil, fmt.Errorf("error preparing server: %w", err)
 	}
 
-	rt := &tcprouter.Router{}
+	rt, err := tcprouter.NewRouter()
+	if err != nil {
+		return nil, fmt.Errorf("error preparing tcp router: %w", err)
+	}
 
 	reqDecorator := requestdecorator.New(hostResolverConfig)
 


Test patch:
diff --git a/pkg/server/router/tcp/router_test.go b/pkg/server/router/tcp/router_test.go
index 8f6ae37c15..2f997c7f1e 100644
--- a/pkg/server/router/tcp/router_test.go
+++ b/pkg/server/router/tcp/router_test.go
@@ -209,9 +209,10 @@ func Test_Routing(t *testing.T) {
 	}
 
 	testCases := []struct {
-		desc    string
-		routers []applyRouter
-		checks  []checkCase
+		desc                    string
+		routers                 []applyRouter
+		checks                  []checkCase
+		allowACMETLSPassthrough bool
 	}{
 		{
 			desc:    "No routers",
@@ -268,6 +269,18 @@ func Test_Routing(t *testing.T) {
 				},
 			},
 		},
+		{
+			desc:                    "TCP TLS passthrough catches ACME TLS",
+			allowACMETLSPassthrough: true,
+			routers:                 []applyRouter{routerTCPTLSCatchAllPassthrough},
+			checks: []checkCase{
+				{
+					desc:          "ACME TLS Challenge",
+					checkRouter:   checkACMETLS,
+					expectedError: "tls: first record does not look like a TLS handshake",
+				},
+			},
+		},
 		{
 			desc:    "Single TCP CatchAll router",
 			routers: []applyRouter{routerTCPCatchAll},
@@ -578,6 +591,10 @@ func Test_Routing(t *testing.T) {
 			router, err := manager.buildEntryPointHandler(context.Background(), dynConf.TCPRouters, dynConf.Routers, nil, nil)
 			require.NoError(t, err)
 
+			if test.allowACMETLSPassthrough {
+				router.EnableACMETLSPassthrough()
+			}
+
 			epListener, err := net.Listen("tcp", "127.0.0.1:0")
 			require.NoError(t, err)
 
@@ -699,7 +716,7 @@ func routerTCPTLSCatchAll(conf *runtime.Configuration) {
 	}
 }
 
-// routerTCPTLSCatchAllPassthrough a TCP TLS CatchAll Passthrough - HostSNI(`*`) router with TLS 1.0 config.
+// routerTCPTLSCatchAllPassthrough a TCP TLS CatchAll Passthrough - HostSNI(`*`) router with TLS 1.2 config.
 func routerTCPTLSCatchAllPassthrough(conf *runtime.Configuration) {
 	conf.TCPRouters["tcp-tls-catchall-passthrough"] = &runtime.TCPRouterInfo{
 		TCPRouter: &dynamic.TCPRouter{
diff --git a/pkg/server/server_entrypoint_tcp_test.go b/pkg/server/server_entrypoint_tcp_test.go
index f0b12c8ddb..4dc9ee4284 100644
--- a/pkg/server/server_entrypoint_tcp_test.go
+++ b/pkg/server/server_entrypoint_tcp_test.go
@@ -20,7 +20,9 @@ import (
 )
 
 func TestShutdownHijacked(t *testing.T) {
-	router := &tcprouter.Router{}
+	router, err := tcprouter.NewRouter()
+	require.NoError(t, err)
+
 	router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 		conn, _, err := rw.(http.Hijacker).Hijack()
 		require.NoError(t, err)
@@ -34,7 +36,9 @@ func TestShutdownHijacked(t *testing.T) {
 }
 
 func TestShutdownHTTP(t *testing.T) {
-	router := &tcprouter.Router{}
+	router, err := tcprouter.NewRouter()
+	require.NoError(t, err)
+
 	router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 		rw.WriteHeader(http.StatusOK)
 		time.Sleep(time.Second)
@@ -167,7 +171,9 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) {
 	}, nil)
 	require.NoError(t, err)
 
-	router := &tcprouter.Router{}
+	router, err := tcprouter.NewRouter()
+	require.NoError(t, err)
+
 	router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 		rw.WriteHeader(http.StatusOK)
 	}))
@@ -204,7 +210,9 @@ func TestReadTimeoutWithFirstByte(t *testing.T) {
 	}, nil)
 	require.NoError(t, err)
 
-	router := &tcprouter.Router{}
+	router, err := tcprouter.NewRouter()
+	require.NoError(t, err)
+
 	router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 		rw.WriteHeader(http.StatusOK)
 	}))
@@ -244,7 +252,9 @@ func TestKeepAliveMaxRequests(t *testing.T) {
 	}, nil)
 	require.NoError(t, err)
 
-	router := &tcprouter.Router{}
+	router, err := tcprouter.NewRouter()
+	require.NoError(t, err)
+
 	router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 		rw.WriteHeader(http.StatusOK)
 	}))
@@ -290,7 +300,9 @@ func TestKeepAliveMaxTime(t *testing.T) {
 	}, nil)
 	require.NoError(t, err)
 
-	router := &tcprouter.Router{}
+	router, err := tcprouter.NewRouter()
+	require.NoError(t, err)
+
 	router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 		rw.WriteHeader(http.StatusOK)
 	}))