Repository: traefik/traefik (language: go)

Issue:
Full Consul Namespace support
### What did you expect to see?

Currently we support defining a single namespace under the [Consul](https://doc.traefik.io/traefik/providers/consul/#namespace) and [Consul Catalog](https://doc.traefik.io/traefik/providers/consul-catalog/) providers. 

The downside of supporting a single namespace on the static provider definition is that users deploying the app stacks on different namespaces won't be able to handle them on the same Traefik Proxy instance, having to rely on multiple separate instances for configuration discovery.

### Why can’t we watch an array of namespace?

Because names can be unique in each namespace, and this will require the user to configure this “uniqueness” in the label system

`service / traefik.http.routers.<router_name>.rule`

There are a few solutions to solve this, each comes with some drawbacks as usual:

## 1) Prefixing resource automatically 

One approach could be to automatically prefix the resource names by the service namespace. Therefore, the dynamic configuration parsed from the labels needs to be transformed and cannot be used as is after the parsing phase.

Downside:
- Requires handling specific cases for service and middleware references
- Not easy to maintain in the long run

## 2) Get rid of the router/service name configuration in the tags

Downside:
- Would affect all providers, not only Consul and Consul Catalog
- Breaking change, can't be made before the next major release

## 3) Support multiple providers of the same type

Downside:
- Not as easy as it looks to implement
- Declaring multiple providers of the same type just for watching more namespaces could feel clunky

These are just a few options quickly discussed with the engineering team. Please note all of them requires further studying and a better detailed plan. Feedback and suggestions are welcomed as usual.


Discussion / hints:
After internal discussions, option 3 seems to be more suitable for the moment as it solves the problem while also addressing other requests like #3200 and #8925.

Fix patch:
diff --git a/.golangci.toml b/.golangci.toml
index 43f02ce28c..93777cec0f 100644
--- a/.golangci.toml
+++ b/.golangci.toml
@@ -174,6 +174,8 @@
     "SA1019: cfg.SSLHost is deprecated",
     "SA1019: cfg.SSLForceHost is deprecated",
     "SA1019: cfg.FeaturePolicy is deprecated",
+    "SA1019: c.Providers.ConsulCatalog.Namespace is deprecated",
+    "SA1019: c.Providers.Consul.Namespace is deprecated",
   ]
  [[issues.exclude-rules]]
     path = "(.+)_test.go"
diff --git a/docs/content/deprecation/features.md b/docs/content/deprecation/features.md
index 6c277c8411..fb400c166b 100644
--- a/docs/content/deprecation/features.md
+++ b/docs/content/deprecation/features.md
@@ -2,10 +2,11 @@
 
 This page is maintained and updated periodically to reflect our roadmap and any decisions around feature deprecation.
 
