Repository: traefik/traefik (language: go)

Issue:
Router exposed with an empty TLS Option 
### 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 want to secure a router using an mTLS connection.
I misspelled the `caFiles` path in the [TLSOption](https://doc.traefik.io/traefik/https/tls/#client-authentication-mtls) configuration.
I expected the router to not be used.

### What did you see instead?

The router is exposed using an empty TLS configuration, which is extra-permissive (with no mTLS).

### What version of Traefik are you using?

Traefik v2.9

### What is your environment & configuration?

```yaml
tls:
  options:
    default:
      clientAuth:
        # in PEM format. each file can contain multiple CAs.
        caFiles:
          - wrongpath/clientca1.crt # Wrong path
```


Discussion / hints:

Fix patch:
diff --git a/integration/fixtures/https/https_invalid_tls_options.toml b/integration/fixtures/https/https_invalid_tls_options.toml
new file mode 100644
index 0000000000..cc915c4ca1
--- /dev/null
+++ b/integration/fixtures/https/https_invalid_tls_options.toml
@@ -0,0 +1,60 @@
+[global]
+  checkNewVersion = false
+  sendAnonymousUsage = false
+
+[log]
+  level = "DEBUG"
+
+[entryPoints.websecure]
+  address = ":4443"
+
+[api]
+  insecure = true
+
+[providers.file]
+  filename = "{{ .SelfFilename }}"
+
+## dynamic configuration ##
+
+[http.routers]
+
+  [http.routers.router1]
+    entryPoints = ["websecure"]
+    service = "service1"
+    rule = "Host(`snitest.com`)"
+    [http.routers.router1.tls]
+      options = "invalidTLSOptions"
+
+  [http.routers.router2]
+    entryPoints = ["websecure"]
+    service = "service1"
+    rule = "Host(`snitest.org`)"
+    [http.routers.router2.tls]
+
+  # fallback router
+  [http.routers.router3]
+    entryPoints = ["websecure"]
+    service = "service1"
+    rule = "Path(`/`)"
+    [http.routers.router3.tls]
+
+[[http.services.service1.loadBalancer.servers]]
+  url = "http://127.0.0.1:9010"
+
+[[tls.certificates]]
+  certFile = "fixtures/https/snitest.com.cert"
+  keyFile = "fixtures/https/snitest.com.key"
+
+[[tls.certificates]]
+  certFile = "fixtures/https/snitest.org.cert"
+  keyFile = "fixtures/https/snitest.org.key"
+
+[tls.options]
+
+  [tls.options.default.clientAuth]
+    # Missing caFile to have an invalid mTLS configuration.
+    clientAuthType = "RequireAndVerifyClientCert"
+
+  [tls.options.invalidTLSOptions.clientAuth]
+    # Missing caFile to have an invalid mTLS configuration.
+    clientAuthType = "RequireAndVerifyClientCert"
diff --git a/integration/fixtures/tcp/multi-tls-options.toml b/integration/fixtures/tcp/multi-tls-options.toml
index f0b4a931c8..480df2d60f 100644
--- a/integration/fixtures/tcp/multi-tls-options.toml
+++ b/integration/fixtures/tcp/multi-tls-options.toml
@@ -33,6 +33,13 @@
       [tcp.routers.to-whoami-sni-strict.tls]
         options = "bar"
 
+    [tcp.routers.to-whoami-invalid-tls]
+      rule = "HostSNI(`whoami-i.test`)"
+      service = "whoami-no-cert"
+      entryPoints = [ "tcp" ]
+      [tcp.routers.to-whoami-invalid-tls.tls]
+        options = "invalid"
+
     [tcp.services.whoami-no-cert]
       [tcp.services.whoami-no-cert.loadBalancer]
         [[tcp.services.whoami-no-cert.loadBalancer.servers]]
@@ -45,3 +52,7 @@
 
   [tls.options.bar]
     minVersion = "VersionTLS13"
+
+  [tls.options.invalid.clientAuth]
+    # Missing CA files to have an invalid mTLS configuration.
+    clientAuthType = "RequireAndVerifyClientCert"
diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go
index 6f1819dbe3..6359102def 100644
--- a/pkg/server/router/router.go
+++ b/pkg/server/router/router.go
@@ -3,6 +3,7 @@ package router
 import (
 	"context"
 	"errors"
+	"fmt"
 	"net/http"
 
 	"github.com/containous/alice"
@@ -16,6 +17,7 @@ import (
 	httpmuxer "github.com/traefik/traefik/v2/pkg/muxer/http"
 	"github.com/traefik/traefik/v2/pkg/server/middleware"
 	"github.com/traefik/traefik/v2/pkg/server/provider"
+	"github.com/traefik/traefik/v2/pkg/tls"
 )
 
 type middlewareBuilder interface {
@@ -35,10 +37,11 @@ type Manager struct {
 	middlewaresBuilder middlewareBuilder
 	chainBuilder       *middleware.ChainBuilder
 	conf               *runtime.Configuration
+	tlsManager         *tls.Manager
 }
 
-// NewManager Creates a new Manager.
-func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry) *Manager {
+// NewManager creates a new Manager.
+func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry, tlsManager *tls.Manager) *Manager {
 	return &Manager{
 		routerHandlers:     make(map[string]http.Handler),
 		serviceManager:     serviceManager,
@@ -46,6 +49,7 @@ func NewManager(conf *runtime.Configuration, serviceManager serviceManager, midd
 		middlewaresBuilder: middlewaresBuilder,
 		chainBuilder:       chainBuilder,
 		conf:               conf,
+		tlsManager:         tlsManager,
 	}
 }
 
@@ -141,6 +145,17 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, rou
 		return handler, nil
 	}
 
+	if routerConfig.TLS != nil {
+		// Don't build the router if the TLSOptions configuration is invalid.
+		tlsOptionsName := tls.DefaultTLSConfigName
+		if len(routerConfig.TLS.Options) > 0 && routerConfig.TLS.Options != tls.DefaultTLSConfigName {
+			tlsOptionsName = provider.GetQualifiedName(ctx, routerConfig.TLS.Options)
+		}
+		if _, err := m.tlsManager.Get(tls.DefaultTLSStoreName, tlsOptionsName); err != nil {
+			return nil, fmt.Errorf("building router handler: %w", err)
+		}
+	}
+
 	handler, err := m.buildHTTPHandler(ctx, routerConfig, routerName)
 	if err != nil {
 		return nil, err
diff --git a/pkg/server/router/tcp/manager.go b/pkg/server/router/tcp/manager.go
index ff92f31121..b149f5f0be 100644
--- a/pkg/server/router/tcp/manager.go
+++ b/pkg/server/router/tcp/manager.go
@@ -103,18 +103,21 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 
 	router.SetHTTPHandler(handlerHTTP)
 
+	// Even though the error is seemingly ignored (aside from logging it),
+	// we actually rely later on the fact that a tls config is nil (which happens when an error is returned) to take special steps
+	// when assigning a handler to a route.
 	defaultTLSConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, traefiktls.DefaultTLSConfigName)
 	if err != nil {
 		log.FromContext(ctx).Errorf("Error during the build of the default TLS configuration: %v", err)
 	}
 
-	// Keyed by domain. The source of truth for doing SNI checking, and for what TLS
-	// options will actually be used for the connection.
+	// Keyed by domain. The source of truth for doing SNI checking (domain fronting).
 	// As soon as there's (at least) two different tlsOptions found for the same domain,
 	// we set the value to the default TLS conf.
 	tlsOptionsForHost := map[string]string{}
 
 	// Keyed by domain, then by options reference.
+	// The actual source of truth for what TLS options will actually be used for the connection.
 	// As opposed to tlsOptionsForHost, it keeps track of all the (different) TLS
 	// options that occur for a given host name, so that later on we can set relevant
 	// errors and logging for all the routers concerned (i.e. wrongly configured).
@@ -142,21 +145,20 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 		}
 
 		if len(domains) == 0 {
-			// Extra Host(*) rule, for HTTPS routers with no Host rule, and for requests for
-			// which the SNI does not match _any_ of the other existing routers Host. This is
-			// only about choosing the TLS configuration. The actual routing will be done
-			// further on by the HTTPS handler. See examples below.
+			// Extra Host(*) rule, for HTTPS routers with no Host rule,
+			// and for requests for which the SNI does not match _any_ of the other existing routers Host.
+			// This is only about choosing the TLS configuration.
+			// The actual routing will be done further on by the HTTPS handler.
+			// See examples below.
 			router.AddHTTPTLSConfig("*", defaultTLSConf)
 
-			// The server name (from a Host(SNI) rule) is the only parameter (available in
-			// HTTP routing rules) on which we can map a TLS config, because it is the only one
-			// accessible before decryption (we obtain it during the ClientHello). Therefore,
-			// when a router has no Host rule, it does not make any sense to specify some TLS
-			// options. Consequently, when it comes to deciding what TLS config will be used,
-			// for a request that will match an HTTPS router with no Host rule, the result will
-			// depend on the _others_ existing routers (their Host rule, to be precise), and
-			// the TLS options associated with them, even though they don't match the incoming
-			// request. Consider the following examples:
+			// The server name (from a Host(SNI) rule) is the only parameter (available in HTTP routing rules) on which we can map a TLS config,
+			// because it is the only one accessible before decryption (we obtain it during the ClientHello).
+			// Therefore, when a router has no Host rule, it does not make any sense to specify some TLS options.
+			// Consequently, when it comes to deciding what TLS config will be used,
+			// for a request that will match an HTTPS router with no Host rule,
+			// the result will depend on the _others_ existing routers (their Host rule, to be precise), and the TLS options associated with them,
+			// even though they don't match the incoming request. Consider the following examples:
 
 			//	# conf1
 			//	httpRouter1:
@@ -170,17 +172,19 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 			//	httpRouter2:
 			//		rule: Host("foo.com") && PathPrefix("/bar")
 			//		tlsoptions: myTLSOptions
-			//	# When a request for "/foo" comes, even though it won't be routed by
-			//	httpRouter2, if its SNI is set to foo.com, myTLSOptions will be used for the TLS
-			//	connection. Otherwise, it will fallback to the default TLS config.
+			//	# When a request for "/foo" comes, even though it won't be routed by httpRouter2,
+			//	# if its SNI is set to foo.com, myTLSOptions will be used for the TLS connection.
+			//	# Otherwise, it will fallback to the default TLS config.
 			logger.Warnf("No domain found in rule %v, the TLS options applied for this router will depend on the SNI of each request", routerHTTPConfig.Rule)
 		}
 
-		tlsConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
-		if err != nil {
-			routerHTTPConfig.AddError(err, true)
-			logger.Error(err)
-			continue
+		// Even though the error is seemingly ignored (aside from logging it),
+		// we actually rely later on the fact that a tls config is nil (which happens when an error is returned) to take special steps
+		// when assigning a handler to a route.
+		tlsConf, tlsConfErr := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
+		if tlsConfErr != nil {
+			// Note: we do not call AddError here because we already did so when buildRouterHandler errored for the same reason.
+			logger.Error(tlsConfErr)
 		}
 
 		for _, domain := range domains {
@@ -204,6 +208,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 
 	sniCheck := snicheck.New(tlsOptionsForHost, handlerHTTPS)
 
+	// Keep in mind that defaultTLSConf might be nil here.
 	router.SetHTTPSHandler(sniCheck, defaultTLSConf)
 
 	logger := log.FromContext(ctx)
@@ -217,22 +222,42 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 				break
 			}
 
-			logger.Debugf("Adding route for %s with TLS options %s", hostSNI, optionsName)
+			if config == nil {
+				// we use nil config as a signal to insert a handler
+				// that enforces that TLS connection attempts to the corresponding (broken) router should fail.
+				logger.Debugf("Adding special closing route for %s because broken TLS options %s", hostSNI, optionsName)
+				router.AddHTTPTLSConfig(hostSNI, nil)
+				continue
+			}
 
+			logger.Debugf("Adding route for %s with TLS options %s", hostSNI, optionsName)
 			router.AddHTTPTLSConfig(hostSNI, config)
-		} else {
-			routers := make([]string, 0, len(tlsConfigs))
-			for _, v := range tlsConfigs {
-				configsHTTP[v.routerName].AddError(fmt.Errorf("found different TLS options for routers on the same host %v, so using the default TLS options instead", hostSNI), false)
-				routers = append(routers, v.routerName)
-			}
+			continue
+		}
+
+		// multiple tlsConfigs
 
-			logger.Warnf("Found different TLS options for routers on the same host %v, so using the default TLS options instead for these routers: %#v", hostSNI, routers)
+		routers := make([]string, 0, len(tlsConfigs))
+		for _, v := range tlsConfigs {
+			configsHTTP[v.routerName].AddError(fmt.Errorf("found different TLS options for routers on the same host %v, so using the default TLS options instead", hostSNI), false)
+			routers = append(routers, v.routerName)
+		}
 
-			router.AddHTTPTLSConfig(hostSNI, defaultTLSConf)
+		logger.Warnf("Found different TLS options for routers on the same host %v, so using the default TLS options instead for these routers: %#v", hostSNI, routers)
+		if defaultTLSConf == nil {
+			logger.Debugf("Adding special closing route for %s because broken default TLS options", hostSNI)
 		}
+
+		router.AddHTTPTLSConfig(hostSNI, defaultTLSConf)
 	}
 
