Repository: traefik/traefik (language: go)

Issue:
Allow assuming default host if no SNI indication is given (for Let's Encrypt)
### Pull request

I rolled up my sleeves and wrote the functionality myself: #8124 

### Do you want to request a *feature* or report a *bug*?

Feature

### What did you expect to see?

Following up on https://github.com/traefik/traefik/issues/2829, I have a situation where I'd like to proxy Redis (TCP, non-HTTP) connections over TLS with automatic Let's Encrypt certificates. (I am aware that Redis 6 has built-in TLS, but it doesn't have built-in Let's Encrypt 😄)

Redis clients in general do not send SNI indications, so one needs a HostSNI(`*`) on the router for it to basically do any routing. However, with that enabled, Traefik always serves the built-in default certificate since there's no other indication on the connection. `openssl s_client` connects nicely, and I can talk Redis to the underlying server, but the certificate is `CN = TRAEFIK DEFAULT CERT`, of course.

### Takeaway

It would be handy to be able to tell Traefik to assume a given fallback SNI name if none was sent by the peer, e.g. (c.f. the below example configuration)

```toml
[tcp.routers.redis-tls-router.tls]
fallbackSNI = "some-example-domain.example.com"
```

### Testing configuration

My very simple file-based testing configuration is (with DNS names censored)

#### static configuration

```toml
[log]
level = "DEBUG"

[entryPoints]
web = { address = ":80" }
redis-tls = { address = ":55555" }

[certificatesResolvers.le.acme]
email = "example@example.com"
storage = "acme.json"
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"

[certificatesResolvers.le.acme.httpChallenge]
entryPoint = "web"

[providers.file]
filename = "/root/traefik.dyn.toml"
```

#### traefik.dyn.toml
```toml
[tcp.routers.redis-tls-router]
entryPoints = [ "redis-tls" ]
rule = "HostSNI(`*`)"
service = "redis"

[tcp.routers.redis-tls-router.tls]
certResolver = "le"

[[tcp.routers.redis-tls-router.tls.domains]]
main = "some-example-domain.example.com"

[tcp.services.redis.loadBalancer]
servers = [ { address = "127.0.0.1:6379" } ]
```



Discussion / hints:

Fix patch:
diff --git a/docs/content/https/tls.md b/docs/content/https/tls.md
index 62a5098cc8..dd756dd44d 100644
--- a/docs/content/https/tls.md
+++ b/docs/content/https/tls.md
@@ -157,7 +157,75 @@ data:
   tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
 ```
 
-If no default certificate is provided, Traefik generates and uses a self-signed certificate.
+If no `defaultCertificate` is provided, Traefik will use the generated one.
+
+### ACME Default Certificate
+
+You can configure Traefik to use an ACME provider (like Let's Encrypt) to generate the default certificate.
+The configuration to resolve the default certificate should be defined in a TLS store:
+
+!!! important "Precedence with the `defaultGeneratedCert` option"
+
+    The `defaultGeneratedCert` definition takes precedence over the ACME default certificate configuration.
+
+```yaml tab="File (YAML)"
+# Dynamic configuration
+
+tls:
+  stores:
+    default:
+      defaultGeneratedCert:
+        resolver: myresolver
+        domain:
+          main: example.org
+          sans:
+            - foo.example.org
+            - bar.example.org
+```
+
+```toml tab="File (TOML)"
+# Dynamic configuration
+
+[tls.stores]
+  [tls.stores.default.defaultGeneratedCert]
+    resolver = "myresolver"
+    [tls.stores.default.defaultGeneratedCert.domain]
+      main = "example.org"
+      sans = ["foo.example.org", "bar.example.org"]
+```
+
+```yaml tab="Kubernetes"
+apiVersion: traefik.containo.us/v1alpha1
+kind: TLSStore
+metadata:
+  name: default
+  namespace: default
+
+spec:
+  defaultGeneratedCert:
+    resolver: myresolver
+    domain:
+      main: example.org
+      sans:
+        - foo.example.org
+        - bar.example.org
+```
+
+```yaml tab="Docker"
+## Dynamic configuration
+labels:
+  - "traefik.tls.stores.default.defaultgeneratedcert.resolver=myresolver"
+  - "traefik.tls.stores.default.defaultgeneratedcert.domain.main=example.org"
+  - "traefik.tls.stores.default.defaultgeneratedcert.domain.sans=foo.example.org, bar.example.org"
+```
+
+```json tab="Marathon"
+labels: {
+  "traefik.tls.stores.default.defaultgeneratedcert.resolver": "myresolver",
+  "traefik.tls.stores.default.defaultgeneratedcert.domain.main": "example.org",
+  "traefik.tls.stores.default.defaultgeneratedcert.domain.sans": "foo.example.org, bar.example.org",
+}
+```
 
 ## TLS Options
 
diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml
index 6716ef8e52..c413f27bd8 100644
--- a/docs/content/reference/dynamic-configuration/docker-labels.yml
+++ b/docs/content/reference/dynamic-configuration/docker-labels.yml
@@ -201,3 +201,13 @@
 - "traefik.udp.routers.udprouter1.entrypoints=foobar, foobar"
 - "traefik.udp.routers.udprouter1.service=foobar"
 - "traefik.udp.services.udpservice01.loadbalancer.server.port=foobar"
+- "traefik.tls.stores.Store0.defaultcertificate.certfile=foobar"
+- "traefik.tls.stores.Store0.defaultcertificate.keyfile=foobar"
+- "traefik.tls.stores.Store0.defaultgeneratedcert.domain.main=foobar"
+- "traefik.tls.stores.Store0.defaultgeneratedcert.domain.sans=foobar, foobar"
+- "traefik.tls.stores.Store0.defaultgeneratedcert.resolver=foobar"
+- "traefik.tls.stores.Store1.defaultcertificate.certfile=foobar"
+- "traefik.tls.stores.Store1.defaultcertificate.keyfile=foobar"
+- "traefik.tls.stores.Store1.defaultgeneratedcert.domain.main=foobar"
+- "traefik.tls.stores.Store1.defaultgeneratedcert.domain.sans=foobar, foobar"
+- "traefik.tls.stores.Store1.defaultgeneratedcert.resolver=foobar"
diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml
index a7e23da179..895a1f97f1 100644
--- a/docs/content/reference/dynamic-configuration/file.toml
+++ b/docs/content/reference/dynamic-configuration/file.toml
@@ -463,7 +463,17 @@
       [tls.stores.Store0.defaultCertificate]
         certFile = "foobar"
         keyFile = "foobar"
+      [tls.stores.Store0.defaultGeneratedCert]
+        resolver = "foobar"
+        [tls.stores.Store0.defaultGeneratedCert.domain]
+          main = "foobar"
+          sans = ["foobar", "foobar"]
     [tls.stores.Store1]
       [tls.stores.Store1.defaultCertificate]
         certFile = "foobar"
         keyFile = "foobar"
+      [tls.stores.Store1.defaultGeneratedCert]
+        resolver = "foobar"
+        [tls.stores.Store1.defaultGeneratedCert.domain]
+          main = "foobar"
+          sans = ["foobar", "foobar"]
diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml
index 7d95a4e8f2..ec30817783 100644
--- a/docs/content/reference/dynamic-configuration/file.yaml
+++ b/docs/content/reference/dynamic-configuration/file.yaml
@@ -518,7 +518,21 @@ tls:
       defaultCertificate:
         certFile: foobar
         keyFile: foobar
+      defaultGeneratedCert:
+        resolver: foobar
+        domain:
+          main: foobar
+          sans:
+            - foobar
+            - foobar
     Store1:
       defaultCertificate:
         certFile: foobar
         keyFile: foobar
+      defaultGeneratedCert:
+        resolver: foobar
+        domain:
+          main: foobar
+          sans:
+            - foobar
+            - foobar
diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
index 2bcfe5b28d..72845c0982 100644
--- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
+++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
@@ -1870,6 +1870,27 @@ spec:
                 required:
                 - secretName
                 type: object
+              defaultGeneratedCert:
+                description: DefaultGeneratedCert defines the default generated certificate
+                  configuration.
+                properties:
+                  domain:
+                    description: Domain is the domain definition for the DefaultCertificate.
+                    properties:
+                      main:
+                        description: Main defines the main domain name.
+                        type: string
+                      sans:
+                        description: SANs defines the subject alternative domain names.
+                        items:
+                          type: string
+                        type: array
+                    type: object
+                  resolver:
+                    description: Resolver is the name of the resolver that will be
+                      used to issue the DefaultCertificate.
+                    type: string
+                type: object
             type: object
         required:
         - metadata
diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md
index 7927f2687e..c12162ccc8 100644
--- a/docs/content/reference/dynamic-configuration/kv-ref.md
+++ b/docs/content/reference/dynamic-configuration/kv-ref.md
@@ -319,8 +319,16 @@
 | `traefik/tls/options/Options1/sniStrict` | `true` |
 | `traefik/tls/stores/Store0/defaultCertificate/certFile` | `foobar` |
 | `traefik/tls/stores/Store0/defaultCertificate/keyFile` | `foobar` |
+| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/main` | `foobar` |
+| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/sans/0` | `foobar` |
+| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/sans/1` | `foobar` |
+| `traefik/tls/stores/Store0/defaultGeneratedCert/resolver` | `foobar` |
 | `traefik/tls/stores/Store1/defaultCertificate/certFile` | `foobar` |
 | `traefik/tls/stores/Store1/defaultCertificate/keyFile` | `foobar` |
+| `traefik/tls/stores/Store1/defaultGeneratedCert/domain/main` | `foobar` |
+| `traefik/tls/stores/Store1/defaultGeneratedCert/domain/sans/0` | `foobar` |
+| `traefik/tls/stores/Store1/defaultGeneratedCert/domain/sans/1` | `foobar` |
+| `traefik/tls/stores/Store1/defaultGeneratedCert/resolver` | `foobar` |
 | `traefik/udp/routers/UDPRouter0/entryPoints/0` | `foobar` |
 | `traefik/udp/routers/UDPRouter0/entryPoints/1` | `foobar` |
 | `traefik/udp/routers/UDPRouter0/service` | `foobar` |
diff --git a/docs/content/reference/dynamic-configuration/marathon-labels.json b/docs/content/reference/dynamic-configuration/marathon-labels.json
index 7eba469894..5da0048199 100644
--- a/docs/content/reference/dynamic-configuration/marathon-labels.json
+++ b/docs/content/reference/dynamic-configuration/marathon-labels.json
@@ -201,3 +201,13 @@
 "traefik.udp.routers.udprouter1.entrypoints": "foobar, foobar",
 "traefik.udp.routers.udprouter1.service": "foobar",
 "traefik.udp.services.udpservice01.loadbalancer.server.port": "foobar",
+"traefik.tls.stores.Store0.defaultcertificate.certfile": "foobar",
+"traefik.tls.stores.Store0.defaultcertificate.keyfile": "foobar",
+"traefik.tls.stores.Store0.defaultgeneratedcert.domain.main": "foobar",
+"traefik.tls.stores.Store0.defaultgeneratedcert.domain.sans": "foobar, foobar",
+"traefik.tls.stores.Store0.defaultgeneratedcert.resolver": "foobar",
+"traefik.tls.stores.Store1.defaultcertificate.certfile": "foobar",
+"traefik.tls.stores.Store1.defaultcertificate.keyfile": "foobar",
+"traefik.tls.stores.Store1.defaultgeneratedcert.domain.main": "foobar",
+"traefik.tls.stores.Store1.defaultgeneratedcert.domain.sans": "foobar, foobar",
+"traefik.tls.stores.Store1.defaultgeneratedcert.resolver": "foobar",
diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml
index dd31abc3ad..924eba2850 100644
--- a/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml
+++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml
@@ -63,6 +63,27 @@ spec:
                 required:
                 - secretName
                 type: object
+              defaultGeneratedCert:
+                description: DefaultGeneratedCert defines the default generated certificate
+                  configuration.
+                properties:
+                  domain:
+                    description: Domain is the domain definition for the DefaultCertificate.
+                    properties:
+                      main:
+                        description: Main defines the main domain name.
+                        type: string
+                      sans:
+                        description: SANs defines the subject alternative domain names.
+                        items:
+                          type: string
+                        type: array
+                    type: object
+                  resolver:
+                    description: Resolver is the name of the resolver that will be
+                      used to issue the DefaultCertificate.
+                    type: string
+                type: object
             type: object
         required:
         - metadata
diff --git a/integration/fixtures/acme/acme_store_domains.toml b/integration/fixtures/acme/acme_store_domains.toml
new file mode 100644
index 0000000000..14bb799d9b
--- /dev/null
+++ b/integration/fixtures/acme/acme_store_domains.toml
@@ -0,0 +1,60 @@
+[global]
+  checkNewVersion = false
+  sendAnonymousUsage = false
+
+[log]
+  level = "DEBUG"
+
+[entryPoints]
+  [entryPoints.web]
+    address = "{{ .PortHTTP }}"
+  [entryPoints.websecure]
+    address = "{{ .PortHTTPS }}"
+
+{{range $name, $resolvers := .Acme }}
+
+[certificatesResolvers.{{ $name }}.acme]
+  email = "test@traefik.io"
+  storage = "/tmp/acme.json"
+  keyType = "{{ $resolvers.ACME.KeyType }}"
+  caServer = "{{ $resolvers.ACME.CAServer }}"
+
+  {{if $resolvers.ACME.HTTPChallenge }}
+  [certificatesResolvers.{{ $name }}.acme.httpChallenge]
+    entryPoint = "{{ $resolvers.ACME.HTTPChallenge.EntryPoint }}"
+  {{end}}
+
+  {{if $resolvers.ACME.TLSChallenge }}
+  [certificatesResolvers.{{ $name }}.acme.tlsChallenge]
+  {{end}}
+
+{{end}}
+
+[api]
+  insecure = true
+
+[providers.file]
+  filename = "{{ .SelfFilename }}"
+
+## dynamic configuration ##
+
+[http.services]
+  [http.services.test.loadBalancer]
+    [[http.services.test.loadBalancer.servers]]
+      url = "http://127.0.0.1:9010"
+
+[http.routers]
+  [http.routers.test]
+    entryPoints = ["websecure"]
+    rule = "PathPrefix(`/`)"
+    service = "test"
+    [http.routers.test.tls]
+
+[tls.stores]
+  [tls.stores.default.defaultGeneratedCert]
+    resolver = "default"
+    [tls.stores.default.defaultGeneratedCert.domain]
+      main = "{{ .Domain.Main }}"
+      sans = [{{range .Domain.SANs }}
+        "{{.}}",
+        {{end}}]
diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml
index 2bcfe5b28d..72845c0982 100644
--- a/integration/fixtures/k8s/01-traefik-crd.yml
+++ b/integration/fixtures/k8s/01-traefik-crd.yml
@@ -1870,6 +1870,27 @@ spec:
                 required:
                 - secretName
                 type: object
+              defaultGeneratedCert:
+                description: DefaultGeneratedCert defines the default generated certificate
+                  configuration.
+                properties:
+                  domain:
+                    description: Domain is the domain definition for the DefaultCertificate.
+                    properties:
+                      main:
+                        description: Main defines the main domain name.
+                        type: string
+                      sans:
+                        description: SANs defines the subject alternative domain names.
+                        items:
+                          type: string
+                        type: array
+                    type: object
+                  resolver:
+                    description: Resolver is the name of the resolver that will be
+                      used to issue the DefaultCertificate.
+                    type: string
+                type: object
             type: object
         required:
         - metadata
diff --git a/pkg/provider/acme/provider.go b/pkg/provider/acme/provider.go
index a403b94baf..81a77ab5ba 100644
--- a/pkg/provider/acme/provider.go
+++ b/pkg/provider/acme/provider.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"net/url"
 	"reflect"
+	"sort"
 	"strings"
 	"sync"
 	"time"
@@ -29,8 +30,8 @@ import (
 	"github.com/traefik/traefik/v2/pkg/version"
 )
 
-// oscpMustStaple enables OSCP stapling as from https://github.com/go-acme/lego/issues/270.
-var oscpMustStaple = false
+// ocspMustStaple enables OCSP stapling as from https://github.com/go-acme/lego/issues/270.
+var ocspMustStaple = false
 
 // Configuration holds ACME configuration provided by users.
 type Configuration struct {
@@ -99,10 +100,11 @@ type Provider struct {
 	TLSChallengeProvider  challenge.Provider
 	HTTPChallengeProvider challenge.Provider
 
-	certificates           []*CertAndStore
+	certificates   []*CertAndStore
+	certificatesMu sync.RWMutex
+
 	account                *Account
 	client                 *lego.Client
-	certsChan              chan *CertAndStore
 	configurationChan      chan<- dynamic.Message
 	tlsManager             *traefiktls.Manager
 	clientMutex            sync.Mutex
@@ -152,7 +154,10 @@ func (p *Provider) Init() error {
 		p.account = nil
 	}
 
+	p.certificatesMu.Lock()
 	p.certificates, err = p.Store.GetCertificates(p.ResolverName)
+	p.certificatesMu.Unlock()
+
 	if err != nil {
 		return fmt.Errorf("unable to get ACME certificates : %w", err)
 	}
@@ -195,11 +200,15 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
 
 	p.pool = pool
 
-	p.watchCertificate(ctx)
 	p.watchNewDomains(ctx)
 
 	p.configurationChan = configurationChan
-	p.refreshCertificates()
+
+	p.certificatesMu.RLock()
+	msg := p.buildMessage()
+	p.certificatesMu.RUnlock()
+
+	p.configurationChan <- msg
 
 	renewPeriod, renewInterval := getCertificateRenewDurations(p.CertificatesDuration)
 	log.FromContext(ctx).Debugf("Attempt to renew certificates %q before expiry and check every %q",
@@ -365,12 +374,14 @@ func (p *Provider) register(ctx context.Context, client *lego.Client) (*registra
 }
 
 func (p *Provider) resolveDomains(ctx context.Context, domains []string, tlsStore string) {
+	logger := log.FromContext(ctx)
+
 	if len(domains) == 0 {
-		log.FromContext(ctx).Debug("No domain parsed in provider ACME")
+		logger.Debug("No domain parsed in provider ACME")
 		return
 	}
 
-	log.FromContext(ctx).Debugf("Try to challenge certificate for domain %v found in HostSNI rule", domains)
+	logger.Debugf("Trying to challenge certificate for domain %v found in HostSNI rule", domains)
 
 	var domain types.Domain
 	if len(domains) > 0 {
@@ -380,14 +391,22 @@ func (p *Provider) resolveDomains(ctx context.Context, domains []string, tlsStor
 		}
 
 		safe.Go(func() {
-			if _, err := p.resolveCertificate(ctx, domain, tlsStore); err != nil {
-				log.FromContext(ctx).Errorf("Unable to obtain ACME certificate for domains %q: %v", strings.Join(domains, ","), err)
+			dom, cert, err := p.resolveCertificate(ctx, domain, tlsStore)
+			if err != nil {
+				logger.Errorf("Unable to obtain ACME certificate for domains %q: %v", strings.Join(domains, ","), err)
+				return
+			}
+
+			err = p.addCertificateForDomain(dom, cert.Certificate, cert.PrivateKey, tlsStore)
+			if err != nil {
+				logger.WithError(err).Error("Error adding certificate for domain")
 			}
 		})
 	}
 }
 
 func (p *Provider) watchNewDomains(ctx context.Context) {
+	ctx = log.With(ctx, log.Str(log.ProviderName, p.ResolverName+".acme"))
 	p.pool.GoCtx(func(ctxPool context.Context) {
 		for {
 			select {
@@ -402,31 +421,63 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
 						logger := log.FromContext(ctxRouter)
 
 						if len(route.TLS.Domains) > 0 {
-							for _, domain := range route.TLS.Domains {
-								if domain.Main != dns01.UnFqdn(domain.Main) {
-									logger.Warnf("FQDN detected, please remove the trailing dot: %s", domain.Main)
-								}
-								for _, san := range domain.SANs {
-									if san != dns01.UnFqdn(san) {
-										logger.Warnf("FQDN detected, please remove the trailing dot: %s", san)
+							domains := deleteUnnecessaryDomains(ctxRouter, route.TLS.Domains)
+							for i := 0; i < len(domains); i++ {
+								domain := domains[i]
+								safe.Go(func() {
+									dom, cert, err := p.resolveCertificate(ctx, domain, traefiktls.DefaultTLSStoreName)
+									if err != nil {
+										logger.WithError(err).Errorf("Unable to obtain ACME certificate for domains %q", strings.Join(domain.ToStrArray(), ","))
+										return
+									}
+
+									err = p.addCertificateForDomain(dom, cert.Certificate, cert.PrivateKey, traefiktls.DefaultTLSStoreName)
+									if err != nil {
+										logger.WithError(err).Error("Error adding certificate for domain")
 									}
-								}
+								})
+							}
+						} else {
+							domains, err := tcpmuxer.ParseHostSNI(route.Rule)
+							if err != nil {
+								logger.WithError(err).Errorf("Error parsing domains in provider ACME")
+								continue
 							}
+							p.resolveDomains(ctxRouter, domains, traefiktls.DefaultTLSStoreName)
+						}
+					}
+				}
+
+				if config.HTTP != nil {
+					for routerName, route := range config.HTTP.Routers {
+						if route.TLS == nil || route.TLS.CertResolver != p.ResolverName {
+							continue
+						}
+
+						ctxRouter := log.With(ctx, log.Str(log.RouterName, routerName), log.Str(log.Rule, route.Rule))
+						logger := log.FromContext(ctxRouter)
 
+						if len(route.TLS.Domains) > 0 {
 							domains := deleteUnnecessaryDomains(ctxRouter, route.TLS.Domains)
 							for i := 0; i < len(domains); i++ {
 								domain := domains[i]
 								safe.Go(func() {
-									if _, err := p.resolveCertificate(ctx, domain, traefiktls.DefaultTLSStoreName); err != nil {
-										log.WithoutContext().WithField(log.ProviderName, p.ResolverName+".acme").
-											Errorf("Unable to obtain ACME certificate for domains %q : %v", strings.Join(domain.ToStrArray(), ","), err)
+									dom, cert, err := p.resolveCertificate(ctx, domain, traefiktls.DefaultTLSStoreName)
+									if err != nil {
+										logger.WithError(err).Errorf("Unable to obtain ACME certificate for domains %q", strings.Join(domain.ToStrArray(), ","))
+										return
+									}
+
+									err = p.addCertificateForDomain(dom, cert.Certificate, cert.PrivateKey, traefiktls.DefaultTLSStoreName)
+									if err != nil {
+										logger.WithError(err).Error("Error adding certificate for domain")
 									}
 								})
 							}
 						} else {
-							domains, err := tcpmuxer.ParseHostSNI(route.Rule)
+							domains, err := httpmuxer.ParseDomains(route.Rule)
 							if err != nil {
-								logger.Errorf("Error parsing domains in provider ACME: %v", err)
+								logger.WithError(err).Errorf("Error parsing domains in provider ACME")
 								continue
 							}
 							p.resolveDomains(ctxRouter, domains, traefiktls.DefaultTLSStoreName)
@@ -434,32 +485,61 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
 					}
 				}
 
-				for routerName, route := range config.HTTP.Routers {
-					if route.TLS == nil || route.TLS.CertResolver != p.ResolverName {
+				if config.TLS == nil {
+					continue
+				}
+
+				for tlsStoreName, tlsStore := range config.TLS.Stores {
+					ctxTLSStore := log.With(ctx, log.Str(log.TLSStoreName, tlsStoreName))
+					logger := log.FromContext(ctxTLSStore)
+
+					if tlsStore.DefaultCertificate != nil && tlsStore.DefaultGeneratedCert != nil {
+						logger.Warn("defaultCertificate and defaultGeneratedCert cannot be defined at the same time.")
+					}
+
+					// Gives precedence to the user defined default certificate.
+					if tlsStore.DefaultCertificate != nil || tlsStore.DefaultGeneratedCert == nil {
 						continue
 					}
 
-					ctxRouter := log.With(ctx, log.Str(log.RouterName, routerName), log.Str(log.Rule, route.Rule))
-
-					if len(route.TLS.Domains) > 0 {
-						domains := deleteUnnecessaryDomains(ctxRouter, route.TLS.Domains)
-						for i := 0; i < len(domains); i++ {
-							domain := domains[i]
-							safe.Go(func() {
-								if _, err := p.resolveCertificate(ctx, domain, traefiktls.DefaultTLSStoreName); err != nil {
-									log.WithoutContext().WithField(log.ProviderName, p.ResolverName+".acme").
-										Errorf("Unable to obtain ACME certificate for domains %q : %v", strings.Join(domain.ToStrArray(), ","), err)
-								}
-							})
+					if tlsStore.DefaultGeneratedCert.Domain == nil || tlsStore.DefaultGeneratedCert.Resolver == "" {
+						logger.Warn("default generated certificate domain or resolver is missing.")
+						continue
+					}
+
+					if tlsStore.DefaultGeneratedCert.Resolver != p.ResolverName {
+						continue
+					}
+
+					validDomains, err := p.sanitizeDomains(ctx, *tlsStore.DefaultGeneratedCert.Domain)
+					if err != nil {
+						logger.WithError(err).Errorf("domains validation: %s", strings.Join(tlsStore.DefaultGeneratedCert.Domain.ToStrArray(), ","))
+					}
+
+					if p.certExists(validDomains) {
+						logger.Debug("Default ACME certificate generation is not required.")
+						continue
+					}
+
+					safe.Go(func() {
+						cert, err := p.resolveDefaultCertificate(ctx, validDomains)
+						if err != nil {
+							logger.WithError(err).Errorf("Unable to obtain ACME certificate for domain %q", strings.Join(validDomains, ","))
+							return
+						}
+
+						domain := types.Domain{
+							Main: validDomains[0],
 						}
-					} else {
-						domains, err := httpmuxer.ParseDomains(route.Rule)
+						if len(validDomains) > 0 {
+							domain.SANs = validDomains[1:]
+						}
+
+						err = p.addCertificateForDomain(domain, cert.Certificate, cert.PrivateKey, traefiktls.DefaultTLSStoreName)
 						if err != nil {
-							log.FromContext(ctxRouter).Errorf("Error parsing domains in provider ACME: %v", err)
-							continue
+							logger.WithError(err).Error("Error adding certificate for domain")
 						}
-						p.resolveDomains(ctxRouter, domains, traefiktls.DefaultTLSStoreName)
-					}
+					})
 				}
 			case <-ctxPool.Done():
 				return
@@ -468,16 +548,69 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
 	})
 }
 
-func (p *Provider) resolveCertificate(ctx context.Context, domain types.Domain, tlsStore string) (*certificate.Resource, error) {
-	domains, err := p.getValidDomains(ctx, domain)
+func (p *Provider) resolveDefaultCertificate(ctx context.Context, domains []string) (*certificate.Resource, error) {
+	logger := log.FromContext(ctx)
+
+	p.resolvingDomainsMutex.Lock()
+
+	sort.Strings(domains)
+	domainKey := strings.Join(domains, ",")
+
+	if _, ok := p.resolvingDomains[domainKey]; ok {
+		p.resolvingDomainsMutex.Unlock()
+		return nil, nil
+	}
+
+	p.resolvingDomains[domainKey] = struct{}{}
+
+	for _, certDomain := range domains {
+		p.resolvingDomains[certDomain] = struct{}{}
+	}
+
+	p.resolvingDomainsMutex.Unlock()
+
+	defer p.removeResolvingDomains(append(domains, domainKey))
+
+	logger.Debugf("Loading ACME certificates %+v...", domains)
+
+	client, err := p.getClient()
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("cannot get ACME client %w", err)
+	}
+
+	request := certificate.ObtainRequest{
+		Domains:        domains,
+		Bundle:         true,
+		MustStaple:     ocspMustStaple,
+		PreferredChain: p.PreferredChain,
+	}
+
+	cert, err := client.Certificate.Obtain(request)
+	if err != nil {
+		return nil, fmt.Errorf("unable to generate a certificate for the domains %v: %w", domains, err)
+	}
+	if cert == nil {
+		return nil, fmt.Errorf("unable to generate a certificate for the domains %v", domains)
+	}
+	if len(cert.Certificate) == 0 || len(cert.PrivateKey) == 0 {
+		return nil, fmt.Errorf("certificate for domains %v is empty: %v", domains, cert)
+	}
+
+	logger.Debugf("Default certificate obtained for domains %+v", domains)
+
+	return cert, nil
+}
+
+func (p *Provider) resolveCertificate(ctx context.Context, domain types.Domain, tlsStore string) (types.Domain, *certificate.Resource, error) {
+	domains, err := p.sanitizeDomains(ctx, domain)
+	if err != nil {
+		return types.Domain{}, nil, err
 	}
 
 	// Check if provided certificates are not already in progress and lock them if needed
 	uncheckedDomains := p.getUncheckedDomains(ctx, domains, tlsStore)
 	if len(uncheckedDomains) == 0 {
-		return nil, nil
+		return types.Domain{}, nil, nil
 	}
 
 	defer p.removeResolvingDomains(uncheckedDomains)
@@ -487,37 +620,35 @@ func (p *Provider) resolveCertificate(ctx context.Context, domain types.Domain,
 
 	client, err := p.getClient()
 	if err != nil {
-		return nil, fmt.Errorf("cannot get ACME client %w", err)
+		return types.Domain{}, nil, fmt.Errorf("cannot get ACME client %w", err)
 	}
 
 	request := certificate.ObtainRequest{
 		Domains:        domains,
 		Bundle:         true,
-		MustStaple:     oscpMustStaple,
+		MustStaple:     ocspMustStaple,
 		PreferredChain: p.PreferredChain,
 	}
 
 	cert, err := client.Certificate.Obtain(request)
 	if err != nil {
-		return nil, fmt.Errorf("unable to generate a certificate for the domains %v: %w", uncheckedDomains, err)
+		return types.Domain{}, nil, fmt.Errorf("unable to generate a certificate for the domains %v: %w", uncheckedDomains, err)
 	}
 	if cert == nil {
-		return nil, fmt.Errorf("domains %v do not generate a certificate", uncheckedDomains)
+		return types.Domain{}, nil, fmt.Errorf("unable to generate a certificate for the domains %v", uncheckedDomains)
 	}
 	if len(cert.Certificate) == 0 || len(cert.PrivateKey) == 0 {
-		return nil, fmt.Errorf("domains %v generate certificate with no value: %v", uncheckedDomains, cert)
+		return types.Domain{}, nil, fmt.Errorf("certificate for domains %v is empty: %v", uncheckedDomains, cert)
 	}
 
 	logger.Debugf("Certificates obtained for domains %+v", uncheckedDomains)
 
+	domain = types.Domain{Main: uncheckedDomains[0]}
 	if len(uncheckedDomains) > 1 {
-		domain = types.Domain{Main: uncheckedDomains[0], SANs: uncheckedDomains[1:]}
-	} else {
-		domain = types.Domain{Main: uncheckedDomains[0]}
+		domain.SANs = uncheckedDomains[1:]
 	}
-	p.addCertificateForDomain(domain, cert.Certificate, cert.PrivateKey, tlsStore)
 
-	return cert, nil
+	return domain, cert, nil
 }
 
 func (p *Provider) removeResolvingDomains(resolvingDomains []string) {
@@ -529,8 +660,28 @@ func (p *Provider) removeResolvingDomains(resolvingDomains []string) {
 	}
 }
 
-func (p *Provider) addCertificateForDomain(domain types.Domain, certificate, key []byte, tlsStore string) {
-	p.certsChan <- &CertAndStore{Certificate: Certificate{Certificate: certificate, Key: key, Domain: domain}, Store: tlsStore}
+func (p *Provider) addCertificateForDomain(domain types.Domain, certificate, key []byte, tlsStore string) error {
+	p.certificatesMu.Lock()
+	defer p.certificatesMu.Unlock()
+
+	cert := Certificate{Certificate: certificate, Key: key, Domain: domain}
+
+	certUpdated := false
+	for _, domainsCertificate := range p.certificates {
+		if reflect.DeepEqual(domain, domainsCertificate.Certificate.Domain) {
+			domainsCertificate.Certificate = cert
+			certUpdated = true
+			break
+		}
+	}
+
+	if !certUpdated {
+		p.certificates = append(p.certificates, &CertAndStore{Certificate: cert, Store: tlsStore})
+	}
+
+	p.configurationChan <- p.buildMessage()
+
+	return p.Store.SaveCertificates(p.ResolverName, p.certificates)
 }
 
 // getCertificateRenewDurations returns renew durations calculated from the given certificatesDuration in hours.
@@ -608,45 +759,7 @@ func deleteUnnecessaryDomains(ctx context.Context, domains []types.Domain) []typ
 	return newDomains
 }
 
-func (p *Provider) watchCertificate(ctx context.Context) {
-	p.certsChan = make(chan *CertAndStore)
-
-	p.pool.GoCtx(func(ctxPool context.Context) {
-		for {
-			select {
-			case cert := <-p.certsChan:
-				certUpdated := false
-				for _, domainsCertificate := range p.certificates {
-					if reflect.DeepEqual(cert.Domain, domainsCertificate.Certificate.Domain) {
-						domainsCertificate.Certificate = cert.Certificate
-						certUpdated = true
-						break
-					}
-				}
-				if !certUpdated {
-					p.certificates = append(p.certificates, cert)
-				}
-
-				err := p.saveCertificates()
-				if err != nil {
-					log.FromContext(ctx).Error(err)
-				}
-			case <-ctxPool.Done():
-				return
-			}
-		}
-	})
-}
-
-func (p *Provider) saveCertificates() error {
-	err := p.Store.SaveCertificates(p.ResolverName, p.certificates)
-
-	p.refreshCertificates()
-
-	return err
-}
-
-func (p *Provider) refreshCertificates() {
+func (p *Provider) buildMessage() dynamic.Message {
 	conf := dynamic.Message{
 		ProviderName: p.ResolverName + ".acme",
 		Configuration: &dynamic.Configuration{
@@ -670,41 +783,54 @@ func (p *Provider) refreshCertificates() {
 		conf.Configuration.TLS.Certificates = append(conf.Configuration.TLS.Certificates, certConf)
 	}
 
-	p.configurationChan <- conf
+	return conf
 }
 
 func (p *Provider) renewCertificates(ctx context.Context, renewPeriod time.Duration) {
 	logger := log.FromContext(ctx)
 
 	logger.Info("Testing certificate renew...")
+
+	p.certificatesMu.RLock()
+
+	var certificates []*CertAndStore
 	for _, cert := range p.certificates {
 		crt, err := getX509Certificate(ctx, &cert.Certificate)
 		// If there's an error, we assume the cert is broken, and needs update
 		if err != nil || crt == nil || crt.NotAfter.Before(time.Now().Add(renewPeriod)) {
-			client, err := p.getClient()
-			if err != nil {
-				logger.Infof("Error renewing certificate from LE : %+v, %v", cert.Domain, err)
-				continue
-			}
+			certificates = append(certificates, cert)
+		}
+	}
 
-			logger.Infof("Renewing certificate from LE : %+v", cert.Domain)
+	p.certificatesMu.RUnlock()
 
-			renewedCert, err := client.Certificate.Renew(certificate.Resource{
-				Domain:      cert.Domain.Main,
-				PrivateKey:  cert.Key,
-				Certificate: cert.Certificate.Certificate,
-			}, true, oscpMustStaple, p.PreferredChain)
-			if err != nil {
-				logger.Errorf("Error renewing certificate from LE: %v, %v", cert.Domain, err)
-				continue
-			}
+	for _, cert := range certificates {
+		client, err := p.getClient()
+		if err != nil {
+			logger.WithError(err).Infof("Error renewing certificate from LE : %+v", cert.Domain)
+			continue
+		}
 
-			if len(renewedCert.Certificate) == 0 || len(renewedCert.PrivateKey) == 0 {
-				logger.Errorf("domains %v renew certificate with no value: %v", cert.Domain.ToStrArray(), cert)
-				continue
-			}
+		logger.Infof("Renewing certificate from LE : %+v", cert.Domain)
+
+		renewedCert, err := client.Certificate.Renew(certificate.Resource{
+			Domain:      cert.Domain.Main,
+			PrivateKey:  cert.Key,
+			Certificate: cert.Certificate.Certificate,
+		}, true, ocspMustStaple, p.PreferredChain)
+		if err != nil {
+			logger.WithError(err).Errorf("Error renewing certificate from LE: %v", cert.Domain)
+			continue
+		}
 
-			p.addCertificateForDomain(cert.Domain, renewedCert.Certificate, renewedCert.PrivateKey, cert.Store)
+		if len(renewedCert.Certificate) == 0 || len(renewedCert.PrivateKey) == 0 {
+			logger.Errorf("domains %v renew certificate with no value: %v", cert.Domain.ToStrArray(), cert)
+			continue
+		}
+
+		err = p.addCertificateForDomain(cert.Domain, renewedCert.Certificate, renewedCert.PrivateKey, cert.Store)
+		if err != nil {
+			logger.WithError(err).Error("Error adding certificate for domain")
 		}
 	}
 }
@@ -712,17 +838,24 @@ func (p *Provider) renewCertificates(ctx context.Context, renewPeriod time.Durat
 // Get provided certificate which check a domains list (Main and SANs)
 // from static and dynamic provided certificates.
 func (p *Provider) getUncheckedDomains(ctx context.Context, domainsToCheck []string, tlsStore string) []string {
-	p.resolvingDomainsMutex.Lock()
-	defer p.resolvingDomainsMutex.Unlock()
-
 	log.FromContext(ctx).Debugf("Looking for provided certificate(s) to validate %q...", domainsToCheck)
 
-	allDomains := p.tlsManager.GetStore(tlsStore).GetAllDomains()
+	var allDomains []string
+	store := p.tlsManager.GetStore(tlsStore)
+	if store != nil {
+		allDomains = append(allDomains, store.GetAllDomains()...)
+	}
 
 	// Get ACME certificates
+
+	p.certificatesMu.RLock()
 	for _, cert := range p.certificates {
 		allDomains = append(allDomains, strings.Join(cert.Domain.ToStrArray(), ","))
 	}
+	p.certificatesMu.RUnlock()
+
+	p.resolvingDomainsMutex.Lock()
+	defer p.resolvingDomainsMutex.Unlock()
 
 	// Get currently resolved domains
 	for domain := range p.resolvingDomains {
@@ -761,7 +894,7 @@ func getX509Certificate(ctx context.Context, cert *Certificate) (*x509.Certifica
 
 	tlsCert, err := tls.X509KeyPair(cert.Certificate, cert.Key)
 	if err != nil {
-		logger.Errorf("Failed to load TLS key pair from ACME certificate for domain %q (SAN : %q), certificate will be renewed : %v", cert.Domain.Main, strings.Join(cert.Domain.SANs, ","), err)
+		logger.WithError(err).Errorf("Failed to load TLS key pair from ACME certificate for domain %q (SAN : %q), certificate will be renewed", cert.Domain.Main, strings.Join(cert.Domain.SANs, ","))
 		return nil, err
 	}
 
@@ -769,43 +902,62 @@ func getX509Certificate(ctx context.Context, cert *Certificate) (*x509.Certifica
 	if crt == nil {
 		crt, err = x509.ParseCertificate(tlsCert.Certificate[0])
 		if err != nil {
-			logger.Errorf("Failed to parse TLS key pair from ACME certificate for domain %q (SAN : %q), certificate will be renewed : %v", cert.Domain.Main, strings.Join(cert.Domain.SANs, ","), err)
+			logger.WithError(err).Errorf("Failed to parse TLS key pair from ACME certificate for domain %q (SAN : %q), certificate will be renewed", cert.Domain.Main, strings.Join(cert.Domain.SANs, ","))
 		}
 	}
 
 	return crt, err
 }
 
-// getValidDomains checks if given domain is allowed to generate a ACME certificate and return it.
-func (p *Provider) getValidDomains(ctx context.Context, domain types.Domain) ([]string, error) {
+// sanitizeDomains checks if given domain is allowed to generate a ACME certificate and return it.
+func (p *Provider) sanitizeDomains(ctx context.Context, domain types.Domain) ([]string, error) {
 	domains := domain.ToStrArray()
 	if len(domains) == 0 {
-		return nil, errors.New("unable to generate a certificate in ACME provider when no domain is given")
+		return nil, errors.New("no domain was given")
 	}
 
-	if strings.HasPrefix(domain.Main, "*") {
-		if p.DNSChallenge == nil {
-			return nil, fmt.Errorf("unable to generate a wildcard certificate in ACME provider for domain %q : ACME needs a DNSChallenge", strings.Join(domains, ","))
-		}
+	var cleanDomains []string
+	for _, dom := range domains {
+		if strings.HasPrefix(dom, "*") {
+			if p.DNSChallenge == nil {
+				return nil, fmt.Errorf("unable to generate a wildcard certificate in ACME provider for domain %q : ACME needs a DNSChallenge", strings.Join(domains, ","))
+			}
 
-		if strings.HasPrefix(domain.Main, "*.*") {
-			return nil, fmt.Errorf("unable to generate a wildcard certificate in ACME provider for domain %q : ACME does not allow '*.*' wildcard domain", strings.Join(domains, ","))
+			if strings.HasPrefix(dom, "*.*") {
+				return nil, fmt.Errorf("unable to generate a wildcard certificate in ACME provider for domain %q : ACME does not allow '*.*' wildcard domain", strings.Join(domains, ","))
+			}
 		}
-	}
 
-	var cleanDomains []string
-	for _, domain := range domains {
-		canonicalDomain := types.CanonicalDomain(domain)
+		canonicalDomain := types.CanonicalDomain(dom)
 		cleanDomain := dns01.UnFqdn(canonicalDomain)
 		if canonicalDomain != cleanDomain {
 			log.FromContext(ctx).Warnf("FQDN detected, please remove the trailing dot: %s", canonicalDomain)
 		}
+
 		cleanDomains = append(cleanDomains, cleanDomain)
 	}
 
 	return cleanDomains, nil
 }
 
+// certExists returns whether a certificate already exists for given domains.
+func (p *Provider) certExists(validDomains []string) bool {
+	p.certificatesMu.RLock()
+	defer p.certificatesMu.RUnlock()
+
+	sort.Strings(validDomains)
+
+	for _, cert := range p.certificates {
+		domains := cert.Certificate.Domain.ToStrArray()
+		sort.Strings(domains)
+		if reflect.DeepEqual(domains, validDomains) {
+			return true
+		}
+	}
+
+	return false
+}
+
 func isDomainAlreadyChecked(domainToCheck string, existentDomains []string) bool {
 	for _, certDomains := range existentDomains {
 		for _, certDomain := range strings.Split(certDomains, ",") {
diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go
index 1052ba0f82..a43345e675 100644
--- a/pkg/provider/kubernetes/crd/kubernetes.go
+++ b/pkg/provider/kubernetes/crd/kubernetes.go
@@ -942,6 +942,13 @@ func buildTLSStores(ctx context.Context, client Client) (map[string]tls.Store, m
 			}
 		}
 
+		if t.Spec.DefaultGeneratedCert != nil {
+			tlsStore.DefaultGeneratedCert = &tls.GeneratedCert{
+				Resolver: t.Spec.DefaultGeneratedCert.Resolver,
+				Domain:   t.Spec.DefaultGeneratedCert.Domain,
+			}
+		}
+
 		if err := buildCertificates(client, id, t.Namespace, t.Spec.Certificates, tlsConfigs); err != nil {
 			logger.Errorf("Failed to load certificates: %v", err)
 			continue
diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go
index 305b403c1a..0d69d084f6 100644
--- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go
+++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go
@@ -1,6 +1,7 @@
 package v1alpha1
 
 import (
+	"github.com/traefik/traefik/v2/pkg/tls"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -27,6 +28,10 @@ type TLSStore struct {
 type TLSStoreSpec struct {
 	// DefaultCertificate defines the default certificate configuration.
 	DefaultCertificate *Certificate `json:"defaultCertificate,omitempty"`
+
+	// DefaultGeneratedCert defines the default generated certificate configuration.
+	DefaultGeneratedCert *tls.GeneratedCert `json:"defaultGeneratedCert,omitempty"`
+
 	// Certificates is a list of secret names, each secret holding a key/certificate pair to add to the store.
 	Certificates []Certificate `json:"certificates,omitempty"`
 }
diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go
index 5d44aeef3f..5cc7903fcd 100644
--- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go
@@ -31,6 +31,7 @@ package v1alpha1
 
 import (
 	dynamic "github.com/traefik/traefik/v2/pkg/config/dynamic"
+	tls "github.com/traefik/traefik/v2/pkg/tls"
 	types "github.com/traefik/traefik/v2/pkg/types"
 	v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	runtime "k8s.io/apimachinery/pkg/runtime"
@@ -1450,6 +1451,11 @@ func (in *TLSStoreSpec) DeepCopyInto(out *TLSStoreSpec) {
 		*out = new(Certificate)
 		**out = **in
 	}
+	if in.DefaultGeneratedCert != nil {
+		in, out := &in.DefaultGeneratedCert, &out.DefaultGeneratedCert
+		*out = new(tls.GeneratedCert)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.Certificates != nil {
 		in, out := &in.Certificates, &out.Certificates
 		*out = make([]Certificate, len(*in))
diff --git a/pkg/tls/certificate.go b/pkg/tls/certificate.go
index 8d2c8b61da..fca4a25865 100644
--- a/pkg/tls/certificate.go
+++ b/pkg/tls/certificate.go
@@ -11,7 +11,6 @@ import (
 	"strings"
 
 	"github.com/traefik/traefik/v2/pkg/log"
-	"github.com/traefik/traefik/v2/pkg/tls/generate"
 )
 
 var (
@@ -101,55 +100,8 @@ func (f FileOrContent) Read() ([]byte, error) {
 	return content, nil
 }
 
-// CreateTLSConfig creates a TLS config from Certificate structures.
-func (c *Certificates) CreateTLSConfig(entryPointName string) (*tls.Config, error) {
-	config := &tls.Config{}
-	domainsCertificates := make(map[string]map[string]*tls.Certificate)
-
-	if c.isEmpty() {
-		config.Certificates = []tls.Certificate{}
-
-		cert, err := generate.DefaultCertificate()
-		if err != nil {
-			return nil, err
-		}
-
-		config.Certificates = append(config.Certificates, *cert)
-	} else {
-		for _, certificate := range *c {
-			err := certificate.AppendCertificate(domainsCertificates, entryPointName)
-			if err != nil {
-				log.Errorf("Unable to add a certificate to the entryPoint %q : %v", entryPointName, err)
-				continue
-			}
-
-			for _, certDom := range domainsCertificates {
-				for _, cert := range certDom {
-					config.Certificates = append(config.Certificates, *cert)
-				}
-			}
-		}
-	}
-	return config, nil
-}
-
-// isEmpty checks if the certificates list is empty.
-func (c *Certificates) isEmpty() bool {
-	if len(*c) == 0 {
-		return true
-	}
-	var key int
-	for _, cert := range *c {
-		if len(cert.CertFile.String()) != 0 && len(cert.KeyFile.String()) != 0 {
-			break
-		}
-		key++
-	}
-	return key == len(*c)
-}
-
-// AppendCertificate appends a Certificate to a certificates map keyed by entrypoint.
-func (c *Certificate) AppendCertificate(certs map[string]map[string]*tls.Certificate, ep string) error {
+// AppendCertificate appends a Certificate to a certificates map keyed by store name.
+func (c *Certificate) AppendCertificate(certs map[string]map[string]*tls.Certificate, storeName string) error {
 	certContent, err := c.CertFile.Read()
 	if err != nil {
 		return fmt.Errorf("unable to read CertFile : %w", err)
@@ -171,7 +123,6 @@ func (c *Certificate) AppendCertificate(certs map[string]map[string]*tls.Certifi
 		SANs = append(SANs, strings.ToLower(parsedCert.Subject.CommonName))
 	}
 	if parsedCert.DNSNames != nil {
-		sort.Strings(parsedCert.DNSNames)
 		for _, dnsName := range parsedCert.DNSNames {
 			if dnsName != parsedCert.Subject.CommonName {
 				SANs = append(SANs, strings.ToLower(dnsName))
@@ -185,13 +136,16 @@ func (c *Certificate) AppendCertificate(certs map[string]map[string]*tls.Certifi
 			}
 		}
 	}
+
+	// Guarantees the order to produce a unique cert key.
+	sort.Strings(SANs)
 	certKey := strings.Join(SANs, ",")
 
 	certExists := false
-	if certs[ep] == nil {
-		certs[ep] = make(map[string]*tls.Certificate)
+	if certs[storeName] == nil {
+		certs[storeName] = make(map[string]*tls.Certificate)
 	} else {
-		for domains := range certs[ep] {
+		for domains := range certs[storeName] {
 			if domains == certKey {
 				certExists = true
 				break
@@ -199,10 +153,10 @@ func (c *Certificate) AppendCertificate(certs map[string]map[string]*tls.Certifi
 		}
 	}
 	if certExists {
-		log.Debugf("Skipping addition of certificate for domain(s) %q, to EntryPoint %s, as it already exists for this Entrypoint.", certKey, ep)
+		log.Debugf("Skipping addition of certificate for domain(s) %q, to TLS Store %s, as it already exists for this store.", certKey, storeName)
 	} else {
 		log.Debugf("Adding certificate for domain(s) %s", certKey)
-		certs[ep][certKey] = &tlsCert
+		certs[storeName][certKey] = &tlsCert
 	}
 
 	return err
diff --git a/pkg/tls/certificate_store.go b/pkg/tls/certificate_store.go
index 8017c981ab..1a954a0e0c 100644
--- a/pkg/tls/certificate_store.go
+++ b/pkg/tls/certificate_store.go
@@ -22,8 +22,11 @@ type CertificateStore struct {
 
 // NewCertificateStore create a store for dynamic certificates.
 func NewCertificateStore() *CertificateStore {
+	s := &safe.Safe{}
+	s.Set(make(map[string]*tls.Certificate))
+
 	return &CertificateStore{
-		DynamicCerts: &safe.Safe{},
+		DynamicCerts: s,
 		CertCache:    cache.New(1*time.Hour, 10*time.Minute),
 	}
 }
@@ -114,6 +117,45 @@ func (c *CertificateStore) GetBestCertificate(clientHello *tls.ClientHelloInfo)
 	return nil
 }
 
+// GetCertificate returns the first certificate matching all the given domains.
+func (c *CertificateStore) GetCertificate(domains []string) *tls.Certificate {
+	if c == nil {
+		return nil
+	}
+
+	sort.Strings(domains)
+	domainsKey := strings.Join(domains, ",")
+
+	if cert, ok := c.CertCache.Get(domainsKey); ok {
+		return cert.(*tls.Certificate)
+	}
+
+	if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil {
+		for certDomains, cert := range c.DynamicCerts.Get().(map[string]*tls.Certificate) {
+			if domainsKey == certDomains {
+				c.CertCache.SetDefault(domainsKey, cert)
+				return cert
+			}
+
+			var matchedDomains []string
+			for _, certDomain := range strings.Split(certDomains, ",") {
+				for _, checkDomain := range domains {
+					if certDomain == checkDomain {
+						matchedDomains = append(matchedDomains, certDomain)
+					}
+				}
+			}
+
+			if len(matchedDomains) == len(domains) {
+				c.CertCache.SetDefault(domainsKey, cert)
+				return cert
+			}
+		}
+	}
+
+	return nil
+}
+
 // ResetCache clears the cache in the store.
 func (c CertificateStore) ResetCache() {
 	if c.CertCache != nil {
diff --git a/pkg/tls/tls.go b/pkg/tls/tls.go
index 26c1b393f1..85bbeada15 100644
--- a/pkg/tls/tls.go
+++ b/pkg/tls/tls.go
@@ -1,5 +1,7 @@
 package tls
 
+import "github.com/traefik/traefik/v2/pkg/types"
+
 const certificateHeader = "-----BEGIN CERTIFICATE-----\n"
 
 // +k8s:deepcopy-gen=true
@@ -36,7 +38,18 @@ func (o *Options) SetDefaults() {
 
 // Store holds the options for a given Store.
 type Store struct {
-	DefaultCertificate *Certificate `json:"defaultCertificate,omitempty" toml:"defaultCertificate,omitempty" yaml:"defaultCertificate,omitempty" export:"true"`
+	DefaultCertificate   *Certificate   `json:"defaultCertificate,omitempty" toml:"defaultCertificate,omitempty" yaml:"defaultCertificate,omitempty" export:"true"`
+	DefaultGeneratedCert *GeneratedCert `json:"defaultGeneratedCert,omitempty" toml:"defaultGeneratedCert,omitempty" yaml:"defaultGeneratedCert,omitempty" export:"true"`
+}
+
+// +k8s:deepcopy-gen=true
+
+// GeneratedCert defines the default generated certificate configuration.
+type GeneratedCert struct {
+	// Resolver is the name of the resolver that will be used to issue the DefaultCertificate.
+	Resolver string `json:"resolver,omitempty" toml:"resolver,omitempty" yaml:"resolver,omitempty" export:"true"`
+	// Domain is the domain definition for the DefaultCertificate.
+	Domain *types.Domain `json:"domain,omitempty" toml:"domain,omitempty" yaml:"domain,omitempty" export:"true"`
 }
 
 // +k8s:deepcopy-gen=true
diff --git a/pkg/tls/tlsmanager.go b/pkg/tls/tlsmanager.go
index 73b98fc63b..0c7daa9923 100644
--- a/pkg/tls/tlsmanager.go
+++ b/pkg/tls/tlsmanager.go
@@ -6,8 +6,10 @@ import (
 	"crypto/x509"
 	"errors"
 	"fmt"
+	"strings"
 	"sync"
 
+	"github.com/go-acme/lego/v4/challenge/dns01"
 	"github.com/go-acme/lego/v4/challenge/tlsalpn01"
 	"github.com/sirupsen/logrus"
 	"github.com/traefik/traefik/v2/pkg/log"
@@ -81,17 +83,6 @@ func (m *Manager) UpdateConfigs(ctx context.Context, stores map[string]Store, co
 		m.storesConfig[tlsalpn01.ACMETLS1Protocol] = Store{}
 	}
 
-	m.stores = make(map[string]*CertificateStore)
-	for storeName, storeConfig := range m.storesConfig {
-		ctxStore := log.With(ctx, log.Str(log.TLSStoreName, storeName))
-		store, err := buildCertificateStore(ctxStore, storeConfig, storeName)
-		if err != nil {
-			log.FromContext(ctxStore).Errorf("Error while creating certificate store: %v", err)
-			continue
-		}
-		m.stores[storeName] = store
-	}
-
 	storesCertificates := make(map[string]map[string]*tls.Certificate)
 	for _, conf := range certs {
 		if len(conf.Stores) == 0 {
@@ -99,24 +90,66 @@ func (m *Manager) UpdateConfigs(ctx context.Context, stores map[string]Store, co
 				log.FromContext(ctx).Debugf("No store is defined to add the certificate %s, it will be added to the default store.",
 					conf.Certificate.GetTruncatedCertificateName())
 			}
-			conf.Stores = []string{"default"}
+			conf.Stores = []string{DefaultTLSStoreName}
 		}
+
 		for _, store := range conf.Stores {
 			ctxStore := log.With(ctx, log.Str(log.TLSStoreName, store))
-			if err := conf.Certificate.AppendCertificate(storesCertificates, store); err != nil {
+
+			if _, ok := m.storesConfig[store]; !ok {
+				m.storesConfig[store] = Store{}
+			}
+
+			err := conf.Certificate.AppendCertificate(storesCertificates, store)
+			if err != nil {
 				log.FromContext(ctxStore).Errorf("Unable to append certificate %s to store: %v", conf.Certificate.GetTruncatedCertificateName(), err)
 			}
 		}
 	}
 
-	for storeName, certs := range storesCertificates {
-		st, ok := m.stores[storeName]
-		if !ok {
-			st, _ = buildCertificateStore(context.Background(), Store{}, storeName)
-			m.stores[storeName] = st
+	m.stores = make(map[string]*CertificateStore)
+
+	for storeName, storeConfig := range m.storesConfig {
+		st := NewCertificateStore()
+		m.stores[storeName] = st
+
+		if certs, ok := storesCertificates[storeName]; ok {
+			st.DynamicCerts.Set(certs)
+		}
+
+		// a default cert for the ACME store does not make any sense, so generating one is a waste.
+		if storeName == tlsalpn01.ACMETLS1Protocol {
+			continue
+		}
+
+		ctxStore := log.With(ctx, log.Str(log.TLSStoreName, storeName))
+
+		certificate, err := getDefaultCertificate(ctxStore, storeConfig, st)
+		if err != nil {
+			log.FromContext(ctxStore).Errorf("Error while creating certificate store: %v", err)
 		}
-		st.DynamicCerts.Set(certs)
+
+		st.DefaultCertificate = certificate
+	}
+}
+
+// sanitizeDomains sanitizes the domain definition Main and SANS,
+// and returns them as a slice.
+// This func apply the same sanitization as the ACME provider do before resolving certificates.
+func sanitizeDomains(domain types.Domain) ([]string, error) {
+	domains := domain.ToStrArray()
+	if len(domains) == 0 {
+		return nil, errors.New("no domain was given")
+	}
+
+	var cleanDomains []string
+	for _, domain := range domains {
+		canonicalDomain := types.CanonicalDomain(domain)
+		cleanDomain := dns01.UnFqdn(canonicalDomain)
+		cleanDomains = append(cleanDomains, cleanDomain)
 	}
+
+	return cleanDomains, nil
 }
 
 // Get gets the TLS configuration to use for a given store / configuration.
@@ -234,32 +267,37 @@ func (m *Manager) GetStore(storeName string) *CertificateStore {
 	return m.getStore(storeName)
 }
 
-func buildCertificateStore(ctx context.Context, tlsStore Store, storename string) (*CertificateStore, error) {
-	certificateStore := NewCertificateStore()
-	certificateStore.DynamicCerts.Set(make(map[string]*tls.Certificate))
-
+func getDefaultCertificate(ctx context.Context, tlsStore Store, st *CertificateStore) (*tls.Certificate, error) {
 	if tlsStore.DefaultCertificate != nil {
 		cert, err := buildDefaultCertificate(tlsStore.DefaultCertificate)
 		if err != nil {
-			return certificateStore, err
+			return nil, err
 		}
-		certificateStore.DefaultCertificate = cert
-		return certificateStore, nil
-	}
 
-	// a default cert for the ACME store does not make any sense, so generating one
-	// is a waste.
-	if storename == tlsalpn01.ACMETLS1Protocol {
-		return certificateStore, nil
+		return cert, nil
 	}
 
-	log.FromContext(ctx).Debug("No default certificate, generating one")
-	cert, err := generate.DefaultCertificate()
+	defaultCert, err := generate.DefaultCertificate()
 	if err != nil {
-		return certificateStore, err
+		return nil, err
 	}
-	certificateStore.DefaultCertificate = cert
-	return certificateStore, nil
+
+	if tlsStore.DefaultGeneratedCert != nil && tlsStore.DefaultGeneratedCert.Domain != nil && tlsStore.DefaultGeneratedCert.Resolver != "" {
+		domains, err := sanitizeDomains(*tlsStore.DefaultGeneratedCert.Domain)
+		if err != nil {
+			return defaultCert, fmt.Errorf("falling back to the internal generated certificate because invalid domains: %w", err)
+		}
+
+		defaultACMECert := st.GetCertificate(domains)
+		if defaultACMECert == nil {
+			return defaultCert, fmt.Errorf("unable to find certificate for domains %q: falling back to the internal generated certificate", strings.Join(domains, ","))
+		}
+
+		return defaultACMECert, nil
+	}
+
+	log.FromContext(ctx).Debug("No default certificate, fallback to the internal generated certificate")
+	return defaultCert, nil
 }
 
 // creates a TLS config that allows terminating HTTPS for multiple domains using SNI.
diff --git a/pkg/tls/zz_generated.deepcopy.go b/pkg/tls/zz_generated.deepcopy.go
index fbdd8e5085..c85405a372 100644
--- a/pkg/tls/zz_generated.deepcopy.go
+++ b/pkg/tls/zz_generated.deepcopy.go
@@ -29,6 +29,10 @@ THE SOFTWARE.
 
 package tls
 
+import (
+	types "github.com/traefik/traefik/v2/pkg/types"
+)
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *CertAndStores) DeepCopyInto(out *CertAndStores) {
 	*out = *in
@@ -72,6 +76,27 @@ func (in *ClientAuth) DeepCopy() *ClientAuth {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GeneratedCert) DeepCopyInto(out *GeneratedCert) {
+	*out = *in
+	if in.Domain != nil {
+		in, out := &in.Domain, &out.Domain
+		*out = new(types.Domain)
+		(*in).DeepCopyInto(*out)
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeneratedCert.
+func (in *GeneratedCert) DeepCopy() *GeneratedCert {
+	if in == nil {
+		return nil
+	}
+	out := new(GeneratedCert)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Options) DeepCopyInto(out *Options) {
 	*out = *in
@@ -112,6 +137,11 @@ func (in *Store) DeepCopyInto(out *Store) {
 		*out = new(Certificate)
 		**out = **in
 	}
+	if in.DefaultGeneratedCert != nil {
+		in, out := &in.DefaultGeneratedCert, &out.DefaultGeneratedCert
+		*out = new(GeneratedCert)
+		(*in).DeepCopyInto(*out)
+	}
 	return
 }
 


Test patch:
diff --git a/integration/acme_test.go b/integration/acme_test.go
index 16b0b83982..73e9958bbf 100644
--- a/integration/acme_test.go
+++ b/integration/acme_test.go
@@ -40,6 +40,7 @@ type acmeTestCase struct {
 }
 
 type templateModel struct {
+	Domain    types.Domain
 	Domains   []types.Domain
 	PortHTTP  string
 	PortHTTPS string
@@ -149,6 +150,29 @@ func (s *AcmeSuite) TestHTTP01Domains(c *check.C) {
 	s.retrieveAcmeCertificate(c, testCase)
 }
 
+func (s *AcmeSuite) TestHTTP01StoreDomains(c *check.C) {
+	testCase := acmeTestCase{
+		traefikConfFilePath: "fixtures/acme/acme_store_domains.toml",
+		subCases: []subCases{{
+			host:               acmeDomain,
+			expectedCommonName: acmeDomain,
+			expectedAlgorithm:  x509.RSA,
+		}},
+		template: templateModel{
+			Domain: types.Domain{
+				Main: "traefik.acme.wtf",
+			},
+			Acme: map[string]static.CertificateResolver{
+				"default": {ACME: &acme.Configuration{
+					HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
+				}},
+			},
+		},
+	}
+
+	s.retrieveAcmeCertificate(c, testCase)
+}
+
 func (s *AcmeSuite) TestHTTP01DomainsInSAN(c *check.C) {
 	testCase := acmeTestCase{
 		traefikConfFilePath: "fixtures/acme/acme_domains.toml",
diff --git a/integration/https_test.go b/integration/https_test.go
index d30eebcc1b..868d31aff5 100644
--- a/integration/https_test.go
+++ b/integration/https_test.go
@@ -325,7 +325,7 @@ func (s *HTTPSSuite) TestWithDefaultCertificate(c *check.C) {
 
 	cs := conn.ConnectionState()
 	err = cs.PeerCertificates[0].VerifyHostname("snitest.com")
-	c.Assert(err, checker.IsNil, check.Commentf("certificate did not serve correct default certificate"))
+	c.Assert(err, checker.IsNil, check.Commentf("server did not serve correct default certificate"))
 
 	proto := cs.NegotiatedProtocol
 	c.Assert(proto, checker.Equals, "h2")
@@ -360,7 +360,7 @@ func (s *HTTPSSuite) TestWithDefaultCertificateNoSNI(c *check.C) {
 
 	cs := conn.ConnectionState()
 	err = cs.PeerCertificates[0].VerifyHostname("snitest.com")
-	c.Assert(err, checker.IsNil, check.Commentf("certificate did not serve correct default certificate"))
+	c.Assert(err, checker.IsNil, check.Commentf("server did not serve correct default certificate"))
 
 	proto := cs.NegotiatedProtocol
 	c.Assert(proto, checker.Equals, "h2")
@@ -397,7 +397,7 @@ func (s *HTTPSSuite) TestWithOverlappingStaticCertificate(c *check.C) {
 
 	cs := conn.ConnectionState()
 	err = cs.PeerCertificates[0].VerifyHostname("www.snitest.com")
-	c.Assert(err, checker.IsNil, check.Commentf("certificate did not serve correct default certificate"))
+	c.Assert(err, checker.IsNil, check.Commentf("server did not serve correct default certificate"))
 
 	proto := cs.NegotiatedProtocol
 	c.Assert(proto, checker.Equals, "h2")
@@ -434,7 +434,7 @@ func (s *HTTPSSuite) TestWithOverlappingDynamicCertificate(c *check.C) {
 
 	cs := conn.ConnectionState()
 	err = cs.PeerCertificates[0].VerifyHostname("www.snitest.com")
-	c.Assert(err, checker.IsNil, check.Commentf("certificate did not serve correct default certificate"))
+	c.Assert(err, checker.IsNil, check.Commentf("server did not serve correct default certificate"))
 
 	proto := cs.NegotiatedProtocol
 	c.Assert(proto, checker.Equals, "h2")
diff --git a/pkg/provider/acme/provider_test.go b/pkg/provider/acme/provider_test.go
index 799ab6b11d..3268b1c923 100644
--- a/pkg/provider/acme/provider_test.go
+++ b/pkg/provider/acme/provider_test.go
@@ -188,7 +188,7 @@ func TestGetUncheckedCertificates(t *testing.T) {
 	}
 }
 
-func TestGetValidDomain(t *testing.T) {
+func TestProvider_sanitizeDomains(t *testing.T) {
 	testCases := []struct {
 		desc            string
 		domains         types.Domain
@@ -214,7 +214,7 @@ func TestGetValidDomain(t *testing.T) {
 			desc:            "no domain",
 			domains:         types.Domain{},
 			dnsChallenge:    nil,
-			expectedErr:     "unable to generate a certificate in ACME provider when no domain is given",
+			expectedErr:     "no domain was given",
 			expectedDomains: nil,
 		},
 		{
@@ -254,7 +254,7 @@ func TestGetValidDomain(t *testing.T) {
 
 			acmeProvider := Provider{Configuration: &Configuration{DNSChallenge: test.dnsChallenge}}
 
-			domains, err := acmeProvider.getValidDomains(context.Background(), test.domains)
+			domains, err := acmeProvider.sanitizeDomains(context.Background(), test.domains)
 
 			if len(test.expectedErr) > 0 {
 				assert.EqualError(t, err, test.expectedErr, "Unexpected error.")