-| Feature                                               | Deprecated | End of Support | Removal |
-|-------------------------------------------------------|------------|----------------|---------|
-| [Pilot Dashboard (Metrics)](#pilot-dashboard-metrics) | 2.7        | 2.8            | 2.9     |
-| [Pilot Plugins](#pilot-plugins)                       | 2.7        | 2.8            | 2.9     |
+| Feature                                                       | Deprecated | End of Support | Removal |
+|---------------------------------------------------------------|------------|----------------|---------|
+| [Pilot Dashboard (Metrics)](#pilot-dashboard-metrics)         | 2.7        | 2.8            | 2.9     |
+| [Pilot Plugins](#pilot-plugins)                               | 2.7        | 2.8            | 2.9     |
+| [Consul Enterprise Namespaces](#consul-enterprise-namespaces) | 2.8        | TBD            | TBD     |
 
 ## Impact
 
@@ -18,3 +19,8 @@ In 2.9, the Pilot platform and all Traefik integration code will be permanently
 
 Starting on 2.7 the pilot token will not be a requirement anymore.  
 At 2.9, a new plugin catalog home should be available, decoupled from pilot.
+
+### Consul Enterprise Namespaces
+
+Starting on 2.8 the `namespace` option of Consul and Consul Catalog providers is deprecated, 
+please use the `namespaces` options instead.  
diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md
index c32f289873..8228281451 100644
--- a/docs/content/migration/v2.md
+++ b/docs/content/migration/v2.md
@@ -460,7 +460,11 @@ In `v2.6.1`, the Datadog tags added to a span changed from `service.name` to `tr
 
 ## v2.8
 
-### TLS client authentication 
+### TLS client authentication
 
 In `v2.8`, the `caOptional` option is deprecated as TLS client authentication is a server side option.
 This option available in the ForwardAuth middleware, as well as in the HTTP, Consul, Etcd, Redis, ZooKeeper, Marathon, Consul Catalog, and Docker providers has no effect and must not be used anymore.
+
+### Consul Enterprise Namespaces
+
+In `v2.8`, the `namespace` option of Consul and Consul Catalog providers is deprecated, please use the `namespaces` options instead.
diff --git a/docs/content/providers/consul-catalog.md b/docs/content/providers/consul-catalog.md
index f9db6f119a..d177df93cf 100644
--- a/docs/content/providers/consul-catalog.md
+++ b/docs/content/providers/consul-catalog.md
@@ -525,7 +525,7 @@ providers:
 ```
 
 ```bash tab="CLI"
---providers.consulcatalog.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
+--providers.consulcatalog.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
 # ...
 ```
 
@@ -669,30 +669,76 @@ For additional information, refer to [Restrict the Scope of Service Discovery](.
 
 ### `namespace`
 
+??? warning "Deprecated in favor of the [`namespaces`](#namespaces) option."
+
+    _Optional, Default=""_
+    
+    The `namespace` option defines the namespace in which the consul catalog services will be discovered.
+    
+    !!! warning
+    
+        The namespace option only works with [Consul Enterprise](https://www.consul.io/docs/enterprise),
+        which provides the [Namespaces](https://www.consul.io/docs/enterprise/namespaces) feature.
+    
+    !!! warning
+    
+        One should only define either the `namespaces` option or the `namespace` option.
+    
+    ```yaml tab="File (YAML)"
+    providers:
+      consulCatalog:
+        namespace: "production" 
+        # ...
+    ```
+    
+    ```toml tab="File (TOML)"
+    [providers.consulCatalog]
+      namespace = "production"
+      # ...
+    ```
+    
+    ```bash tab="CLI"
+    --providers.consulcatalog.namespace=production
+    # ...
+    ```
+
+### `namespaces`
+
 _Optional, Default=""_
 
-The `namespace` option defines the namespace in which the consul catalog services will be discovered.
+The `namespaces` option defines the namespaces in which the consul catalog services will be discovered.
+When using the `namespaces` option, the discovered configuration object names will be suffixed as shown below:
+
+```text
+<resource-name>@consulcatalog-<namespace>
+```
 
 !!! warning
 
-    The namespace option only works with [Consul Enterprise](https://www.consul.io/docs/enterprise),
+    The namespaces option only works with [Consul Enterprise](https://www.consul.io/docs/enterprise),
     which provides the [Namespaces](https://www.consul.io/docs/enterprise/namespaces) feature.
 
+!!! warning
+
+    One should only define either the `namespaces` option or the `namespace` option.
+
 ```yaml tab="File (YAML)"
 providers:
   consulCatalog:
-    namespace: "production" 
+    namespaces: 
+      - "ns1"
+      - "ns2"
     # ...
 ```
 
 ```toml tab="File (TOML)"
 [providers.consulCatalog]
-  namespace = "production"
+  namespaces = ["ns1", "ns2"]
   # ...
 ```
 
 ```bash tab="CLI"
---providers.consulcatalog.namespace=production
+--providers.consulcatalog.namespaces=ns1,ns2
 # ...
 ```
 
diff --git a/docs/content/providers/consul.md b/docs/content/providers/consul.md
index 25e84abbe5..cd82fd0655 100644
--- a/docs/content/providers/consul.md
+++ b/docs/content/providers/consul.md
@@ -61,30 +61,76 @@ providers:
 
 ### `namespace`
 
+??? warning "Deprecated in favor of the [`namespaces`](#namespaces) option."
+
+    _Optional, Default=""_
+    
+    The `namespace` option defines the namespace to query.
+    
+    !!! warning
+    
+        The namespace option only works with [Consul Enterprise](https://www.consul.io/docs/enterprise),
+        which provides the [Namespaces](https://www.consul.io/docs/enterprise/namespaces) feature.
+    
+    !!! warning
+    
+        One should only define either the `namespaces` option or the `namespace` option.
+    
+    ```yaml tab="File (YAML)"
+    providers:
+      consul:
+        # ...
+        namespace: "production"
+    ```
+    
+    ```toml tab="File (TOML)"
+    [providers.consul]
+      # ...
+      namespace = "production"
+    ```
+    
+    ```bash tab="CLI"
+    --providers.consul.namespace=production
+    ```
+
+### `namespaces`
+
 _Optional, Default=""_
 
-The `namespace` option defines the namespace to query.
+The `namespaces` option defines the namespaces to query.
+When using the `namespaces` option, the discovered configuration object names will be suffixed as shown below:
+
+```text
+<resource-name>@consul-<namespace>
+```
 
 !!! warning
 
-    The namespace option only works with [Consul Enterprise](https://www.consul.io/docs/enterprise),
+    The namespaces option only works with [Consul Enterprise](https://www.consul.io/docs/enterprise),
     which provides the [Namespaces](https://www.consul.io/docs/enterprise/namespaces) feature.
 
+!!! warning
+
+    One should only define either the `namespaces` option or the `namespace` option.
+
 ```yaml tab="File (YAML)"
 providers:
   consul:
+    namespaces: 
+      - "ns1"
+      - "ns2"
     # ...
-    namespace: "production"
 ```
 
 ```toml tab="File (TOML)"
 [providers.consul]
+  namespaces = ["ns1", "ns2"]
   # ...
-  namespace = "production"
 ```
 
 ```bash tab="CLI"
---providers.consul.namespace=production
+--providers.consul.namespaces=ns1,ns2
+# ...
 ```
 
 ### `username`
diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md
index 9dcc60921b..1f7fba4750 100644
--- a/docs/content/reference/static-configuration/cli-ref.md
+++ b/docs/content/reference/static-configuration/cli-ref.md
@@ -403,7 +403,10 @@ Enable Consul backend with default settings. (Default: ```false```)
 KV store endpoints (Default: ```127.0.0.1:8500```)
 
 `--providers.consul.namespace`:  
-KV Namespace
+Sets the namespace used to discover the configuration (Consul Enterprise only).
+
+`--providers.consul.namespaces`:  
+Sets the namespaces used to discover the configuration (Consul Enterprise only).
 
 `--providers.consul.password`:  
 KV Password
@@ -492,11 +495,14 @@ Expose containers by default. (Default: ```true```)
 `--providers.consulcatalog.namespace`:  
 Sets the namespace used to discover services (Consul Enterprise only).
 
+`--providers.consulcatalog.namespaces`:  
+Sets the namespaces used to discover services (Consul Enterprise only).
+
 `--providers.consulcatalog.prefix`:  
-Prefix for consul service tags. Default 'traefik' (Default: ```traefik```)
+Prefix for consul service tags. (Default: ```traefik```)
 
 `--providers.consulcatalog.refreshinterval`:  
-Interval for check Consul API. Default 15s (Default: ```15```)
+Interval for check Consul API. (Default: ```15```)
 
 `--providers.consulcatalog.requireconsistent`:  
 Forces the read to be fully consistent. (Default: ```false```)
@@ -594,9 +600,6 @@ Enable Etcd backend with default settings. (Default: ```false```)
 `--providers.etcd.endpoints`:  
 KV store endpoints (Default: ```127.0.0.1:2379```)
 
-`--providers.etcd.namespace`:  
-KV Namespace
-
 `--providers.etcd.password`:  
 KV Password
 
@@ -858,9 +861,6 @@ Enable Redis backend with default settings. (Default: ```false```)
 `--providers.redis.endpoints`:  
 KV store endpoints (Default: ```127.0.0.1:6379```)
 
-`--providers.redis.namespace`:  
-KV Namespace
-
 `--providers.redis.password`:  
 KV Password
 
@@ -900,9 +900,6 @@ Enable ZooKeeper backend with default settings. (Default: ```false```)
 `--providers.zookeeper.endpoints`:  
 KV store endpoints (Default: ```127.0.0.1:2181```)
 
-`--providers.zookeeper.namespace`:  
-KV Namespace
-
 `--providers.zookeeper.password`:  
 KV Password
 
diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md
index 44a976c37f..861ac7c87f 100644
--- a/docs/content/reference/static-configuration/env-ref.md
+++ b/docs/content/reference/static-configuration/env-ref.md
@@ -459,11 +459,14 @@ Expose containers by default. (Default: ```true```)
 `TRAEFIK_PROVIDERS_CONSULCATALOG_NAMESPACE`:  
 Sets the namespace used to discover services (Consul Enterprise only).
 
+`TRAEFIK_PROVIDERS_CONSULCATALOG_NAMESPACES`:  
+Sets the namespaces used to discover services (Consul Enterprise only).
+
 `TRAEFIK_PROVIDERS_CONSULCATALOG_PREFIX`:  
-Prefix for consul service tags. Default 'traefik' (Default: ```traefik```)
+Prefix for consul service tags. (Default: ```traefik```)
 
 `TRAEFIK_PROVIDERS_CONSULCATALOG_REFRESHINTERVAL`:  
-Interval for check Consul API. Default 15s (Default: ```15```)
+Interval for check Consul API. (Default: ```15```)
 
 `TRAEFIK_PROVIDERS_CONSULCATALOG_REQUIRECONSISTENT`:  
 Forces the read to be fully consistent. (Default: ```false```)
@@ -481,7 +484,10 @@ Watch Consul API events. (Default: ```false```)
 KV store endpoints (Default: ```127.0.0.1:8500```)
 
 `TRAEFIK_PROVIDERS_CONSUL_NAMESPACE`:  
-KV Namespace
+Sets the namespace used to discover the configuration (Consul Enterprise only).
+
+`TRAEFIK_PROVIDERS_CONSUL_NAMESPACES`:  
+Sets the namespaces used to discover the configuration (Consul Enterprise only).
 
 `TRAEFIK_PROVIDERS_CONSUL_PASSWORD`:  
 KV Password
@@ -594,9 +600,6 @@ Enable Etcd backend with default settings. (Default: ```false```)
 `TRAEFIK_PROVIDERS_ETCD_ENDPOINTS`:  
 KV store endpoints (Default: ```127.0.0.1:2379```)
 
-`TRAEFIK_PROVIDERS_ETCD_NAMESPACE`:  
-KV Namespace
-
 `TRAEFIK_PROVIDERS_ETCD_PASSWORD`:  
 KV Password
 
@@ -858,9 +861,6 @@ Enable Redis backend with default settings. (Default: ```false```)
 `TRAEFIK_PROVIDERS_REDIS_ENDPOINTS`:  
 KV store endpoints (Default: ```127.0.0.1:6379```)
 
-`TRAEFIK_PROVIDERS_REDIS_NAMESPACE`:  
-KV Namespace
-
 `TRAEFIK_PROVIDERS_REDIS_PASSWORD`:  
 KV Password
 
@@ -900,9 +900,6 @@ Enable ZooKeeper backend with default settings. (Default: ```false```)
 `TRAEFIK_PROVIDERS_ZOOKEEPER_ENDPOINTS`:  
 KV store endpoints (Default: ```127.0.0.1:2181```)
 
-`TRAEFIK_PROVIDERS_ZOOKEEPER_NAMESPACE`:  
-KV Namespace
-
 `TRAEFIK_PROVIDERS_ZOOKEEPER_PASSWORD`:  
 KV Password
 
diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml
index aad84318dc..7f30c61f82 100644
--- a/docs/content/reference/static-configuration/file.toml
+++ b/docs/content/reference/static-configuration/file.toml
@@ -28,12 +28,6 @@
     [entryPoints.EntryPoint0.forwardedHeaders]
       insecure = true
       trustedIPs = ["foobar", "foobar"]
-    [entryPoints.EntryPoint0.udp]
-      timeout = 42
-    [entryPoints.EntryPoint0.http2]
-      maxConcurrentStreams = 42
-    [entryPoints.EntryPoint0.http3]
-      advertisedPort = 42
     [entryPoints.EntryPoint0.http]
       middlewares = ["foobar", "foobar"]
       [entryPoints.EntryPoint0.http.redirections]
@@ -53,6 +47,8 @@
         [[entryPoints.EntryPoint0.http.tls.domains]]
           main = "foobar"
           sans = ["foobar", "foobar"]
+    [entryPoints.EntryPoint0.http2]
+      maxConcurrentStreams = 42
     [entryPoints.EntryPoint0.http3]
       advertisedPort = 42
     [entryPoints.EntryPoint0.udp]
@@ -161,6 +157,7 @@
     connectByDefault = true
     serviceName = "foobar"
     namespace = "foobar"
+    namespaces = ["foobar", "foobar"]
     watch = true
     [providers.consulCatalog.endpoint]
       address = "foobar"
@@ -194,6 +191,7 @@
     password = "foobar"
     token = "foobar"
     namespace = "foobar"
+    namespaces = ["foobar", "foobar"]
     [providers.consul.tls]
       ca = "foobar"
       caOptional = true
@@ -206,7 +204,6 @@
     username = "foobar"
     password = "foobar"
     token = "foobar"
-    namespace = "foobar"
     [providers.etcd.tls]
       ca = "foobar"
       caOptional = true
@@ -219,7 +216,6 @@
     username = "foobar"
     password = "foobar"
     token = "foobar"
-    namespace = "foobar"
     [providers.zooKeeper.tls]
       ca = "foobar"
       caOptional = true
@@ -232,7 +228,6 @@
     username = "foobar"
     password = "foobar"
     token = "foobar"
-    namespace = "foobar"
     [providers.redis.tls]
       ca = "foobar"
       caOptional = true
diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml
index 3887876e35..656909d9b4 100644
--- a/docs/content/reference/static-configuration/file.yaml
+++ b/docs/content/reference/static-configuration/file.yaml
@@ -32,12 +32,6 @@ entryPoints:
       trustedIPs:
       - foobar
       - foobar
-    http2:
-      maxConcurrentStreams: 42
-    http3:
-      advertisedPort: 42
-    udp:
-      timeout: 42
     http:
       redirections:
         entryPoint:
@@ -60,6 +54,8 @@ entryPoints:
             sans:
               - foobar
               - foobar
+    http2:
+      maxConcurrentStreams: 42
     http3:
       advertisedPort: 42
     udp:
@@ -173,6 +169,9 @@ providers:
     connectByDefault: true
     serviceName: foobar
     namespace: foobar
+    namespaces:
+      - foobar
+      - foobar
     watch: true
     endpoint:
       address: foobar
@@ -210,6 +209,9 @@ providers:
     password: foobar
     token: foobar
     namespace: foobar
+    namespaces:
+      - foobar
+      - foobar
     tls:
       ca: foobar
       caOptional: true
@@ -224,7 +226,6 @@ providers:
     username: foobar
     password: foobar
     token: foobar
-    namespace: foobar
     tls:
       ca: foobar
       caOptional: true
@@ -239,7 +240,6 @@ providers:
     username: foobar
     password: foobar
     token: foobar
-    namespace: foobar
     tls:
       ca: foobar
       caOptional: true
@@ -254,7 +254,6 @@ providers:
     username: foobar
     password: foobar
     token: foobar
-    namespace: foobar
     tls:
       ca: foobar
       caOptional: true
diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go
index b285996f88..095ce08ff8 100644
--- a/pkg/config/static/static_config.go
+++ b/pkg/config/static/static_config.go
@@ -175,22 +175,22 @@ func (t *Tracing) SetDefaults() {
 type Providers struct {
 	ProvidersThrottleDuration ptypes.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." json:"providersThrottleDuration,omitempty" toml:"providersThrottleDuration,omitempty" yaml:"providersThrottleDuration,omitempty" export:"true"`
 
-	Docker            *docker.Provider        `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
-	File              *file.Provider          `description:"Enable File backend with default settings." json:"file,omitempty" toml:"file,omitempty" yaml:"file,omitempty" export:"true"`
-	Marathon          *marathon.Provider      `description:"Enable Marathon backend with default settings." json:"marathon,omitempty" toml:"marathon,omitempty" yaml:"marathon,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
-	KubernetesIngress *ingress.Provider       `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
-	KubernetesCRD     *crd.Provider           `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
-	KubernetesGateway *gateway.Provider       `description:"Enable Kubernetes gateway api provider with default settings." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
-	Rest              *rest.Provider          `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
-	Rancher           *rancher.Provider       `description:"Enable Rancher backend with default settings." json:"rancher,omitempty" toml:"rancher,omitempty" yaml:"rancher,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
-	ConsulCatalog     *consulcatalog.Provider `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
-	Ecs               *ecs.Provider           `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
-
-	Consul    *consul.Provider `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty"  export:"true"`
-	Etcd      *etcd.Provider   `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
-	ZooKeeper *zk.Provider     `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
-	Redis     *redis.Provider  `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
-	HTTP      *http.Provider   `description:"Enable HTTP backend with default settings." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
+	Docker            *docker.Provider               `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
+	File              *file.Provider                 `description:"Enable File backend with default settings." json:"file,omitempty" toml:"file,omitempty" yaml:"file,omitempty" export:"true"`
+	Marathon          *marathon.Provider             `description:"Enable Marathon backend with default settings." json:"marathon,omitempty" toml:"marathon,omitempty" yaml:"marathon,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
+	KubernetesIngress *ingress.Provider              `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
+	KubernetesCRD     *crd.Provider                  `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
+	KubernetesGateway *gateway.Provider              `description:"Enable Kubernetes gateway api provider with default settings." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
+	Rest              *rest.Provider                 `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
+	Rancher           *rancher.Provider              `description:"Enable Rancher backend with default settings." json:"rancher,omitempty" toml:"rancher,omitempty" yaml:"rancher,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
+	ConsulCatalog     *consulcatalog.ProviderBuilder `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
+	Ecs               *ecs.Provider                  `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
+
+	Consul    *consul.ProviderBuilder `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty"  export:"true"`
+	Etcd      *etcd.Provider          `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
+	ZooKeeper *zk.Provider            `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
+	Redis     *redis.Provider         `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
+	HTTP      *http.Provider          `description:"Enable HTTP backend with default settings." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
 
 	Plugin map[string]PluginConf `description:"Plugins configuration." json:"plugin,omitempty" toml:"plugin,omitempty" yaml:"plugin,omitempty"`
 }
@@ -257,7 +257,7 @@ func (c *Configuration) SetEffectiveConfiguration() {
 		c.Pilot.SetDefaults()
 	}
 
-	// Disable Gateway API provider if not enabled in experimental
+	// Disable Gateway API provider if not enabled in experimental.
 	if c.Experimental == nil || !c.Experimental.KubernetesGateway {
 		c.Providers.KubernetesGateway = nil
 	}
@@ -328,6 +328,14 @@ func (c *Configuration) ValidateConfiguration() error {
 		acmeEmail = resolver.ACME.Email
 	}
 
+	if c.Providers.ConsulCatalog != nil && c.Providers.ConsulCatalog.Namespace != "" && len(c.Providers.ConsulCatalog.Namespaces) > 0 {
+		return fmt.Errorf("consul catalog provider cannot have both namespace and namespaces options configured")
+	}
+
+	if c.Providers.Consul != nil && c.Providers.Consul.Namespace != "" && len(c.Providers.Consul.Namespaces) > 0 {
+		return fmt.Errorf("consul provider cannot have both namespace and namespaces options configured")
+	}
+
 	return nil
 }
 
diff --git a/pkg/provider/aggregator/aggregator.go b/pkg/provider/aggregator/aggregator.go
index 21109a1841..16a32003f1 100644
--- a/pkg/provider/aggregator/aggregator.go
+++ b/pkg/provider/aggregator/aggregator.go
@@ -109,11 +109,15 @@ func NewProviderAggregator(conf static.Providers) ProviderAggregator {
 	}
 
 	if conf.ConsulCatalog != nil {
-		p.quietAddProvider(conf.ConsulCatalog)
+		for _, pvd := range conf.ConsulCatalog.BuildProviders() {
+			p.quietAddProvider(pvd)
+		}
 	}
 
 	if conf.Consul != nil {
-		p.quietAddProvider(conf.Consul)
+		for _, pvd := range conf.Consul.BuildProviders() {
+			p.quietAddProvider(pvd)
+		}
 	}
 
 	if conf.Etcd != nil {
diff --git a/pkg/provider/consulcatalog/consul_catalog.go b/pkg/provider/consulcatalog/consul_catalog.go
index a51127d5a3..1bd0dab766 100644
--- a/pkg/provider/consulcatalog/consul_catalog.go
+++ b/pkg/provider/consulcatalog/consul_catalog.go
@@ -22,8 +22,11 @@ import (
 	"github.com/traefik/traefik/v2/pkg/types"
 )
 
-// DefaultTemplateRule The default template for the default rule.
-const DefaultTemplateRule = "Host(`{{ normalize .Name }}`)"
+// defaultTemplateRule is the default template for the default rule.
+const defaultTemplateRule = "Host(`{{ normalize .Name }}`)"
+
+// providerName is the Consul Catalog provider name.
+const providerName = "consulcatalog"
 
 var _ provider.Provider = (*Provider)(nil)
 
@@ -41,12 +44,50 @@ type itemData struct {
 	ExtraConf  configuration
 }
 
-// Provider holds configurations of the provider.
-type Provider struct {
+// ProviderBuilder is responsible for constructing namespaced instances of the Consul Catalog provider.
+type ProviderBuilder struct {
+	Configuration `export:"true"`
+
+	// Deprecated: use Namespaces option instead.
+	Namespace  string   `description:"Sets the namespace used to discover services (Consul Enterprise only)." json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"`
+	Namespaces []string `description:"Sets the namespaces used to discover services (Consul Enterprise only)." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty"`
+}
+
+// BuildProviders builds Consul Catalog provider instances for the given namespaces configuration.
+func (p *ProviderBuilder) BuildProviders() []*Provider {
+	// We can warn about that, because we've already made sure before that
+	// Namespace and Namespaces are mutually exclusive.
+	if p.Namespace != "" {
+		log.WithoutContext().Warnf("Namespace option is deprecated, please use the Namespaces option instead.")
+	}
+
+	if len(p.Namespaces) == 0 {
+		return []*Provider{{
+			Configuration: p.Configuration,
+			name:          providerName,
+			// p.Namespace could very well be empty.
+			namespace: p.Namespace,
+		}}
+	}
+
+	var providers []*Provider
+	for _, namespace := range p.Namespaces {
+		providers = append(providers, &Provider{
+			Configuration: p.Configuration,
+			name:          providerName + "-" + namespace,
+			namespace:     namespace,
+		})
+	}
+
+	return providers
+}
+
+// Configuration represents the Consul Catalog provider configuration.
+type Configuration struct {
 	Constraints       string          `description:"Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true"`
 	Endpoint          *EndpointConfig `description:"Consul endpoint settings" json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" export:"true"`
-	Prefix            string          `description:"Prefix for consul service tags. Default 'traefik'" json:"prefix,omitempty" toml:"prefix,omitempty" yaml:"prefix,omitempty" export:"true"`
-	RefreshInterval   ptypes.Duration `description:"Interval for check Consul API. Default 15s" json:"refreshInterval,omitempty" toml:"refreshInterval,omitempty" yaml:"refreshInterval,omitempty" export:"true"`
+	Prefix            string          `description:"Prefix for consul service tags." json:"prefix,omitempty" toml:"prefix,omitempty" yaml:"prefix,omitempty" export:"true"`
+	RefreshInterval   ptypes.Duration `description:"Interval for check Consul API." json:"refreshInterval,omitempty" toml:"refreshInterval,omitempty" yaml:"refreshInterval,omitempty" export:"true"`
 	RequireConsistent bool            `description:"Forces the read to be fully consistent." json:"requireConsistent,omitempty" toml:"requireConsistent,omitempty" yaml:"requireConsistent,omitempty" export:"true"`
 	Stale             bool            `description:"Use stale consistency for catalog reads." json:"stale,omitempty" toml:"stale,omitempty" yaml:"stale,omitempty" export:"true"`
 	Cache             bool            `description:"Use local agent caching for catalog reads." json:"cache,omitempty" toml:"cache,omitempty" yaml:"cache,omitempty" export:"true"`
@@ -55,9 +96,25 @@ type Provider struct {
 	ConnectAware      bool            `description:"Enable Consul Connect support." json:"connectAware,omitempty" toml:"connectAware,omitempty" yaml:"connectAware,omitempty" export:"true"`
 	ConnectByDefault  bool            `description:"Consider every service as Connect capable by default." json:"connectByDefault,omitempty" toml:"connectByDefault,omitempty" yaml:"connectByDefault,omitempty" export:"true"`
 	ServiceName       string          `description:"Name of the Traefik service in Consul Catalog (needs to be registered via the orchestrator or manually)." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"`
-	Namespace         string          `description:"Sets the namespace used to discover services (Consul Enterprise only)." json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty" export:"true"`
 	Watch             bool            `description:"Watch Consul API events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"`
+}
+
+// SetDefaults sets the default values.
+func (c *Configuration) SetDefaults() {
+	c.Endpoint = &EndpointConfig{}
+	c.RefreshInterval = ptypes.Duration(15 * time.Second)
+	c.Prefix = "traefik"
+	c.ExposedByDefault = true
+	c.DefaultRule = defaultTemplateRule
+	c.ServiceName = "traefik"
+}
+
+// Provider is the Consul Catalog provider implementation.
+type Provider struct {
+	Configuration
 
+	name              string
+	namespace         string
 	client            *api.Client
 	defaultRuleTpl    *template.Template
 	certChan          chan *connectCert
@@ -81,17 +138,6 @@ type EndpointHTTPAuthConfig struct {
 	Password string `description:"Basic Auth password" json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" loggable:"false"`
 }
 
-// SetDefaults sets the default values.
-func (p *Provider) SetDefaults() {
-	endpoint := &EndpointConfig{}
-	p.Endpoint = endpoint
-	p.RefreshInterval = ptypes.Duration(15 * time.Second)
-	p.Prefix = "traefik"
-	p.ExposedByDefault = true
-	p.DefaultRule = DefaultTemplateRule
-	p.ServiceName = "traefik"
-}
-
 // Init the provider.
 func (p *Provider) Init() error {
 	defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, nil)
@@ -103,19 +149,24 @@ func (p *Provider) Init() error {
 	p.certChan = make(chan *connectCert, 1)
 	p.watchServicesChan = make(chan struct{}, 1)
 
+	// In case they didn't initialize Provider with BuildProviders.
+	if p.name == "" {
+		p.name = providerName
+	}
+
 	return nil
 }
 
 // Provide allows the consul catalog provider to provide configurations to traefik using the given configuration channel.
 func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
 	var err error
-	p.client, err = createClient(p.Namespace, p.Endpoint)
+	p.client, err = createClient(p.namespace, p.Endpoint)
 	if err != nil {
 		return fmt.Errorf("failed to create consul client: %w", err)
 	}
 
 	pool.GoCtx(func(routineCtx context.Context) {
-		ctxLog := log.With(routineCtx, log.Str(log.ProviderName, "consulcatalog"))
+		ctxLog := log.With(routineCtx, log.Str(log.ProviderName, p.name))
 		logger := log.FromContext(ctxLog)
 
 		operation := func() error {
@@ -210,7 +261,7 @@ func (p *Provider) loadConfiguration(ctx context.Context, certInfo *connectCert,
 	}
 
 	configurationChan <- dynamic.Message{
-		ProviderName:  "consulcatalog",
+		ProviderName:  p.name,
 		Configuration: p.buildConfiguration(ctx, data, certInfo),
 	}
 
diff --git a/pkg/provider/kv/consul/consul.go b/pkg/provider/kv/consul/consul.go
index 6f3860955a..492652c368 100644
--- a/pkg/provider/kv/consul/consul.go
+++ b/pkg/provider/kv/consul/consul.go
@@ -4,30 +4,80 @@ import (
 	"errors"
 
 	"github.com/kvtools/valkeyrie/store"
+	"github.com/traefik/traefik/v2/pkg/log"
 	"github.com/traefik/traefik/v2/pkg/provider"
 	"github.com/traefik/traefik/v2/pkg/provider/kv"
 )
 
+// providerName is the Consul provider name.
+const providerName = "consul"
+
 var _ provider.Provider = (*Provider)(nil)
 
-// Provider holds configurations of the provider.
-type Provider struct {
+// ProviderBuilder is responsible for constructing namespaced instances of the Consul provider.
+type ProviderBuilder struct {
 	kv.Provider `export:"true"`
+
+	// Deprecated: use Namespaces instead.
+	Namespace  string   `description:"Sets the namespace used to discover the configuration (Consul Enterprise only)." json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"`
+	Namespaces []string `description:"Sets the namespaces used to discover the configuration (Consul Enterprise only)." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty"`
 }
 
 // SetDefaults sets the default values.
-func (p *Provider) SetDefaults() {
+func (p *ProviderBuilder) SetDefaults() {
 	p.Provider.SetDefaults()
 	p.Endpoints = []string{"127.0.0.1:8500"}
 }
 
+// BuildProviders builds Consul provider instances for the given namespaces configuration.
+func (p *ProviderBuilder) BuildProviders() []*Provider {
+	// We can warn about that, because we've already made sure before that
+	// Namespace and Namespaces are mutually exclusive.
+	if p.Namespace != "" {
+		log.WithoutContext().Warnf("Namespace option is deprecated, please use the Namespaces option instead.")
+	}
+
+	if len(p.Namespaces) == 0 {
+		return []*Provider{{
+			Provider: p.Provider,
+			name:     providerName,
+			// p.Namespace could very well be empty.
+			namespace: p.Namespace,
+		}}
+	}
+
+	var providers []*Provider
+	for _, namespace := range p.Namespaces {
+		providers = append(providers, &Provider{
+			Provider:  p.Provider,
+			name:      providerName + "-" + namespace,
+			namespace: namespace,
+		})
+	}
+
+	return providers
+}
+
+// Provider holds configurations of the provider.
+type Provider struct {
+	kv.Provider
+
+	name      string
+	namespace string
+}
+
 // Init the provider.
 func (p *Provider) Init() error {
 	// Wildcard namespace allows fetching KV values from any namespace for recursive requests (see https://www.consul.io/api/kv#ns).
 	// As we are not supporting multiple namespaces at the same time, wildcard namespace is not allowed.
-	if p.Namespace == "*" {
+	if p.namespace == "*" {
 		return errors.New("wildcard namespace is not supported")
 	}
 
-	return p.Provider.Init(store.CONSUL, "consul")
+	// In case they didn't initialize with BuildProviders.
+	if p.name == "" {
+		p.name = providerName
+	}
+
+	return p.Provider.Init(store.CONSUL, p.name, p.namespace)
 }
diff --git a/pkg/provider/kv/etcd/etcd.go b/pkg/provider/kv/etcd/etcd.go
index a06f1b1a15..a4b83bfbfa 100644
--- a/pkg/provider/kv/etcd/etcd.go
+++ b/pkg/provider/kv/etcd/etcd.go
@@ -21,5 +21,5 @@ func (p *Provider) SetDefaults() {
 
 // Init the provider.
 func (p *Provider) Init() error {
-	return p.Provider.Init(store.ETCDV3, "etcd")
+	return p.Provider.Init(store.ETCDV3, "etcd", "")
 }
diff --git a/pkg/provider/kv/kv.go b/pkg/provider/kv/kv.go
index 50279e6bb1..173c49698a 100644
--- a/pkg/provider/kv/kv.go
+++ b/pkg/provider/kv/kv.go
@@ -30,12 +30,12 @@ type Provider struct {
 	Username  string           `description:"KV Username" json:"username,omitempty" toml:"username,omitempty" yaml:"username,omitempty" loggable:"false"`
 	Password  string           `description:"KV Password" json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" loggable:"false"`
 	Token     string           `description:"KV Token" json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"`
-	Namespace string           `description:"KV Namespace" json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"`
 	TLS       *types.ClientTLS `description:"Enable TLS support" json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true" `
 
+	name      string
+	namespace string
 	storeType store.Backend
 	kvClient  store.Store
-	name      string
 }
 
 // SetDefaults sets the default values.
@@ -44,11 +44,12 @@ func (p *Provider) SetDefaults() {
 }
 
 // Init the provider.
-func (p *Provider) Init(storeType store.Backend, name string) error {
+func (p *Provider) Init(storeType store.Backend, name, namespace string) error {
 	ctx := log.With(context.Background(), log.Str(log.ProviderName, name))
 
-	p.storeType = storeType
 	p.name = name
+	p.namespace = namespace
+	p.storeType = storeType
 
 	kvClient, err := p.createKVClient(ctx)
 	if err != nil {
@@ -167,7 +168,7 @@ func (p *Provider) createKVClient(ctx context.Context) (store.Store, error) {
 		Username:          p.Username,
 		Password:          p.Password,
 		Token:             p.Token,
-		Namespace:         p.Namespace,
+		Namespace:         p.namespace,
 	}
 
 	if p.TLS != nil {
diff --git a/pkg/provider/kv/redis/redis.go b/pkg/provider/kv/redis/redis.go
index f24b94e8e9..96e48380e4 100644
--- a/pkg/provider/kv/redis/redis.go
+++ b/pkg/provider/kv/redis/redis.go
@@ -21,5 +21,5 @@ func (p *Provider) SetDefaults() {
 
 // Init the provider.
 func (p *Provider) Init() error {
-	return p.Provider.Init(store.REDIS, "redis")
+	return p.Provider.Init(store.REDIS, "redis", "")
 }
diff --git a/pkg/provider/kv/zk/zk.go b/pkg/provider/kv/zk/zk.go
index 7f21219ef1..01e5c9698a 100644
--- a/pkg/provider/kv/zk/zk.go
+++ b/pkg/provider/kv/zk/zk.go
@@ -21,5 +21,5 @@ func (p *Provider) SetDefaults() {
 
 // Init the provider.
 func (p *Provider) Init() error {
-	return p.Provider.Init(store.ZK, "zookeeper")
+	return p.Provider.Init(store.ZK, "zookeeper", "")
 }
diff --git a/webui/src/components/_commons/PanelMiddlewares.vue b/webui/src/components/_commons/PanelMiddlewares.vue
index 20243462e6..3437f8bef1 100644
--- a/webui/src/components/_commons/PanelMiddlewares.vue
+++ b/webui/src/components/_commons/PanelMiddlewares.vue
@@ -1142,9 +1142,15 @@ export default {
     getProviderLogoPath (provider) {
       const name = provider.toLowerCase()
 
-      if (name.includes('plugin-')) {
+      if (name.startsWith('plugin-')) {
         return 'statics/providers/plugin.svg'
       }
+      if (name.startsWith('consul-')) {
+        return `statics/providers/consul.svg`
+      }
+      if (name.startsWith('consulcatalog-')) {
+        return `statics/providers/consulcatalog.svg`
+      }
 
       return `statics/providers/${name}.svg`
     }
diff --git a/webui/src/components/_commons/PanelMirroringServices.vue b/webui/src/components/_commons/PanelMirroringServices.vue
index d0d748fb75..849d995593 100644
--- a/webui/src/components/_commons/PanelMirroringServices.vue
+++ b/webui/src/components/_commons/PanelMirroringServices.vue
@@ -66,9 +66,15 @@ export default {
       const provider = this.getProvider(service)
       const name = provider.toLowerCase()
 
-      if (name.includes('plugin-')) {
+      if (name.startsWith('plugin-')) {
         return 'statics/providers/plugin.svg'
       }
+      if (name.startsWith('consul-')) {
+        return `statics/providers/consul.svg`
+      }
+      if (name.startsWith('consulcatalog-')) {
+        return `statics/providers/consulcatalog.svg`
+      }
 
       return `statics/providers/${name}.svg`
     }
diff --git a/webui/src/components/_commons/PanelRouterDetails.vue b/webui/src/components/_commons/PanelRouterDetails.vue
index 41eaca57ed..582d131330 100644
--- a/webui/src/components/_commons/PanelRouterDetails.vue
+++ b/webui/src/components/_commons/PanelRouterDetails.vue
@@ -132,9 +132,15 @@ export default {
     getProviderLogoPath () {
       const name = this.data.provider.toLowerCase()
 
-      if (name.includes('plugin-')) {
+      if (name.startsWith('plugin-')) {
         return 'statics/providers/plugin.svg'
       }
+      if (name.startsWith('consul-')) {
+        return `statics/providers/consul.svg`
+      }
+      if (name.startsWith('consulcatalog-')) {
+        return `statics/providers/consulcatalog.svg`
+      }
 
       return `statics/providers/${name}.svg`
     }
diff --git a/webui/src/components/_commons/PanelServiceDetails.vue b/webui/src/components/_commons/PanelServiceDetails.vue
index 71a3c54dd1..6d8de0857a 100644
--- a/webui/src/components/_commons/PanelServiceDetails.vue
+++ b/webui/src/components/_commons/PanelServiceDetails.vue
@@ -146,9 +146,15 @@ export default {
     getProviderLogoPath () {
       const name = this.data.provider.toLowerCase()
 
-      if (name.includes('plugin-')) {
+      if (name.startsWith('plugin-')) {
         return 'statics/providers/plugin.svg'
       }
+      if (name.startsWith('consul-')) {
+        return `statics/providers/consul.svg`
+      }
+      if (name.startsWith('consulcatalog-')) {
+        return `statics/providers/consulcatalog.svg`
+      }
 
       return `statics/providers/${name}.svg`
     }
diff --git a/webui/src/components/_commons/PanelWeightedServices.vue b/webui/src/components/_commons/PanelWeightedServices.vue
index 3f23ed697c..2b6f539d63 100644
--- a/webui/src/components/_commons/PanelWeightedServices.vue
+++ b/webui/src/components/_commons/PanelWeightedServices.vue
@@ -66,9 +66,15 @@ export default {
       const provider = this.getProvider(service)
       const name = provider.toLowerCase()
 
-      if (name.includes('plugin-')) {
+      if (name.startsWith('plugin-')) {
         return 'statics/providers/plugin.svg'
       }
+      if (name.startsWith('consul-')) {
+        return `statics/providers/consul.svg`
+      }
+      if (name.startsWith('consulcatalog-')) {
+        return `statics/providers/consulcatalog.svg`
+      }
 
       return `statics/providers/${name}.svg`
     }
diff --git a/webui/src/components/_commons/ProviderIcon.vue b/webui/src/components/_commons/ProviderIcon.vue
index 035a548a49..6b3bac4098 100644
--- a/webui/src/components/_commons/ProviderIcon.vue
+++ b/webui/src/components/_commons/ProviderIcon.vue
@@ -11,9 +11,15 @@ export default {
     getLogoPath () {
       const name = this.name.toLowerCase()
 
-      if (name.includes('plugin-')) {
+      if (name.startsWith('plugin-')) {
         return 'statics/providers/plugin.svg'
       }
+      if (name.startsWith('consul-')) {
+        return `statics/providers/consul.svg`
+      }
+      if (name.startsWith('consulcatalog-')) {
+        return `statics/providers/consulcatalog.svg`
+      }
 
       return `statics/providers/${name}.svg`
     }
diff --git a/webui/src/components/dashboard/PanelProvider.vue b/webui/src/components/dashboard/PanelProvider.vue
index dba854c356..a4d451866f 100644
--- a/webui/src/components/dashboard/PanelProvider.vue
+++ b/webui/src/components/dashboard/PanelProvider.vue
@@ -28,9 +28,15 @@ export default {
     getLogoPath () {
       const name = this.getName.toLowerCase()
 
-      if (name.includes('plugin-')) {
+      if (name.startsWith('plugin-')) {
         return 'statics/providers/plugin.svg'
       }
+      if (name.startsWith('consul-')) {
+        return `statics/providers/consul.svg`
+      }
+      if (name.startsWith('consulcatalog-')) {
+        return `statics/providers/consulcatalog.svg`
+      }
 
       return `statics/providers/${name}.svg`
     }


Test patch:
diff --git a/pkg/provider/consulcatalog/config_test.go b/pkg/provider/consulcatalog/config_test.go
index ca3d75d143..a63d916d0c 100644
--- a/pkg/provider/consulcatalog/config_test.go
+++ b/pkg/provider/consulcatalog/config_test.go
@@ -219,7 +219,7 @@ func TestDefaultRule(t *testing.T) {
 					Status:  api.HealthPassing,
 				},
 			},
-			defaultRule: DefaultTemplateRule,
+			defaultRule: defaultTemplateRule,
 			expected: &dynamic.Configuration{
 				TCP: &dynamic.TCPConfiguration{
 					Routers:     map[string]*dynamic.TCPRouter{},
@@ -262,8 +262,10 @@ func TestDefaultRule(t *testing.T) {
 			t.Parallel()
 
 			p := Provider{
-				ExposedByDefault: true,
-				DefaultRule:      test.defaultRule,
+				Configuration: Configuration{
+					ExposedByDefault: true,
+					DefaultRule:      test.defaultRule,
+				},
 			}
 
 			err := p.Init()
@@ -2618,10 +2620,12 @@ func Test_buildConfiguration(t *testing.T) {
 			t.Parallel()
 
 			p := Provider{
-				ExposedByDefault: true,
-				DefaultRule:      "Host(`{{ normalize .Name }}.traefik.wtf`)",
-				ConnectAware:     test.ConnectAware,
-				Constraints:      test.constraints,
+				Configuration: Configuration{
+					ExposedByDefault: true,
+					DefaultRule:      "Host(`{{ normalize .Name }}.traefik.wtf`)",
+					ConnectAware:     test.ConnectAware,
+					Constraints:      test.constraints,
+				},
 			}
 
 			err := p.Init()
@@ -2651,3 +2655,55 @@ func Test_buildConfiguration(t *testing.T) {
 		})
 	}
 }
+
+func TestNamespaces(t *testing.T) {
+	testCases := []struct {
+		desc               string
+		namespace          string
+		namespaces         []string
+		expectedNamespaces []string
+	}{
+		{
+			desc:               "no defined namespaces",
+			expectedNamespaces: []string{""},
+		},
+		{
+			desc:               "deprecated: use of defined namespace",
+			namespace:          "test-ns",
+			expectedNamespaces: []string{"test-ns"},
+		},
+		{
+			desc:               "use of 1 defined namespaces",
+			namespaces:         []string{"test-ns"},
+			expectedNamespaces: []string{"test-ns"},
+		},
+		{
+			desc:               "use of multiple defined namespaces",
+			namespaces:         []string{"test-ns1", "test-ns2", "test-ns3", "test-ns4"},
+			expectedNamespaces: []string{"test-ns1", "test-ns2", "test-ns3", "test-ns4"},
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			pb := &ProviderBuilder{
+				Namespace:  test.namespace,
+				Namespaces: test.namespaces,
+			}
+
+			assert.Equal(t, test.expectedNamespaces, extractNSFromProvider(pb.BuildProviders()))
+		})
+	}
+}
+
+func extractNSFromProvider(providers []*Provider) []string {
+	res := make([]string, len(providers))
+	for i, p := range providers {
+		res[i] = p.namespace
+	}
+	return res
+}
diff --git a/pkg/provider/kv/consul/consul_test.go b/pkg/provider/kv/consul/consul_test.go
new file mode 100644
index 0000000000..286c0cd9c9
--- /dev/null
+++ b/pkg/provider/kv/consul/consul_test.go
@@ -0,0 +1,59 @@
+package consul
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNamespaces(t *testing.T) {
+	testCases := []struct {
+		desc               string
+		namespace          string
+		namespaces         []string
+		expectedNamespaces []string
+	}{
+		{
+			desc:               "no defined namespaces",
+			expectedNamespaces: []string{""},
+		},
+		{
+			desc:               "deprecated: use of defined namespace",
+			namespace:          "test-ns",
+			expectedNamespaces: []string{"test-ns"},
+		},
+		{
+			desc:               "use of 1 defined namespaces",
+			namespaces:         []string{"test-ns"},
+			expectedNamespaces: []string{"test-ns"},
+		},
+		{
+			desc:               "use of multiple defined namespaces",
+			namespaces:         []string{"test-ns1", "test-ns2", "test-ns3", "test-ns4"},
+			expectedNamespaces: []string{"test-ns1", "test-ns2", "test-ns3", "test-ns4"},
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			pb := &ProviderBuilder{
+				Namespace:  test.namespace,
+				Namespaces: test.namespaces,
+			}
+
+			assert.Equal(t, test.expectedNamespaces, extractNSFromProvider(pb.BuildProviders()))
+		})
+	}
+}
+
+func extractNSFromProvider(providers []*Provider) []string {
+	res := make([]string, len(providers))
+	for i, p := range providers {
+		res[i] = p.namespace
+	}
+	return res
+}
diff --git a/pkg/redactor/redactor_config_test.go b/pkg/redactor/redactor_config_test.go
index 1241a4f5f6..a8fa09e508 100644
--- a/pkg/redactor/redactor_config_test.go
+++ b/pkg/redactor/redactor_config_test.go
@@ -682,33 +682,37 @@ func TestDo_staticConfiguration(t *testing.T) {
 		Prefix:                    "MyPrefix",
 	}
 
-	config.Providers.ConsulCatalog = &consulcatalog.Provider{
-		Constraints: `Label("foo", "bar")`,
-		Endpoint: &consulcatalog.EndpointConfig{
-			Address:    "MyAddress",
-			Scheme:     "MyScheme",
-			DataCenter: "MyDatacenter",
-			Token:      "MyToken",
-			TLS: &types.ClientTLS{
-				CA:                 "myCa",
-				CAOptional:         true,
-				Cert:               "mycert.pem",
-				Key:                "mycert.key",
-				InsecureSkipVerify: true,
-			},
-			HTTPAuth: &consulcatalog.EndpointHTTPAuthConfig{
-				Username: "MyUsername",
-				Password: "MyPassword",
-			},
-			EndpointWaitTime: 42,
-		},
-		Prefix:            "MyPrefix",
-		RefreshInterval:   42,
-		RequireConsistent: true,
-		Stale:             true,
-		Cache:             true,
-		ExposedByDefault:  true,
-		DefaultRule:       "PathPrefix(`/`)",
+	config.Providers.ConsulCatalog = &consulcatalog.ProviderBuilder{
+		Configuration: consulcatalog.Configuration{
+			Constraints: `Label("foo", "bar")`,
+			Endpoint: &consulcatalog.EndpointConfig{
+				Address:    "MyAddress",
+				Scheme:     "MyScheme",
+				DataCenter: "MyDatacenter",
+				Token:      "MyToken",
+				TLS: &types.ClientTLS{
+					CA:                 "myCa",
+					CAOptional:         true,
+					Cert:               "mycert.pem",
+					Key:                "mycert.key",
+					InsecureSkipVerify: true,
+				},
+				HTTPAuth: &consulcatalog.EndpointHTTPAuthConfig{
+					Username: "MyUsername",
+					Password: "MyPassword",
+				},
+				EndpointWaitTime: 42,
+			},
+			Prefix:            "MyPrefix",
+			RefreshInterval:   42,
+			RequireConsistent: true,
+			Stale:             true,
+			Cache:             true,
+			ExposedByDefault:  true,
+			DefaultRule:       "PathPrefix(`/`)",
+		},
+		Namespace:  "ns",
+		Namespaces: []string{"ns1", "ns2"},
 	}
 
 	config.Providers.Ecs = &ecs.Provider{
@@ -723,7 +727,7 @@ func TestDo_staticConfiguration(t *testing.T) {
 		SecretAccessKey:      "AwsSecretAccessKey",
 	}
 
-	config.Providers.Consul = &consul.Provider{
+	config.Providers.Consul = &consul.ProviderBuilder{
 		Provider: kv.Provider{
 			RootKey:   "RootKey",
 			Endpoints: nil,
@@ -737,6 +741,8 @@ func TestDo_staticConfiguration(t *testing.T) {
 				InsecureSkipVerify: true,
 			},
 		},
+		Namespace:  "ns",
+		Namespaces: []string{"ns1", "ns2"},
 	}
 
 	config.Providers.Etcd = &etcd.Provider{
diff --git a/pkg/redactor/testdata/anonymized-static-config.json b/pkg/redactor/testdata/anonymized-static-config.json
index a525c388af..6773b047a5 100644
--- a/pkg/redactor/testdata/anonymized-static-config.json
+++ b/pkg/redactor/testdata/anonymized-static-config.json
@@ -206,7 +206,12 @@
       "stale": true,
       "cache": true,
       "exposedByDefault": true,
-      "defaultRule": "xxxx"
+      "defaultRule": "xxxx",
+      "namespace": "xxxx",
+      "namespaces": [
+        "xxxx",
+        "xxxx"
+      ]
     },
     "ecs": {
       "constraints": "Label(\"foo\", \"bar\")",
@@ -232,7 +237,12 @@
         "cert": "xxxx",
         "key": "xxxx",
         "insecureSkipVerify": true
-      }
+      },
+      "namespace": "xxxx",
+      "namespaces": [
+        "xxxx",
+        "xxxx"
+      ]
     },
     "etcd": {
       "rootKey": "xxxx",