+	m.addTCPHandlers(ctx, configs, router)
+
+	return router, nil
+}
+
+// addTCPHandlers creates the TCP handlers defined in configs, and adds them to router.
+func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtime.TCPRouterInfo, router *Router) {
 	for routerName, routerConfig := range configs {
 		ctxRouter := log.With(provider.AddInContext(ctx, routerName), log.Str(log.RouterName, routerName))
 		logger := log.FromContext(ctxRouter)
@@ -251,13 +276,6 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 			continue
 		}
 
-		handler, err := m.buildTCPHandler(ctxRouter, routerConfig)
-		if err != nil {
-			routerConfig.AddError(err, true)
-			logger.Error(err)
-			continue
-		}
-
 		domains, err := tcpmuxer.ParseHostSNI(routerConfig.Rule)
 		if err != nil {
 			routerErr := fmt.Errorf("invalid rule: %q , %w", routerConfig.Rule, err)
@@ -274,6 +292,16 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 			logger.Error(routerErr)
 		}
 
+		var handler tcp.Handler
+		if routerConfig.TLS == nil || routerConfig.TLS.Passthrough {
+			handler, err = m.buildTCPHandler(ctxRouter, routerConfig)
+			if err != nil {
+				routerConfig.AddError(err, true)
+				logger.Error(err)
+				continue
+			}
+		}
+
 		if routerConfig.TLS == nil {
 			logger.Debugf("Adding route for %q", routerConfig.Rule)
 			if err := router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
@@ -285,7 +313,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 
 		if routerConfig.TLS.Passthrough {
 			logger.Debugf("Adding Passthrough route for %q", routerConfig.Rule)
-			if err := router.AddRouteTLS(routerConfig.Rule, routerConfig.Priority, handler, nil); err != nil {
+			if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
 				routerConfig.AddError(err, true)
 				logger.Error(err)
 			}
@@ -315,7 +343,15 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 		tlsConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
 		if err != nil {
 			routerConfig.AddError(err, true)
+
 			logger.Error(err)
+			logger.Debugf("Adding special TLS closing route for %q because broken TLS options %s", routerConfig.Rule, tlsOptionsName)
+
+			err = router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, &brokenTLSRouter{})
+			if err != nil {
+				routerConfig.AddError(err, true)
+				logger.Error(err)
+			}
 			continue
 		}
 
@@ -327,20 +363,30 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 		//		rule: HostSNI(foo.com) && ClientIP(IP2)
 		//		tlsOption: tlsTwo
 		// i.e. same HostSNI but different tlsOptions
-		// This is only applicable if the muxer can decide about the routing _before_
-		// telling the client about the tlsConf (i.e. before the TLS HandShake). This seems
-		// to be the case so far with the existing matchers (HostSNI, and ClientIP), so
-		// it's all good. Otherwise, we would have to do as for HTTPS, i.e. disallow
-		// different TLS configs for the same HostSNIs.
+		// This is only applicable if the muxer can decide about the routing _before_ telling the client about the tlsConf (i.e. before the TLS HandShake).
+		// This seems to be the case so far with the existing matchers (HostSNI, and ClientIP), so it's all good.
+		// Otherwise, we would have to do as for HTTPS, i.e. disallow different TLS configs for the same HostSNIs.
+
+		handler, err = m.buildTCPHandler(ctxRouter, routerConfig)
+		if err != nil {
+			routerConfig.AddError(err, true)
+			logger.Error(err)
+			continue
+		}
+
+		handler = &tcp.TLSHandler{
+			Next:   handler,
+			Config: tlsConf,
+		}
 
 		logger.Debugf("Adding TLS route for %q", routerConfig.Rule)
-		if err := router.AddRouteTLS(routerConfig.Rule, routerConfig.Priority, handler, tlsConf); err != nil {
+
+		err = router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler)
+		if err != nil {
 			routerConfig.AddError(err, true)
 			logger.Error(err)
 		}
 	}
-
-	return router, nil
 }
 
 func (m *Manager) buildTCPHandler(ctx context.Context, router *runtime.TCPRouterInfo) (tcp.Handler, error) {
diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go
index 0a41e80e25..dca4e6fae6 100644
--- a/pkg/server/router/tcp/router.go
+++ b/pkg/server/router/tcp/router.go
@@ -27,19 +27,20 @@ type Router struct {
 	muxerHTTPS tcpmuxer.Muxer
 
 	// Forwarder handlers.
-	// Handles all HTTP requests.
+	// httpForwarder handles all HTTP requests.
 	httpForwarder tcp.Handler
-	// Handles (indirectly through muxerHTTPS, or directly) all HTTPS requests.
+	// httpsForwarder handles (indirectly through muxerHTTPS, or directly) all HTTPS requests.
 	httpsForwarder tcp.Handler
 
-	// Neither is used directly, but they are held here, and recreated on config
-	// reload, so that they can be passed to the Switcher at the end of the config
-	// reload phase.
+	// Neither is used directly, but they are held here, and recreated on config reload,
+	// so that they can be passed to the Switcher at the end of the config reload phase.
 	httpHandler  http.Handler
 	httpsHandler http.Handler
 
 	// TLS configs.
-	httpsTLSConfig    *tls.Config            // default TLS config
+	httpsTLSConfig *tls.Config // default TLS config
+	// hostHTTPTLSConfig contains TLS configs keyed by SNI.
+	// A nil config is the hint to set up a brokenTLSRouter.
 	hostHTTPTLSConfig map[string]*tls.Config // TLS configs keyed by SNI
 }
 
@@ -80,11 +81,11 @@ func (r *Router) GetTLSGetClientInfo() func(info *tls.ClientHelloInfo) (*tls.Con
 
 // ServeTCP forwards the connection to the right TCP/HTTP handler.
 func (r *Router) ServeTCP(conn tcp.WriteCloser) {
-	// Handling Non-TLS TCP connection early if there is neither HTTP(S) nor TLS
-	// routers on the entryPoint, and if there is at least one non-TLS TCP router.
-	// In the case of a non-TLS TCP client (that does not "send" first), we would
-	// block forever on clientHelloInfo, which is why we want to detect and
-	// handle that case first and foremost.
+	// Handling Non-TLS TCP connection early if there is neither HTTP(S) nor TLS routers on the entryPoint,
+	// and if there is at least one non-TLS TCP router.
+	// In the case of a non-TLS TCP client (that does not "send" first),
+	// we would block forever on clientHelloInfo,
+	// which is why we want to detect and handle that case first and foremost.
 	if r.muxerTCP.HasRoutes() && !r.muxerTCPTLS.HasRoutes() && !r.muxerHTTPS.HasRoutes() {
 		connData, err := tcpmuxer.NewConnData("", conn, nil)
 		if err != nil {
@@ -152,9 +153,9 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
 	// (wrapped inside the returned handler) requested for the given HostSNI.
 	handlerHTTPS, catchAllHTTPS := r.muxerHTTPS.Match(connData)
 	if handlerHTTPS != nil && !catchAllHTTPS {
-		// In order not to depart from the behavior in 2.6, we only allow an HTTPS router
-		// to take precedence over a TCP-TLS router if it is _not_ an HostSNI(*) router (so
-		// basically any router that has a specific HostSNI based rule).
+		// In order not to depart from the behavior in 2.6,
+		// we only allow an HTTPS router to take precedence over a TCP-TLS router if it is _not_ an HostSNI(*) router
+		// (so basically any router that has a specific HostSNI based rule).
 		handlerHTTPS.ServeTCP(r.GetConn(conn, hello.peeked))
 		return
 	}
@@ -180,7 +181,7 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
 		return
 	}
 
-	// needed to handle 404s for HTTPS, as well as all non-Host (e.g. PathPrefix) matches.
+	// To handle 404s for HTTPS.
 	if r.httpsForwarder != nil {
 		r.httpsForwarder.ServeTCP(r.GetConn(conn, hello.peeked))
 		return
@@ -194,19 +195,6 @@ func (r *Router) AddRoute(rule string, priority int, target tcp.Handler) error {
 	return r.muxerTCP.AddRoute(rule, priority, target)
 }
 
-// AddRouteTLS defines a handler for a given rule and sets the matching tlsConfig.
-func (r *Router) AddRouteTLS(rule string, priority int, target tcp.Handler, config *tls.Config) error {
-	// TLS PassThrough
-	if config == nil {
-		return r.muxerTCPTLS.AddRoute(rule, priority, target)
-	}
-
-	return r.muxerTCPTLS.AddRoute(rule, priority, &tcp.TLSHandler{
-		Next:   target,
-		Config: config,
-	})
-}
-
 // AddHTTPTLSConfig defines a handler for a given sniHost and sets the matching tlsConfig.
 func (r *Router) AddHTTPTLSConfig(sniHost string, config *tls.Config) {
 	if r.hostHTTPTLSConfig == nil {
@@ -242,20 +230,44 @@ func (r *Router) SetHTTPForwarder(handler tcp.Handler) {
 	r.httpForwarder = handler
 }
 
-// SetHTTPSForwarder sets the tcp handler that will forward the TLS connections to an http handler.
+// brokenTLSRouter is associated to a Host(SNI) rule for which we know the TLS conf is broken.
+// It is used to make sure any attempt to connect to that hostname is closed,
+// since we cannot proceed with the intended TLS conf.
+type brokenTLSRouter struct{}
+
+// ServeTCP instantly closes the connection.
+func (t *brokenTLSRouter) ServeTCP(conn tcp.WriteCloser) {
+	_ = conn.Close()
+}
+
+// SetHTTPSForwarder sets the tcp handler that will forward the TLS connections to an HTTP handler.
+// It also sets up each TLS handler (with its TLS config) for each Host(SNI) rule we previously kept track of.
+// It sets up a special handler that closes the connection if a TLS config is nil.
 func (r *Router) SetHTTPSForwarder(handler tcp.Handler) {
 	for sniHost, tlsConf := range r.hostHTTPTLSConfig {
+		var tcpHandler tcp.Handler
+		if tlsConf == nil {
+			tcpHandler = &brokenTLSRouter{}
+		} else {
+			tcpHandler = &tcp.TLSHandler{
+				Next:   handler,
+				Config: tlsConf,
+			}
+		}
+
 		// muxerHTTPS only contains single HostSNI rules (and no other kind of rules),
 		// so there's no need for specifying a priority for them.
-		err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, &tcp.TLSHandler{
-			Next:   handler,
-			Config: tlsConf,
-		})
+		err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, tcpHandler)
 		if err != nil {
 			log.WithoutContext().Errorf("Error while adding route for host: %v", err)
 		}
 	}
 
+	if r.httpsTLSConfig == nil {
+		r.httpsForwarder = &brokenTLSRouter{}
+		return
+	}
+
 	r.httpsForwarder = &tcp.TLSHandler{
 		Next:   handler,
 		Config: r.httpsTLSConfig,
@@ -275,15 +287,14 @@ func (r *Router) SetHTTPSHandler(handler http.Handler, config *tls.Config) {
 
 // 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, but have not yet been consumed
-	// by Read calls. It set to nil by Read when fully consumed.
+	// Peeked are the bytes that have been read from Conn for the purposes of route matching,
+	// but have not yet been consumed by Read calls.
+	// It set to nil by Read when fully consumed.
 	Peeked []byte
 
 	// Conn is the underlying connection.
-	// It can be type asserted against *net.TCPConn or other types
-	// as needed. It should not be read from directly unless
-	// Peeked is nil.
+	// It can be type asserted against *net.TCPConn or other types as needed.
+	// It should not be read from directly unless Peeked is nil.
 	tcp.WriteCloser
 }
 
@@ -320,15 +331,14 @@ func clientHelloInfo(br *bufio.Reader) (*clientHello, error) {
 		return nil, err
 	}
 
-	// No valid TLS record has a type of 0x80, however SSLv2 handshakes
-	// start with a uint16 length where the MSB is set and the first record
-	// is always < 256 bytes long. Therefore typ == 0x80 strongly suggests
-	// an SSLv2 client.
+	// No valid TLS record has a type of 0x80, however SSLv2 handshakes start with an uint16 length
+	// where the MSB is set and the first record is always < 256 bytes long.
+	// Therefore, typ == 0x80 strongly suggests an SSLv2 client.
 	const recordTypeSSLv2 = 0x80
 	const recordTypeHandshake = 0x16
 	if hdr[0] != recordTypeHandshake {
 		if hdr[0] == recordTypeSSLv2 {
-			// we consider SSLv2 as TLS and it will be refused by real TLS handshake.
+			// we consider SSLv2 as TLS, and it will be refused by real TLS handshake.
 			return &clientHello{
 				isTLS:  true,
 				peeked: getPeeked(br),
diff --git a/pkg/server/routerfactory.go b/pkg/server/routerfactory.go
index fff7e3eea1..6b7b80ff4b 100644
--- a/pkg/server/routerfactory.go
+++ b/pkg/server/routerfactory.go
@@ -72,7 +72,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
 
 	middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder)
 
-	routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry)
+	routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry, f.tlsManager)
 
 	handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false)
 	handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)
diff --git a/pkg/tls/tlsmanager.go b/pkg/tls/tlsmanager.go
index 0c7daa9923..91fd6402f9 100644
--- a/pkg/tls/tlsmanager.go
+++ b/pkg/tls/tlsmanager.go
@@ -157,19 +157,16 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
 	m.lock.RLock()
 	defer m.lock.RUnlock()
 
-	var tlsConfig *tls.Config
-	var err error
-
 	sniStrict := false
 	config, ok := m.configs[configName]
-	if ok {
-		sniStrict = config.SniStrict
-		tlsConfig, err = buildTLSConfig(config)
-	} else {
-		err = fmt.Errorf("unknown TLS options: %s", configName)
+	if !ok {
+		return nil, fmt.Errorf("unknown TLS options: %s", configName)
 	}
+
+	sniStrict = config.SniStrict
+	tlsConfig, err := buildTLSConfig(config)
 	if err != nil {
-		tlsConfig = &tls.Config{}
+		return nil, fmt.Errorf("building TLS config: %w", err)
 	}
 
 	store := m.getStore(storeName)
@@ -177,7 +174,7 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
 		err = fmt.Errorf("TLS store %s not found", storeName)
 	}
 	acmeTLSStore := m.getStore(tlsalpn01.ACMETLS1Protocol)
-	if acmeTLSStore == nil {
+	if acmeTLSStore == nil && err == nil {
 		err = fmt.Errorf("ACME TLS store %s not found", tlsalpn01.ACMETLS1Protocol)
 	}
 
@@ -188,15 +185,12 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
 			certificate := acmeTLSStore.GetBestCertificate(clientHello)
 			if certificate == nil {
 				log.WithoutContext().Debugf("TLS: no certificate for TLSALPN challenge: %s", domainToCheck)
-				// We want the user to eventually get the (alertUnrecognizedName) "unrecognized
-				// name" error.
-				// Unfortunately, if we returned an error here, since we can't use
-				// the unexported error (errNoCertificates) that our caller (config.getCertificate
-				// in crypto/tls) uses as a sentinel, it would report an (alertInternalError)
-				// "internal error" instead of an alertUnrecognizedName.
-				// Which is why we return no error, and we let the caller detect that there's
-				// actually no certificate, and fall back into the flow that will report
-				// the desired error.
+				// We want the user to eventually get the (alertUnrecognizedName) "unrecognized name" error.
+				// Unfortunately, if we returned an error here,
+				// since we can't use the unexported error (errNoCertificates) that our caller (config.getCertificate in crypto/tls) uses as a sentinel,
+				// it would report an (alertInternalError) "internal error" instead of an alertUnrecognizedName.
+				// Which is why we return no error, and we let the caller detect that there's actually no certificate,
+				// and fall back into the flow that will report the desired error.
 				// https://cs.opensource.google/go/go/+/dev.boringcrypto.go1.17:src/crypto/tls/common.go;l=1058
 				return nil, nil
 			}


Test patch:
diff --git a/integration/https_test.go b/integration/https_test.go
index 868d31aff5..19983faebb 100644
--- a/integration/https_test.go
+++ b/integration/https_test.go
@@ -1226,3 +1226,53 @@ func (s *HTTPSSuite) TestWithDomainFronting(c *check.C) {
 		c.Assert(err, checker.IsNil)
 	}
 }
+
+// TestWithInvalidTLSOption verifies the behavior when using an invalid tlsOption configuration.
+func (s *HTTPSSuite) TestWithInvalidTLSOption(c *check.C) {
+	backend := startTestServer("9010", http.StatusOK, "server1")
+	defer backend.Close()
+
+	file := s.adaptFile(c, "fixtures/https/https_invalid_tls_options.toml", struct{}{})
+	defer os.Remove(file)
+	cmd, display := s.traefikCmd(withConfigFile(file))
+	defer display(c)
+	err := cmd.Start()
+	c.Assert(err, checker.IsNil)
+	defer s.killCmd(cmd)
+
+	// wait for Traefik
+	err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)"))
+	c.Assert(err, checker.IsNil)
+
+	testCases := []struct {
+		desc       string
+		serverName string
+	}{
+		{
+			desc:       "With invalid TLS Options specified",
+			serverName: "snitest.com",
+		},
+		{
+			desc:       "With invalid Default TLS Options",
+			serverName: "snitest.org",
+		},
+		{
+			desc: "With TLS Options without servername (fallback to default)",
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+
+		tlsConfig := &tls.Config{
+			InsecureSkipVerify: true,
+		}
+		if test.serverName != "" {
+			tlsConfig.ServerName = test.serverName
+		}
+
+		conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
+		c.Assert(err, checker.NotNil, check.Commentf("connected to server successfully"))
+		c.Assert(conn, checker.IsNil)
+	}
+}
diff --git a/integration/tcp_test.go b/integration/tcp_test.go
index 52a3813c9f..0a60ae5b4f 100644
--- a/integration/tcp_test.go
+++ b/integration/tcp_test.go
@@ -116,6 +116,14 @@ func (s *TCPSuite) TestTLSOptions(c *check.C) {
 	_, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS12)
 	c.Assert(err, checker.NotNil)
 	c.Assert(err.Error(), checker.Contains, "protocol version not supported")
+
+	// Check that we can't reach a route with an invalid mTLS configuration.
+	conn, err := tls.Dial("tcp", "127.0.0.1:8093", &tls.Config{
+		ServerName:         "whoami-i.test",
+		InsecureSkipVerify: true,
+	})
+	c.Assert(conn, checker.IsNil)
+	c.Assert(err, checker.NotNil)
 }
 
 func (s *TCPSuite) TestNonTLSFallback(c *check.C) {
diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go
index 26e8a68a34..0ba85b5278 100644
--- a/pkg/server/router/router_test.go
+++ b/pkg/server/router/router_test.go
@@ -20,6 +20,7 @@ import (
 	"github.com/traefik/traefik/v2/pkg/server/middleware"
 	"github.com/traefik/traefik/v2/pkg/server/service"
 	"github.com/traefik/traefik/v2/pkg/testhelpers"
+	"github.com/traefik/traefik/v2/pkg/tls"
 	"github.com/traefik/traefik/v2/pkg/types"
 )
 
@@ -317,8 +318,9 @@ func TestRouterManager_Get(t *testing.T) {
 			serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
 			middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
 			chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
+			tlsManager := tls.NewManager()
 
-			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
+			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
 
 			handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
 
@@ -423,8 +425,9 @@ func TestAccessLog(t *testing.T) {
 			serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
 			middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
 			chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
+			tlsManager := tls.NewManager()
 
-			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
+			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
 
 			handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
 
@@ -462,6 +465,7 @@ func TestRuntimeConfiguration(t *testing.T) {
 		serviceConfig    map[string]*dynamic.Service
 		routerConfig     map[string]*dynamic.Router
 		middlewareConfig map[string]*dynamic.Middleware
+		tlsOptions       map[string]tls.Options
 		expectedError    int
 	}{
 		{
@@ -665,7 +669,6 @@ func TestRuntimeConfiguration(t *testing.T) {
 			},
 			expectedError: 1,
 		},
-
 		{
 			desc: "Router with broken middleware",
 			serviceConfig: map[string]*dynamic.Service{
@@ -696,8 +699,71 @@ func TestRuntimeConfiguration(t *testing.T) {
 			},
 			expectedError: 2,
 		},
+		{
+			desc: "Router with broken tlsOption",
+			serviceConfig: map[string]*dynamic.Service{
+				"foo-service": {
+					LoadBalancer: &dynamic.ServersLoadBalancer{
+						Servers: []dynamic.Server{
+							{
+								URL: "http://127.0.0.1",
+							},
+						},
+					},
+				},
+			},
+			middlewareConfig: map[string]*dynamic.Middleware{},
+			routerConfig: map[string]*dynamic.Router{
+				"bar": {
+					EntryPoints: []string{"web"},
+					Service:     "foo-service",
+					Rule:        "Host(`foo.bar`)",
+					TLS: &dynamic.RouterTLSConfig{
+						Options: "broken-tlsOption",
+					},
+				},
+			},
+			tlsOptions: map[string]tls.Options{
+				"broken-tlsOption": {
+					ClientAuth: tls.ClientAuth{
+						ClientAuthType: "foobar",
+					},
+				},
+			},
+			expectedError: 1,
+		},
+		{
+			desc: "Router with broken default tlsOption",
+			serviceConfig: map[string]*dynamic.Service{
+				"foo-service": {
+					LoadBalancer: &dynamic.ServersLoadBalancer{
+						Servers: []dynamic.Server{
+							{
+								URL: "http://127.0.0.1",
+							},
+						},
+					},
+				},
+			},
+			middlewareConfig: map[string]*dynamic.Middleware{},
+			routerConfig: map[string]*dynamic.Router{
+				"bar": {
+					EntryPoints: []string{"web"},
+					Service:     "foo-service",
+					Rule:        "Host(`foo.bar`)",
+					TLS:         &dynamic.RouterTLSConfig{},
+				},
+			},
+			tlsOptions: map[string]tls.Options{
+				"default": {
+					ClientAuth: tls.ClientAuth{
+						ClientAuthType: "foobar",
+					},
+				},
+			},
+			expectedError: 1,
+		},
 	}
-
 	for _, test := range testCases {
 		test := test
 		t.Run(test.desc, func(t *testing.T) {
@@ -711,6 +777,9 @@ func TestRuntimeConfiguration(t *testing.T) {
 					Routers:     test.routerConfig,
 					Middlewares: test.middlewareConfig,
 				},
+				TLS: &dynamic.TLSConfiguration{
+					Options: test.tlsOptions,
+				},
 			})
 
 			roundTripperManager := service.NewRoundTripperManager()
@@ -718,10 +787,13 @@ func TestRuntimeConfiguration(t *testing.T) {
 			serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
 			middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
 			chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
+			tlsManager := tls.NewManager()
+			tlsManager.UpdateConfigs(context.Background(), nil, test.tlsOptions, nil)
 
-			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
+			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
 
 			_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
+			_ = routerManager.BuildHandlers(context.Background(), entryPoints, true)
 
 			// even though rtConf was passed by argument to the manager builders above,
 			// it's ok to use it as the result we check, because everything worth checking
@@ -793,8 +865,9 @@ func TestProviderOnMiddlewares(t *testing.T) {
 	serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
 	middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
 	chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
+	tlsManager := tls.NewManager()
 
-	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
+	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
 
 	_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
 
@@ -861,8 +934,9 @@ func BenchmarkRouterServe(b *testing.B) {
 	serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res})
 	middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
 	chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
+	tlsManager := tls.NewManager()
 
-	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
+	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
 
 	handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false)
 
diff --git a/pkg/tls/tlsmanager_test.go b/pkg/tls/tlsmanager_test.go
index 7f38532995..08acf04a24 100644
--- a/pkg/tls/tlsmanager_test.go
+++ b/pkg/tls/tlsmanager_test.go
@@ -119,8 +119,9 @@ func TestManager_Get(t *testing.T) {
 	}}
 
 	tlsConfigs := map[string]Options{
-		"foo": {MinVersion: "VersionTLS12"},
-		"bar": {MinVersion: "VersionTLS11"},
+		"foo":     {MinVersion: "VersionTLS12"},
+		"bar":     {MinVersion: "VersionTLS11"},
+		"invalid": {CurvePreferences: []string{"42"}},
 	}
 
 	testCases := []struct {
@@ -140,15 +141,20 @@ func TestManager_Get(t *testing.T) {
 			expectedMinVersion: uint16(tls.VersionTLS11),
 		},
 		{
-			desc:           "Get an tls config from an invalid name",
+			desc:           "Get a tls config from an invalid name",
 			tlsOptionsName: "unknown",
 			expectedError:  true,
 		},
 		{
-			desc:           "Get an tls config from unexisting 'default' name",
+			desc:           "Get a tls config from unexisting 'default' name",
 			tlsOptionsName: "default",
 			expectedError:  true,
 		},
+		{
+			desc:           "Get an invalid tls config",
+			tlsOptionsName: "invalid",
+			expectedError:  true,
+		},
 	}
 
 	tlsManager := NewManager()
@@ -161,42 +167,13 @@ func TestManager_Get(t *testing.T) {
 
 			config, err := tlsManager.Get("default", test.tlsOptionsName)
 			if test.expectedError {
-				assert.Error(t, err)
+				require.Nil(t, config)
+				require.Error(t, err)
 				return
 			}
 
-			assert.NoError(t, err)
-			assert.Equal(t, config.MinVersion, test.expectedMinVersion)
-		})
-	}
-}
-
-func TestManager_Get_GetCertificate(t *testing.T) {
-	testCases := []struct {
-		desc                 string
-		expectedGetConfigErr require.ErrorAssertionFunc
-		expectedCertificate  assert.ValueAssertionFunc
-	}{
-		{
-			desc:                 "Get a default certificate from non-existing store",
-			expectedGetConfigErr: require.Error,
-			expectedCertificate:  assert.Nil,
-		},
-	}
-
-	tlsManager := NewManager()
-
-	for _, test := range testCases {
-		test := test
-		t.Run(test.desc, func(t *testing.T) {
-			t.Parallel()
-
-			config, err := tlsManager.Get("default", "foo")
-			test.expectedGetConfigErr(t, err)
-
-			certificate, err := config.GetCertificate(&tls.ClientHelloInfo{})
 			require.NoError(t, err)
-			test.expectedCertificate(t, certificate)
+			assert.Equal(t, config.MinVersion, test.expectedMinVersion)
 		})
 	}
 }