Repository: containous/traefik
Pull request #5246 (2019-08-23 14:55:05)

Add rate limiter, rename maxConn into inFlightReq
<!--
PLEASE READ THIS MESSAGE.

HOW TO WRITE A GOOD PULL REQUEST?

- Make it small.
- Do only one thing.
- Avoid re-formatting.
- Make sure the code builds.
- Make sure all tests pass.
- Add tests.
- Write useful descriptions and titles.
- Address review comments in terms of additional commits.
- Do not amend/squash existing ones unless the PR is trivial.
- Read the contributing guide: https://github.com/containous/traefik/blob/master/CONTRIBUTING.md.

-->

### What does this PR do?

The rate limiting middleware had been removed at the beta1 version of Traefik v2, because its behaviour seemed unintuitive at times, and made us doubt its usefulness. It is now being reintroduced based on a simpler and clearer package (golang.org/x/time/rate), which results in a more satisfying behaviour and throughput. It also allowed us to change the configuration for it.

In addition, as the maxConn middleware did not exactly reflect what it was about, it is being renamed as inflightReq. Indeed, it is not about the number of currently opened connections (since there can be many requests using the same keep-alive connection), but rather about the number of requests being simultaneously processed.

<!-- A brief description of the change being made with this pull request. -->

### Motivation

To have a simpler and more intuitive rate limiting middleware.

<!-- What inspired you to submit this pull request? -->


### More

- [x] Added/updated tests
- [x] Added/updated documentation

### Additional Notes

<!-- Anything else we should know when reviewing? -->

#### Breaking changes

##### Rate limiting

```toml
# BEFORE
[http.middlewares]
  [http.middlewares.example.rateLimit]
    extractorFunc = "client.ip"
    
    [http.middlewares.example.rateLimit.rateSet.rate0]
      period = "10s"
      average = 100
      burst = 200
    
    [http.middlewares.example.rateLimit.rateSet.rate1]
      period = "3s"
      average = 5
      burst = 10

# AFTER
[http.middlewares]
  [http.middlewares.example.rateLimit]
    average = 100
    burst = 50
  [http.middlewares.example.rateLimit.sourceCriterion]
    requestheadername = "foobar"
    requesthost = true
    [http.middlewares.example.rateLimit.sourceCriterion.ipStrategy]
      depth =  3
      excludedIPs = ["foo", "bar"]
```

```yaml
# BEFORE
- "traefik.http.middlewares.example.ratelimit.extractorfunc=client.ip"
- "traefik.http.middlewares.example.ratelimit.rateset.rate0.period=10s"
- "traefik.http.middlewares.example.ratelimit.rateset.rate0.average=100"
- "traefik.http.middlewares.example.ratelimit.rateset.rate0.burst=200"
- "traefik.http.middlewares.example.ratelimit.rateset.rate1.period=3s"
- "traefik.http.middlewares.example.ratelimit.rateset.rate1.average=5"
- "traefik.http.middlewares.example.ratelimit.rateset.rate1.burst=10"

# AFTER
- "traefik.http.middlewares.example.ratelimit.average=100"
- "traefik.http.middlewares.example.ratelimit.burst=50"
- "traefik.http.middlewares.example.ratelimit.sourcecriterion.requestheadername=foobar"
- "traefik.http.middlewares.example.ratelimit.sourcecriterion.requesthost=true"
- "traefik.http.middlewares.example.ratelimit.sourcecriterion.ipstrategy.depth=3"
- "traefik.http.middlewares.example.ratelimit.sourcecriterion.ipstrategy.excludedips=foo,bar"
```

##### Inflight Requests

```toml
# BEFORE
[http.middlewares]
  [http.middlewares.example.maxConn]
    amount = 42
    extractorFunc = "foobar"

[http.middlewares]
  [http.middlewares.example.inFilghtReq]
    amount = 100
  [http.middlewares.example.inFilghtReq.sourceCriterion]
    requestHost = true
    requestHeaderName = "foobar"
    [http.middlewares.example.inFilghtReq.sourceCriterion.ipStrategy]
      depth = 3
      excludedIPs = ["foo", "bar"]
```

```yaml
# BEFORE
- "traefik.http.middlewares.example.maxconn.amount=100"
- "traefik.http.middlewares.example.maxconn.extractorfunc=request.host"

# AFTER
- "traefik.http.middlewares.example.inflightreq.amount=100"
- "traefik.http.middlewares.example.inflightreq.sourcecriterion.ipstrategy.depth=3"
- "traefik.http.middlewares.example.inflightreq.sourcecriterion.ipstrategy.excludedips=foo,bar"
- "traefik.http.middlewares.example.inflightreq.sourcecriterion.requestheadername=foobar"
- "traefik.http.middlewares.example.inflightreq.sourcecriterion.requesthost=true"

```

Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
Co-authored-by: Jean-Baptiste Doumenjou <jb.doumenjou@gmail.com>

<!-- Anything else we should know when reviewing? -->



Patch:
diff --git a/docs/content/assets/img/middleware/maxconnection.png b/docs/content/assets/img/middleware/inflightreq.png
similarity index 100%
rename from docs/content/assets/img/middleware/maxconnection.png
rename to docs/content/assets/img/middleware/inflightreq.png
diff --git a/docs/content/assets/styles/extra.css b/docs/content/assets/styles/extra.css
index 4498eacf1..f865034b0 100644
--- a/docs/content/assets/styles/extra.css
+++ b/docs/content/assets/styles/extra.css
@@ -34,6 +34,10 @@ h3 {
     font-weight: bold !important;
 }
 
+.md-typeset h5 {
+    text-transform: none;
+}
+
 figcaption {
     text-align: center;
     font-size: 0.8em;
diff --git a/docs/content/middlewares/inflightreq.md b/docs/content/middlewares/inflightreq.md
new file mode 100644
index 000000000..9b07b7a82
--- /dev/null
+++ b/docs/content/middlewares/inflightreq.md
@@ -0,0 +1,247 @@
+# InFlightReq
+
+Limiting the Number of Simultaneous In-Flight Requests
+{: .subtitle }
+
+![InFlightReq](../assets/img/middleware/inflightreq.png)
+
+To proactively prevent services from being overwhelmed with high load, a limit on the number of simultaneous in-flight requests can be applied.
+
+## Configuration Examples
+
+```yaml tab="Docker"
+labels:
+- "traefik.http.middlewares.test-inflightreq.inflightreq.amount=10"
+```
+
+```yaml tab="Kubernetes"
+apiVersion: traefik.containo.us/v1alpha1
+kind: Middleware
+metadata:
+  name: test-inflightreq
+spec:
+  inFlightReq:
+    amount: 10
+```
+
+```json tab="Marathon"
+"labels": {
+  "traefik.http.middlewares.test-inflightreq.inflightreq.amount": "10"
+}
+```
+
+```yaml tab="Rancher"
+# Limiting to 10 simultaneous connections
+labels:
+- "traefik.http.middlewares.test-inflightreq.inflightreq.amount=10"
+```
+
+```toml tab="File (TOML)"
+# Limiting to 10 simultaneous connections
+[http.middlewares]
+  [http.middlewares.test-inflightreq.inFlightReq]
+    amount = 10 
+```
+
+```yaml tab="File (YAML)"
+# Limiting to 10 simultaneous connections
+http:
+  middlewares:
+    test-inflightreq:
+      inFlightReq:
+        amount: 10 
+```
+
+## Configuration Options
+
+### `amount`
+
+The `amount` option defines the maximum amount of allowed simultaneous in-flight request.
+The middleware will return an `HTTP 429 Too Many Requests` if there are already `amount` requests in progress (based on the same `sourceCriterion` strategy).
+
+### `sourceCriterion`
+ 
+SourceCriterion defines what criterion is used to group requests as originating from a common source.
+The precedence order is `ipStrategy`, then `requestHeaderName`, then `requestHost`.
+If none are set, the default is to use the `requestHost`.
+
+#### `sourceCriterion.ipStrategy`
+
+The `ipStrategy` option defines two parameters that sets how Traefik will determine the client IP: `depth`, and `excludedIPs`.
+
+##### `ipStrategy.depth`
+
+The `depth` option tells Traefik to use the `X-Forwarded-For` header and take the IP located at the `depth` position (starting from the right).
+
+- If `depth` is greater than the total number of IPs in `X-Forwarded-For`, then the client IP will be empty.
+- `depth` is ignored if its value is is lesser than or equal to 0.
+    
+!!! note "Example of Depth & X-Forwarded-For"
+
+    If `depth` was equal to 2, and the request `X-Forwarded-For` header was `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` then the "real" client IP would be `"10.0.0.1"` (at depth 4) but the IP used as the criterion would be `"12.0.0.1"` (`depth=2`).
+
+    | `X-Forwarded-For`                       | `depth` | clientIP     |
+    |-----------------------------------------|---------|--------------|
+    | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `1`     | `"13.0.0.1"` |
+    | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3`     | `"11.0.0.1"` |
+    | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5`     | `""`         |
+
+##### `ipStrategy.excludedIPs`
+
+`excludedIPs` tells Traefik to scan the `X-Forwarded-For` header and pick the first IP not in the list.
+
+!!! important
+    If `depth` is specified, `excludedIPs` is ignored.
+
+!!! note "Example of ExcludedIPs & X-Forwarded-For"
+
+    | `X-Forwarded-For`                       | `excludedIPs`         | clientIP     |
+    |-----------------------------------------|-----------------------|--------------|
+    | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"12.0.0.1,13.0.0.1"` | `"11.0.0.1"` |
+    | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,13.0.0.1"` | `"12.0.0.1"` |
+    | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"10.0.0.1,13.0.0.1"` | `"12.0.0.1"` |
+    | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` |
+    | `"10.0.0.1,11.0.0.1"`                   | `"10.0.0.1,11.0.0.1"` | `""`         |
+
+```yaml tab="Docker"
+labels:
+- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
+```
+
+```yaml tab="Kubernetes"
+apiVersion: traefik.containo.us/v1alpha1
+kind: Middleware
+metadata:
+  name: test-inflightreq
+spec:
+  inFlightReq:
+    sourceCriterion:
+      ipStrategy:
+        excludedIPs:
+        - 127.0.0.1/32
+        - 192.168.1.7
+```
+
+```yaml tab="Rancher"
+labels:
+- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
+```
+
+```json tab="Marathon"
+"labels": {
+  "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.ipstrategy.excludedips": "127.0.0.1/32, 192.168.1.7"
+}
+```
+
+```toml tab="File (TOML)"
+[http.middlewares]
+  [http.middlewares.test-inflightreq.inflightreq]
+    [http.middlewares.test-inflightreq.inFlightReq.sourceCriterion.ipStrategy]
+      excludedIPs = ["127.0.0.1/32", "192.168.1.7"]
+```
+
+```yaml tab="File (YAML)"
+http:
+  middlewares:
+    test-inflightreq:
+      inFlightReq:
+        sourceCriterion:
+          ipStrategy:
+            excludedIPs:
+            - "127.0.0.1/32"
+            - "192.168.1.7"
+```
+
+#### `sourceCriterion.requestHeaderName`
+
+Requests having the same value for the given header are grouped as coming from the same source.
+
+```yaml tab="Docker"
+labels:
+- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requestheadername=username"
+```
+
+```yaml tab="Kubernetes"
+apiVersion: traefik.containo.us/v1alpha1
+kind: Middleware
+metadata:
+  name: test-inflightreq
+spec:
+  inFlightReq:
+	sourceCriterion:
+      requestHeaderName: username
+```
+
+```yaml tab="Rancher"
+labels:
+- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requestheadername=username"
+```
+
+```json tab="Marathon"
+"labels": {
+  "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requestheadername": "username"
+}
+```
+
+```toml tab="File (TOML)"
+[http.middlewares]
+  [http.middlewares.test-inflightreq.inflightreq]
+    [http.middlewares.test-inflightreq.inFlightReq.sourceCriterion]
+      requestHeaderName = "username"
+```
+
+```yaml tab="File (YAML)"
+http:
+  middlewares:
+    test-inflightreq:
+      inFlightReq:
+        sourceCriterion:
+          requestHeaderName: username
+```
+
+#### `sourceCriterion.requestHost`
+
+Whether to consider the request host as the source.
+
+```yaml tab="Docker"
+labels:
+- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requesthost=true"
+```
+
+```yaml tab="Kubernetes"
+apiVersion: traefik.containo.us/v1alpha1
+kind: Middleware
+metadata:
+  name: test-inflightreq
+spec:
+  inFlightReq:
+    sourceCriterion:
+      requestHost: true
+```
+
+```yaml tab="Rancher"
+labels:
+- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requesthost=true"
+```
+
+```json tab="Marathon"
+"labels": {
+  "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requesthost": "true"
+}
+```
+
+```toml tab="File (TOML)"
+[http.middlewares]
+  [http.middlewares.test-inflightreq.inflightreq]
+    [http.middlewares.test-inflightreq.inFlightReq.sourceCriterion]
+      requestHost = true
+```
+
+```yaml tab="File (YAML)"
+http:
+  middlewares:
+    test-inflightreq:
+      inFlightReq:
+        sourceCriterion:
+          requestHost: true
+```
diff --git a/docs/content/middlewares/maxconnection.md b/docs/content/middlewares/maxconnection.md
deleted file mode 100644
index bfddf30d9..000000000
--- a/docs/content/middlewares/maxconnection.md
+++ /dev/null
@@ -1,71 +0,0 @@
-# MaxConnection
-
-Limiting the Number of Simultaneous Clients
-{: .subtitle }
-
-![MaxConnection](../assets/img/middleware/maxconnection.png)
-
-To proactively prevent services from being overwhelmed with high load, a maximum connection limit can be applied.
-
-## Configuration Examples
-
-```yaml tab="Docker"
-# Limiting to 10 simultaneous connections
-labels:
-- "traefik.http.middlewares.test-maxconn.maxconn.amount=10"
-```
-
-```yaml tab="Kubernetes"
-apiVersion: traefik.containo.us/v1alpha1
-kind: Middleware
-metadata:
-  name: test-maxconn
-spec:
-  maxConn:
-    amount: 10
-```
-
-```json tab="Marathon"
-"labels": {
-  "traefik.http.middlewares.test-maxconn.maxconn.amount": "10"
-}
-```
-
-```yaml tab="Rancher"
-# Limiting to 10 simultaneous connections
-labels:
-- "traefik.http.middlewares.test-maxconn.maxconn.amount=10"
-```
-
-```toml tab="File (TOML)"
-# Limiting to 10 simultaneous connections
-[http.middlewares]
-  [http.middlewares.test-maxconn.maxConn]
-    amount = 10 
-```
-
-```yaml tab="File (YAML)"
-# Limiting to 10 simultaneous connections
-http:
-  middlewares:
-    test-maxconn:
-      maxConn:
-        amount: 10 
-```
-
-## Configuration Options
-
-### `amount`
-
-The `amount` option defines the maximum amount of allowed simultaneous connections.
-The middleware will return an `HTTP 429 Too Many Requests` if there are already `amount` requests in progress (based on the same `extractorFunc` strategy).
-
-### `extractorFunc`
-
-The `extractorFunc` defines the strategy used to categorize requests.
-
-The possible values are:
-
-- `request.host` categorizes requests based on the request host.
-- `client.ip` categorizes requests based on the client ip.
-- `request.header.ANY_HEADER` categorizes requests based on the provided `ANY_HEADER` value.
diff --git a/docs/content/middlewares/overview.md b/docs/content/middlewares/overview.md
index 91740e41f..a69ccddca 100644
--- a/docs/content/middlewares/overview.md
+++ b/docs/content/middlewares/overview.md
@@ -208,7 +208,7 @@ and therefore this specification would be ignored even if present.
 | [ForwardAuth](forwardauth.md)             | Authentication delegation                         | Security, Authentication    |
 | [Headers](headers.md)                     | Add / Update headers                              | Security                    |
 | [IPWhiteList](ipwhitelist.md)             | Limit the allowed client IPs                      | Security, Request lifecycle |
-| [MaxConnection](maxconnection.md)         | Limit the number of simultaneous connections      | Security, Request lifecycle |
+| [InFlightReq](inflightreq.md)             | Limit the number of simultaneous connections      | Security, Request lifecycle |
 | [PassTLSClientCert](passtlsclientcert.md) | Adding Client Certificates in a Header            | Security                    |
 | [RateLimit](ratelimit.md)                 | Limit the call frequency                          | Security, Request lifecycle |
 | [RedirectScheme](redirectscheme.md)       | Redirect easily the client elsewhere              | Request lifecycle           |
diff --git a/docs/content/middlewares/ratelimit.md b/docs/content/middlewares/ratelimit.md
index fa3856df5..058c67b27 100644
--- a/docs/content/middlewares/ratelimit.md
+++ b/docs/content/middlewares/ratelimit.md
@@ -1,9 +1,6 @@
 # RateLimit
 
-!!! warning
-    This middleware is disable for now.
-
-Protection from Too Many Calls
+To Control the Number of Requests Going to a Service
 {: .subtitle }
 
 ![RateLimit](../assets/img/middleware/ratelimit.png)
@@ -13,124 +10,337 @@ The RateLimit middleware ensures that services will receive a _fair_ number of r
 ## Configuration Example
 
 ```yaml tab="Docker"
-# Here, an average of 5 requests every 3 seconds is allowed and an average of 100 requests every 10 seconds.
-# These can "burst" up to 10 and 200 in each period, respectively.
+# Here, an average of 100 requests per second is allowed.
+# In addition, a burst of 50 requests is allowed.
 labels:
-- "traefik.http.middlewares.test-ratelimit.ratelimit.extractorfunc=client.ip"
-- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.period=10s"
-- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.average=100"
-- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.burst=200"
-- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.period=3s"
-- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.average=5"
-- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.burst=10"
-  		
+- "traefik.http.middlewares.test-ratelimit.ratelimit.average=100"
+- "traefik.http.middlewares.test-ratelimit.ratelimit.burst=50"
 ```
 
 ```yaml tab="Kubernetes"
-# Here, an average of 5 requests every 3 seconds is allowed and an average of 100 requests every 10 seconds.
-# These can "burst" up to 10 and 200 in each period, respectively.
+# Here, an average of 100 requests per second is allowed.
+# In addition, a burst of 50 requests is allowed.
 apiVersion: traefik.containo.us/v1alpha1
 kind: Middleware
 metadata:
   name: test-ratelimit
 spec:
   rateLimit:
-    extractorFunc: client.ip
-    rateSet:
-      rate0:
-          period: 10s
-          average: 100
-          burst: 200
-      rate1:
-          period: 3s
-          average: 5
-          burst: 10
+      average: 100
+      burst: 50
 ```
 
 ```json tab="Marathon"
 "labels": {
-  "traefik.http.middlewares.test-ratelimit.ratelimit.extractorfunc": "client.ip",
-  "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.period": "10s",
-  "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.average": "100",
-  "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.burst": "200",
-  "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.period": "3s",
-  "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.average": "5",
-  "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.burst": "10"
+  "traefik.http.middlewares.test-ratelimit.ratelimit.average": "100",
+  "traefik.http.middlewares.test-ratelimit.ratelimit.burst": "50"
 }
 ```
 
 ```yaml tab="Rancher"
-# Here, an average of 5 requests every 3 seconds is allowed and an average of 100 requests every 10 seconds.
-# These can "burst" up to 10 and 200 in each period, respectively.
+# Here, an average of 100 requests per second is allowed.
+# In addition, a burst of 50 requests is allowed.
 labels:
-- "traefik.http.middlewares.test-ratelimit.ratelimit.extractorfunc=client.ip"
-- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.period=10s"
-- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.average=100"
-- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate0.burst=200"
-- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.period=3s"
-- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.average=5"
-- "traefik.http.middlewares.test-ratelimit.ratelimit.rateset.rate1.burst=10"
-  		
+- "traefik.http.middlewares.test-ratelimit.ratelimit.average=100"
+- "traefik.http.middlewares.test-ratelimit.ratelimit.burst=50"
 ```
 
 ```toml tab="File (TOML)"
-# Here, an average of 5 requests every 3 seconds is allowed and an average of 100 requests every 10 seconds.
-# These can "burst" up to 10 and 200 in each period, respectively.
+# Here, an average of 100 requests per second is allowed.
+# In addition, a burst of 50 requests is allowed.
 [http.middlewares]
   [http.middlewares.test-ratelimit.rateLimit]
-    extractorFunc = "client.ip"
-    
-    [http.middlewares.test-ratelimit.rateLimit.rateSet.rate0]
-      period = "10s"
-      average = 100
-      burst = 200
-    
-    [http.middlewares.test-ratelimit.rateLimit.rateSet.rate1]
-      period = "3s"
-      average = 5
-      burst = 10
+    average = 100
+    burst = 50
 ```
 
 ```yaml tab="File (YAML)"
-# Here, an average of 5 requests every 3 seconds is allowed and an average of 100 requests every 10 seconds.
-# These can "burst" up to 10 and 200 in each period, respectively.
+# Here, an average of 100 requests per second is allowed.
+# In addition, a burst of 50 requests is allowed.
 http:
   middlewares:
     test-ratelimit:
       rateLimit:
-        extractorFunc: "client.ip"
-        rateSet:
-          rate0:
-            period: "10s"
-            average: 100
-            burst: 200
-          rate1:
-            period: "3s"
-            average: 5
-            burst: 10
+        average: 100
+        burst: 50
 ```
 
 ## Configuration Options
 
-### `extractorFunc`
+### `average`
+
+Average is the maximum rate, in requests/s, allowed for the given source.
+It defaults to 0, which means no rate limiting.
+
+```yaml tab="Docker"
+labels:
+- "traefik.http.middlewares.test-ratelimit.ratelimit.average=100"
+```
+
+```yaml tab="Kubernetes"
+apiVersion: traefik.containo.us/v1alpha1
+kind: Middleware
+metadata:
+  name: test-ratelimit
+spec:
+  rateLimit:
+      average: 100
+```
+
+```json tab="Marathon"
+"labels": {
+  "traefik.http.middlewares.test-ratelimit.ratelimit.average": "100",
+}
+```
+
+```yaml tab="Rancher"
+labels:
+- "traefik.http.middlewares.test-ratelimit.ratelimit.average=100"
+```
+
+```toml tab="File (TOML)"
+[http.middlewares]
+  [http.middlewares.test-ratelimit.rateLimit]
+    average = 100
+```
+
+```yaml tab="File (YAML)"
+http:
+  middlewares:
+    test-ratelimit:
+      rateLimit:
+        average: 100
+```
+
+### `burst`
+
+Burst is the maximum number of requests allowed to go through in the same arbitrarily small period of time.
+It defaults to 1.
+
+```yaml tab="Docker"
+labels:
+- "traefik.http.middlewares.test-ratelimit.ratelimit.burst=100"
+```
+
+```yaml tab="Kubernetes"
+apiVersion: traefik.containo.us/v1alpha1
+kind: Middleware
+metadata:
+  name: test-ratelimit
+spec:
+  rateLimit:
+      burst: 100
+```
+
+```json tab="Marathon"
+"labels": {
+  "traefik.http.middlewares.test-ratelimit.ratelimit.burst": "100",
+}
+```
+
+```yaml tab="Rancher"
+labels:
+- "traefik.http.middlewares.test-ratelimit.ratelimit.burst=100"
+  		
+```
+
+```toml tab="File (TOML)"
+[http.middlewares]
+  [http.middlewares.test-ratelimit.rateLimit]
+    burst = 100
+```
+
+```yaml tab="File (YAML)"
+http:
+  middlewares:
+    test-ratelimit:
+      rateLimit:
+        burst: 100
+```
+
+### `sourceCriterion`
  
-The `extractorFunc` option defines the strategy used to categorize requests.
+SourceCriterion defines what criterion is used to group requests as originating from a common source.
+The precedence order is `ipStrategy`, then `requestHeaderName`, then `requestHost`.
+If none are set, the default is to use the request's remote address field (as an `ipStrategy`).
+
+#### `sourceCriterion.ipStrategy`
+
+The `ipStrategy` option defines two parameters that sets how Traefik will determine the client IP: `depth`, and `excludedIPs`.
+
+##### `ipStrategy.depth`
+
+The `depth` option tells Traefik to use the `X-Forwarded-For` header and take the IP located at the `depth` position (starting from the right).
+
+- If `depth` is greater than the total number of IPs in `X-Forwarded-For`, then the client IP will be empty.
+- `depth` is ignored if its value is is lesser than or equal to 0.
+
+!!! note "Example of Depth & X-Forwarded-For"
+
+    If `depth` was equal to 2, and the request `X-Forwarded-For` header was `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` then the "real" client IP would be `"10.0.0.1"` (at depth 4) but the IP used as the criterion would be `"12.0.0.1"` (`depth=2`).
+
+    | `X-Forwarded-For`                       | `depth` | clientIP     |
+    |-----------------------------------------|---------|--------------|
+    | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `1`     | `"13.0.0.1"` |
+    | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3`     | `"11.0.0.1"` |
+    | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5`     | `""`         |
+
+##### `ipStrategy.excludedIPs`
+
+`excludedIPs` tells Traefik to scan the `X-Forwarded-For` header and pick the first IP not in the list.
+
+!!! important
+    If `depth` is specified, `excludedIPs` is ignored.
+
+!!! note "Example of ExcludedIPs & X-Forwarded-For"
+
+    | `X-Forwarded-For`                       | `excludedIPs`         | clientIP     |
+    |-----------------------------------------|-----------------------|--------------|
+    | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"12.0.0.1,13.0.0.1"` | `"11.0.0.1"` |
+    | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,13.0.0.1"` | `"12.0.0.1"` |
+    | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"10.0.0.1,13.0.0.1"` | `"12.0.0.1"` |
+    | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` |
+    | `"10.0.0.1,11.0.0.1"`                   | `"10.0.0.1,11.0.0.1"` | `""`         |
+
+```yaml tab="Docker"
+labels:
+- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
+```
 
-The possible values are:
+```yaml tab="Kubernetes"
+apiVersion: traefik.containo.us/v1alpha1
+kind: Middleware
+metadata:
+  name: test-ratelimit
+spec:
+  rateLimit:
+    sourceCriterion:
+      ipStrategy:
+        excludedIPs:
+        - 127.0.0.1/32
+        - 192.168.1.7
+```
 
-- `request.host` categorizes requests based on the request host.
-- `client.ip` categorizes requests based on the client ip.
-- `request.header.ANY_HEADER` categorizes requests based on the provided `ANY_HEADER` value.
+```yaml tab="Rancher"
+labels:
+- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
+```
 
-### `rateSet`
+```json tab="Marathon"
+"labels": {
+  "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.ipstrategy.excludedips": "127.0.0.1/32, 192.168.1.7"
+}
+```
 
-You can combine multiple rate limits. 
-The rate limit will trigger with the first reached limit.
+```toml tab="File (TOML)"
+[http.middlewares]
+  [http.middlewares.test-ratelimit.rateLimit]
+    [http.middlewares.test-ratelimit.rateLimit.sourceCriterion.ipStrategy]
+      excludedIPs = ["127.0.0.1/32", "192.168.1.7"]
+```
 
-Each rate limit has 3 options, `period`, `average`, and `burst`.
+```yaml tab="File (YAML)"
+http:
+  middlewares:
+    test-ratelimit:
+      rateLimit:
+        sourceCriterion:
+          ipStrategy:
+            excludedIPs:
+            - "127.0.0.1/32"
+            - "192.168.1.7"
+```
+
+#### `sourceCriterion.requestHeaderName`
+
+Requests having the same value for the given header are grouped as coming from the same source.
 
-The rate limit will allow an average of `average` requests every `period`, with a maximum of `burst` request on that period.
+```yaml tab="Docker"
+labels:
+- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requestheadername=username"
+```
 
-!!! note "Period Format"
+```yaml tab="Kubernetes"
+apiVersion: traefik.containo.us/v1alpha1
+kind: Middleware
+metadata:
+  name: test-ratelimit
+spec:
+  rateLimit:
+	sourceCriterion:
+      requestHeaderName: username
+```
+
+```yaml tab="Rancher"
+labels:
+- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requestheadername=username"
+```
+
+```json tab="Marathon"
+"labels": {
+  "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requestheadername": "username"
+}
+```
 
-    Period is to be given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration).
+```toml tab="File (TOML)"
+[http.middlewares]
+  [http.middlewares.test-ratelimit.rateLimit]
+    [http.middlewares.test-ratelimit.rateLimit.sourceCriterion]
+      requestHeaderName = "username"
+```
+
+```yaml tab="File (YAML)"
+http:
+  middlewares:
+    test-ratelimit:
+      rateLimit:
+        sourceCriterion:
+          requestHeaderName: username
+```
+
+#### `sourceCriterion.requestHost`
+
+Whether to consider the request host as the source.
+
+```yaml tab="Docker"
+labels:
+- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requesthost=true"
+```
+
+```yaml tab="Kubernetes"
+apiVersion: traefik.containo.us/v1alpha1
+kind: Middleware
+metadata:
+  name: test-ratelimit
+spec:
+  rateLimit:
+    sourceCriterion:
+      requestHost: true
+```
+
+```yaml tab="Rancher"
+labels:
+- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requesthost=true"
+```
+
+```json tab="Marathon"
+"labels": {
+  "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requesthost": "true"
+}
+```
+
+```toml tab="File (TOML)"
+[http.middlewares]
+  [http.middlewares.test-ratelimit.rateLimit]
+    [http.middlewares.test-ratelimit.rateLimit.sourceCriterion]
+      requestHost = true
+```
+
+```yaml tab="File (YAML)"
+http:
+  middlewares:
+    test-ratelimit:
+      rateLimit:
+        sourceCriterion:
+          requestHost: true
+```
\ No newline at end of file
diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml
index 69c40b9f5..c01640149 100644
--- a/docs/content/reference/dynamic-configuration/docker-labels.yml
+++ b/docs/content/reference/dynamic-configuration/docker-labels.yml
@@ -64,8 +64,12 @@
 - "traefik.http.middlewares.middleware10.ipwhitelist.ipstrategy.depth=42"
 - "traefik.http.middlewares.middleware10.ipwhitelist.ipstrategy.excludedips=foobar, foobar"
 - "traefik.http.middlewares.middleware10.ipwhitelist.sourcerange=foobar, foobar"
-- "traefik.http.middlewares.middleware11.maxconn.amount=42"
-- "traefik.http.middlewares.middleware11.maxconn.extractorfunc=foobar"
+- "traefik.http.middlewares.middleware11.inflightreq.amount=42"
+- "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.requestheadername=foobar"
+- "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.requesthost=true"
+- "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.ipstrategy.depth=42"
+- "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.ipstrategy.excludedips=foobar, foobar"
+- "traefik.http.middlewares.middleware11.inflightreq.sourcecriterion.requesthost=true"
 - "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.commonname=true"
 - "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.country=true"
 - "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.domaincomponent=true"
diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml
index 1e0a6bfb5..71ad8d891 100644
--- a/docs/content/reference/dynamic-configuration/file.toml
+++ b/docs/content/reference/dynamic-configuration/file.toml
@@ -170,9 +170,14 @@
           depth = 42
           excludedIPs = ["foobar", "foobar"]
     [http.middlewares.Middleware11]
-      [http.middlewares.Middleware11.maxConn]
+      [http.middlewares.Middleware11.inFlightReq]
         amount = 42
-        extractorFunc = "foobar"
+        [http.middlewares.Middleware11.inFlightReq.sourceCriterion]
+          requestHeaderName = "foobar"
+          requestHost = true
+          [http.middlewares.Middleware11.inFlightReq.sourceCriterion.ipStrategy]
+            depth = 42
+            excludedIPs = ["foobar", "foobar"]
     [http.middlewares.Middleware12]
       [http.middlewares.Middleware12.passTLSClientCert]
         pem = true
diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml
index bea253fdc..89753c2a5 100644
--- a/docs/content/reference/dynamic-configuration/file.yaml
+++ b/docs/content/reference/dynamic-configuration/file.yaml
@@ -198,9 +198,14 @@ http:
           - foobar
           - foobar
     Middleware11:
-      maxConn:
+      inFlightReq:
         amount: 42
-        extractorFunc: foobar
+        sourceCriterion:
+          ipStrategy:
+            depth: 42
+            excludedIPs: [ foobar, foobar ]
+          requestHeaderName: foobar
+          requestHost: true
     Middleware12:
       passTLSClientCert:
         pem: true
@@ -254,6 +259,16 @@ http:
         regex:
         - foobar
         - foobar
+    Middleware20:
+      rateLimit:
+        average: 42
+        burst: 42
+        sourceCriterion:
+          ipStrategy:
+            depth: 42
+            excludedIPs: [ foobar, foobar ]
+          requestHeaderName: foobar
+          requestHost: true
 tcp:
   routers:
     TCPRouter0:
diff --git a/docs/content/reference/dynamic-configuration/marathon-labels.json b/docs/content/reference/dynamic-configuration/marathon-labels.json
index 6c2432fdb..163924b4a 100644
--- a/docs/content/reference/dynamic-configuration/marathon-labels.json
+++ b/docs/content/reference/dynamic-configuration/marathon-labels.json
@@ -64,8 +64,11 @@
 "traefik.http.middlewares.middleware10.ipwhitelist.ipstrategy.depth": "42",
 "traefik.http.middlewares.middleware10.ipwhitelist.ipstrategy.excludedips": "foobar, foobar",
 "traefik.http.middlewares.middleware10.ipwhitelist.sourcerange": "foobar, foobar",
-"traefik.http.middlewares.middleware11.maxconn.amount": "42",
-"traefik.http.middlewares.middleware11.maxconn.extractorfunc": "foobar",
+"traefik.http.middlewares.Middleware11.inflightreq.amount":                                 "42",
+"traefik.http.middlewares.Middleware11.inflightreq.sourcecriterion.ipstrategy.depth":       "42",
+"traefik.http.middlewares.Middleware11.inflightreq.sourcecriterion.ipstrategy.excludedips": "foobar, fiibar",
+"traefik.http.middlewares.Middleware11.inflightreq.sourcecriterion.requestheadername":      "foobar",
+"traefik.http.middlewares.Middleware11.inflightreq.sourcecriterion.requesthost":            "true",
 "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.commonname": "true",
 "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.country": "true",
 "traefik.http.middlewares.middleware12.passtlsclientcert.info.issuer.domaincomponent": "true",
@@ -96,6 +99,12 @@
 "traefik.http.middlewares.middleware17.retry.attempts": "42",
 "traefik.http.middlewares.middleware18.stripprefix.prefixes": "foobar, foobar",
 "traefik.http.middlewares.middleware19.stripprefixregex.regex": "foobar, foobar",
+"traefik.http.middlewares.Middleware20.ratelimit.average":                                  "42",
+"traefik.http.middlewares.Middleware20.ratelimit.burst":                                    "42",
+"traefik.http.middlewares.Middleware20.ratelimit.sourcecriterion.requestheadername":        "foobar",
+"traefik.http.middlewares.Middleware20.ratelimit.sourcecriterion.requesthost":              "true",
+"traefik.http.middlewares.Middleware20.ratelimit.sourcecriterion.ipstrategy.depth":         "42",
+"traefik.http.middlewares.Middleware20.ratelimit.sourcecriterion.ipstrategy.excludedips":   "foobar, foobar",
 "traefik.http.routers.router0.entrypoints": "foobar, foobar",
 "traefik.http.routers.router0.middlewares": "foobar, foobar",
 "traefik.http.routers.router0.priority": "42",
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 76f03293b..46cba835d 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -102,7 +102,7 @@ nav:
       - 'ForwardAuth': 'middlewares/forwardauth.md'
       - 'Headers': 'middlewares/headers.md'
       - 'IpWhitelist': 'middlewares/ipwhitelist.md'
-      - 'Maxconn': 'middlewares/maxconnection.md'
+      - 'InFlightReq': 'middlewares/inflightreq.md'
       - 'PassTLSClientCert': 'middlewares/passtlsclientcert.md'
       - 'RateLimit': 'middlewares/ratelimit.md'
       - 'RedirectRegex': 'middlewares/redirectregex.md'
diff --git a/go.mod b/go.mod
index df006a4ad..9facb5675 100644
--- a/go.mod
+++ b/go.mod
@@ -57,6 +57,7 @@ require (
 	github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591
 	github.com/looplab/fsm v0.1.0 // indirect
 	github.com/magiconair/properties v1.8.1 // indirect
+	github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f
 	github.com/miekg/dns v1.1.15
 	github.com/mitchellh/copystructure v1.0.0
 	github.com/mitchellh/hashstructure v1.0.0
@@ -90,6 +91,7 @@ require (
 	github.com/vulcand/predicate v1.1.0
 	golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
 	golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect
+	golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
 	golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 // indirect
 	google.golang.org/grpc v1.22.1
 	gopkg.in/DataDog/dd-trace-go.v1 v1.16.1
diff --git a/integration/fixtures/ratelimit/simple.toml b/integration/fixtures/ratelimit/simple.toml
index 49373088a..7bdb5a367 100644
--- a/integration/fixtures/ratelimit/simple.toml
+++ b/integration/fixtures/ratelimit/simple.toml
@@ -24,15 +24,8 @@
 
 [http.middlewares]
   [http.middlewares.ratelimit.rateLimit]
-    extractorfunc = "client.ip"
-      [http.middlewares.ratelimit.rateLimit.rateSet.rateset1]
-        period = "60s"
-        average = 4
-        burst = 5
-      [http.middlewares.ratelimit.rateLimit.rateSet.rateset2]
-        period = "3s"
-        average = 1
-        burst = 2
+    average = 100
+    burst = 1
 
 [http.services]
   [http.services.service1]
diff --git a/pkg/config/dynamic/fixtures/sample.toml b/pkg/config/dynamic/fixtures/sample.toml
index 748cf6497..a90cee3f6 100644
--- a/pkg/config/dynamic/fixtures/sample.toml
+++ b/pkg/config/dynamic/fixtures/sample.toml
@@ -295,9 +295,14 @@
           key = "foobar"
           insecureSkipVerify = true
     [http.middlewares.Middleware16]
-      [http.middlewares.Middleware16.maxConn]
+      [http.middlewares.Middleware16.inFlightReq]
         amount = 42
-        extractorFunc = "foobar"
+        [http.middlewares.Middleware16.inFlightReq.sourceCriterion]
+          requestHeaderName = "foobar"
+          requestHost = true
+          [http.middlewares.Middleware16.inFlightReq.sourceCriterion.ipStrategy]
+            depth = 42
+            excludedIPs = ["foobar", "foobar"]
     [http.middlewares.Middleware17]
       [http.middlewares.Middleware17.buffering]
         maxRequestBodyBytes = 42
diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go
index 578ed2e28..b8fb8df27 100644
--- a/pkg/config/dynamic/middlewares.go
+++ b/pkg/config/dynamic/middlewares.go
@@ -8,30 +8,28 @@ import (
 	"os"
 
 	"github.com/containous/traefik/v2/pkg/ip"
-	"github.com/containous/traefik/v2/pkg/types"
 )
 
 // +k8s:deepcopy-gen=true
 
 // Middleware holds the Middleware configuration.
 type Middleware struct {
-	AddPrefix        *AddPrefix        `json:"addPrefix,omitempty" toml:"addPrefix,omitempty" yaml:"addPrefix,omitempty"`
-	StripPrefix      *StripPrefix      `json:"stripPrefix,omitempty" toml:"stripPrefix,omitempty" yaml:"stripPrefix,omitempty"`
-	StripPrefixRegex *StripPrefixRegex `json:"stripPrefixRegex,omitempty" toml:"stripPrefixRegex,omitempty" yaml:"stripPrefixRegex,omitempty"`
-	ReplacePath      *ReplacePath      `json:"replacePath,omitempty" toml:"replacePath,omitempty" yaml:"replacePath,omitempty"`
-	ReplacePathRegex *ReplacePathRegex `json:"replacePathRegex,omitempty" toml:"replacePathRegex,omitempty" yaml:"replacePathRegex,omitempty"`
-	Chain            *Chain            `json:"chain,omitempty" toml:"chain,omitempty" yaml:"chain,omitempty"`
-	IPWhiteList      *IPWhiteList      `json:"ipWhiteList,omitempty" toml:"ipWhiteList,omitempty" yaml:"ipWhiteList,omitempty"`
-	Headers          *Headers          `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"`
-	Errors           *ErrorPage        `json:"errors,omitempty" toml:"errors,omitempty" yaml:"errors,omitempty"`
-	// TODO: disable temporarily
-	// RateLimit         *RateLimit         `json:"rateLimit,omitempty" toml:"rateLimit,omitempty" yaml:"rateLimit,omitempty"`
+	AddPrefix         *AddPrefix         `json:"addPrefix,omitempty" toml:"addPrefix,omitempty" yaml:"addPrefix,omitempty"`
+	StripPrefix       *StripPrefix       `json:"stripPrefix,omitempty" toml:"stripPrefix,omitempty" yaml:"stripPrefix,omitempty"`
+	StripPrefixRegex  *StripPrefixRegex  `json:"stripPrefixRegex,omitempty" toml:"stripPrefixRegex,omitempty" yaml:"stripPrefixRegex,omitempty"`
+	ReplacePath       *ReplacePath       `json:"replacePath,omitempty" toml:"replacePath,omitempty" yaml:"replacePath,omitempty"`
+	ReplacePathRegex  *ReplacePathRegex  `json:"replacePathRegex,omitempty" toml:"replacePathRegex,omitempty" yaml:"replacePathRegex,omitempty"`
+	Chain             *Chain             `json:"chain,omitempty" toml:"chain,omitempty" yaml:"chain,omitempty"`
+	IPWhiteList       *IPWhiteList       `json:"ipWhiteList,omitempty" toml:"ipWhiteList,omitempty" yaml:"ipWhiteList,omitempty"`
+	Headers           *Headers           `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"`
+	Errors            *ErrorPage         `json:"errors,omitempty" toml:"errors,omitempty" yaml:"errors,omitempty"`
+	RateLimit         *RateLimit         `json:"rateLimit,omitempty" toml:"rateLimit,omitempty" yaml:"rateLimit,omitempty"`
 	RedirectRegex     *RedirectRegex     `json:"redirectRegex,omitempty" toml:"redirectRegex,omitempty" yaml:"redirectRegex,omitempty"`
 	RedirectScheme    *RedirectScheme    `json:"redirectScheme,omitempty" toml:"redirectScheme,omitempty" yaml:"redirectScheme,omitempty"`
 	BasicAuth         *BasicAuth         `json:"basicAuth,omitempty" toml:"basicAuth,omitempty" yaml:"basicAuth,omitempty"`
 	DigestAuth        *DigestAuth        `json:"digestAuth,omitempty" toml:"digestAuth,omitempty" yaml:"digestAuth,omitempty"`
 	ForwardAuth       *ForwardAuth       `json:"forwardAuth,omitempty" toml:"forwardAuth,omitempty" yaml:"forwardAuth,omitempty"`
-	MaxConn           *MaxConn           `json:"maxConn,omitempty" toml:"maxConn,omitempty" yaml:"maxConn,omitempty"`
+	InFlightReq       *InFlightReq       `json:"inFlightReq,omitempty" toml:"inFlightReq,omitempty" yaml:"inFlightReq,omitempty"`
 	Buffering         *Buffering         `json:"buffering,omitempty" toml:"buffering,omitempty" yaml:"buffering,omitempty"`
 	CircuitBreaker    *CircuitBreaker    `json:"circuitBreaker,omitempty" toml:"circuitBreaker,omitempty" yaml:"circuitBreaker,omitempty"`
 	Compress          *Compress          `json:"compress,omitempty" toml:"compress,omitempty" yaml:"compress,omitempty" label:"allowEmpty"`
@@ -219,10 +217,11 @@ func (h *Headers) HasSecureHeadersDefined() bool {
 type IPStrategy struct {
 	Depth       int      `json:"depth,omitempty" toml:"depth,omitempty" yaml:"depth,omitempty" export:"true"`
 	ExcludedIPs []string `json:"excludedIPs,omitempty" toml:"excludedIPs,omitempty" yaml:"excludedIPs,omitempty"`
+	// TODO(mpl): I think we should make RemoteAddr an explicit field. For one thing, it would yield better documentation.
 }
 
-// Get an IP selection strategy
-// if nil return the RemoteAddr strategy
+// Get an IP selection strategy.
+// If nil return the RemoteAddr strategy
 // else return a strategy base on the configuration using the X-Forwarded-For Header.
 // Depth override the ExcludedIPs
 func (s *IPStrategy) Get() (ip.Strategy, error) {
@@ -259,15 +258,17 @@ type IPWhiteList struct {
 
 // +k8s:deepcopy-gen=true
 
-// MaxConn holds maximum connection configuration.
-type MaxConn struct {
-	Amount        int64  `json:"amount,omitempty" toml:"amount,omitempty" yaml:"amount,omitempty"`
-	ExtractorFunc string `json:"extractorFunc,omitempty" toml:"extractorFunc,omitempty" yaml:"extractorFunc,omitempty"`
+// InFlightReq limits the number of requests being processed and served concurrently.
+type InFlightReq struct {
+	Amount          int64            `json:"amount,omitempty" toml:"amount,omitempty" yaml:"amount,omitempty"`
+	SourceCriterion *SourceCriterion `json:"sourceCriterion,omitempty" toml:"sourceCriterion,omitempty" yaml:"sourceCriterion,omitempty"`
 }
 
-// SetDefaults Default values for a MaxConn.
-func (m *MaxConn) SetDefaults() {
-	m.ExtractorFunc = "request.host"
+// SetDefaults Default values for a InFlightReq.
+func (i *InFlightReq) SetDefaults() {
+	i.SourceCriterion = &SourceCriterion{
+		RequestHost: true,
+	}
 }
 
 // +k8s:deepcopy-gen=true
@@ -280,25 +281,34 @@ type PassTLSClientCert struct {
 
 // +k8s:deepcopy-gen=true
 
-// Rate holds the rate limiting configuration for a specific time period.
-type Rate struct {
-	Period  types.Duration `json:"period,omitempty" toml:"period,omitempty" yaml:"period,omitempty"`
-	Average int64          `json:"average,omitempty" toml:"average,omitempty" yaml:"average,omitempty"`
-	Burst   int64          `json:"burst,omitempty" toml:"burst,omitempty" yaml:"burst,omitempty"`
+// SourceCriterion defines what criterion is used to group requests as originating from a common source.
+// The precedence order is IPStrategy, then RequestHeaderName.
+// If none are set, the default is to use the request's remote address field.
+type SourceCriterion struct {
+	IPStrategy        *IPStrategy `json:"ipStrategy" toml:"ipStrategy, omitempty"`
+	RequestHeaderName string      `json:"requestHeaderName,omitempty" toml:"requestHeaderName,omitempty" yaml:"requestHeaderName,omitempty"`
+	RequestHost       bool        `json:"requestHost,omitempty" toml:"requestHost,omitempty" yaml:"requestHost,omitempty"`
 }
 
 // +k8s:deepcopy-gen=true
 
-// RateLimit holds the rate limiting configuration for a given frontend.
+// RateLimit holds the rate limiting configuration for a given router.
 type RateLimit struct {
-	RateSet map[string]*Rate `json:"rateSet,omitempty" toml:"rateSet,omitempty" yaml:"rateSet,omitempty"`
-	// FIXME replace by ipStrategy see oxy and replace
-	ExtractorFunc string `json:"extractorFunc,omitempty" toml:"extractorFunc,omitempty" yaml:"extractorFunc,omitempty"`
+	// Average is the maximum rate, in requests/s, allowed for the given source.
+	// It defaults to 0, which means no rate limiting.
+	Average int64 `json:"average,omitempty" toml:"average,omitempty" yaml:"average,omitempty"`
+	// Burst is the maximum number of requests allowed to arrive in the same arbitrarily small period of time.
+	// It defaults to 1.
+	Burst           int64            `json:"burst,omitempty" toml:"burst,omitempty" yaml:"burst,omitempty"`
+	SourceCriterion *SourceCriterion `json:"sourceCriterion,omitempty" toml:"sourceCriterion,omitempty" yaml:"sourceCriterion,omitempty"`
 }
 
-// SetDefaults Default values for a MaxConn.
+// SetDefaults sets the default values on a RateLimit.
 func (r *RateLimit) SetDefaults() {
-	r.ExtractorFunc = "request.host"
+	r.Burst = 1
+	r.SourceCriterion = &SourceCriterion{
+		IPStrategy: &IPStrategy{},
+	}
 }
 
 // +k8s:deepcopy-gen=true
@@ -398,30 +408,30 @@ type ClientTLS struct {
 }
 
 // CreateTLSConfig creates a TLS config from ClientTLS structures.
-func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
-	if clientTLS == nil {
+func (c *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
+	if c == nil {
 		return nil, nil
 	}
 
 	var err error
 	caPool := x509.NewCertPool()
 	clientAuth := tls.NoClientCert
-	if clientTLS.CA != "" {
+	if c.CA != "" {
 		var ca []byte
-		if _, errCA := os.Stat(clientTLS.CA); errCA == nil {
-			ca, err = ioutil.ReadFile(clientTLS.CA)
+		if _, errCA := os.Stat(c.CA); errCA == nil {
+			ca, err = ioutil.ReadFile(c.CA)
 			if err != nil {
 				return nil, fmt.Errorf("failed to read CA. %s", err)
 			}
 		} else {
-			ca = []byte(clientTLS.CA)
+			ca = []byte(c.CA)
 		}
 
 		if !caPool.AppendCertsFromPEM(ca) {
 			return nil, fmt.Errorf("failed to parse CA")
 		}
 
-		if clientTLS.CAOptional {
+		if c.CAOptional {
 			clientAuth = tls.VerifyClientCertIfGiven
 		} else {
 			clientAuth = tls.RequireAndVerifyClientCert
@@ -429,16 +439,16 @@ func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
 	}
 
 	cert := tls.Certificate{}
-	_, errKeyIsFile := os.Stat(clientTLS.Key)
+	_, errKeyIsFile := os.Stat(c.Key)
 
-	if !clientTLS.InsecureSkipVerify && (len(clientTLS.Cert) == 0 || len(clientTLS.Key) == 0) {
+	if !c.InsecureSkipVerify && (len(c.Cert) == 0 || len(c.Key) == 0) {
 		return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created")
 	}
 
-	if len(clientTLS.Cert) > 0 && len(clientTLS.Key) > 0 {
-		if _, errCertIsFile := os.Stat(clientTLS.Cert); errCertIsFile == nil {
+	if len(c.Cert) > 0 && len(c.Key) > 0 {
+		if _, errCertIsFile := os.Stat(c.Cert); errCertIsFile == nil {
 			if errKeyIsFile == nil {
-				cert, err = tls.LoadX509KeyPair(clientTLS.Cert, clientTLS.Key)
+				cert, err = tls.LoadX509KeyPair(c.Cert, c.Key)
 				if err != nil {
 					return nil, fmt.Errorf("failed to load TLS keypair: %v", err)
 				}
@@ -447,7 +457,7 @@ func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
 			}
 		} else {
 			if errKeyIsFile != nil {
-				cert, err = tls.X509KeyPair([]byte(clientTLS.Cert), []byte(clientTLS.Key))
+				cert, err = tls.X509KeyPair([]byte(c.Cert), []byte(c.Key))
 				if err != nil {
 					return nil, fmt.Errorf("failed to load TLS keypair: %v", err)
 
@@ -461,7 +471,7 @@ func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
 	return &tls.Config{
 		Certificates:       []tls.Certificate{cert},
 		RootCAs:            caPool,
-		InsecureSkipVerify: clientTLS.InsecureSkipVerify,
+		InsecureSkipVerify: c.InsecureSkipVerify,
 		ClientAuth:         clientAuth,
 	}, nil
 }
diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go
index 454015d9c..84aefd044 100644
--- a/pkg/config/dynamic/zz_generated.deepcopy.go
+++ b/pkg/config/dynamic/zz_generated.deepcopy.go
@@ -525,17 +525,22 @@ func (in *IPWhiteList) DeepCopy() *IPWhiteList {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *MaxConn) DeepCopyInto(out *MaxConn) {
+func (in *InFlightReq) DeepCopyInto(out *InFlightReq) {
 	*out = *in
+	if in.SourceCriterion != nil {
+		in, out := &in.SourceCriterion, &out.SourceCriterion
+		*out = new(SourceCriterion)
+		(*in).DeepCopyInto(*out)
+	}
 	return
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MaxConn.
-func (in *MaxConn) DeepCopy() *MaxConn {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InFlightReq.
+func (in *InFlightReq) DeepCopy() *InFlightReq {
 	if in == nil {
 		return nil
 	}
-	out := new(MaxConn)
+	out := new(InFlightReq)
 	in.DeepCopyInto(out)
 	return out
 }
@@ -609,6 +614,11 @@ func (in *Middleware) DeepCopyInto(out *Middleware) {
 		*out = new(ErrorPage)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.RateLimit != nil {
+		in, out := &in.RateLimit, &out.RateLimit
+		*out = new(RateLimit)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.RedirectRegex != nil {
 		in, out := &in.RedirectRegex, &out.RedirectRegex
 		*out = new(RedirectRegex)
@@ -634,10 +644,10 @@ func (in *Middleware) DeepCopyInto(out *Middleware) {
 		*out = new(ForwardAuth)
 		(*in).DeepCopyInto(*out)
 	}
-	if in.MaxConn != nil {
-		in, out := &in.MaxConn, &out.MaxConn
-		*out = new(MaxConn)
-		**out = **in
+	if in.InFlightReq != nil {
+		in, out := &in.InFlightReq, &out.InFlightReq
+		*out = new(InFlightReq)
+		(*in).DeepCopyInto(*out)
 	}
 	if in.Buffering != nil {
 		in, out := &in.Buffering, &out.Buffering
@@ -698,39 +708,13 @@ func (in *PassTLSClientCert) DeepCopy() *PassTLSClientCert {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *Rate) DeepCopyInto(out *Rate) {
-	*out = *in
-	return
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rate.
-func (in *Rate) DeepCopy() *Rate {
-	if in == nil {
-		return nil
-	}
-	out := new(Rate)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *RateLimit) DeepCopyInto(out *RateLimit) {
 	*out = *in
-	if in.RateSet != nil {
-		in, out := &in.RateSet, &out.RateSet
-		*out = make(map[string]*Rate, len(*in))
-		for key, val := range *in {
-			var outVal *Rate
-			if val == nil {
-				(*out)[key] = nil
-			} else {
-				in, out := &val, &outVal
-				*out = new(Rate)
-				**out = **in
-			}
-			(*out)[key] = outVal
-		}
+	if in.SourceCriterion != nil {
+		in, out := &in.SourceCriterion, &out.SourceCriterion
+		*out = new(SourceCriterion)
+		(*in).DeepCopyInto(*out)
 	}
 	return
 }
@@ -996,6 +980,27 @@ func (in *Service) DeepCopy() *Service {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SourceCriterion) DeepCopyInto(out *SourceCriterion) {
+	*out = *in
+	if in.IPStrategy != nil {
+		in, out := &in.IPStrategy, &out.IPStrategy
+		*out = new(IPStrategy)
+		(*in).DeepCopyInto(*out)
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceCriterion.
+func (in *SourceCriterion) DeepCopy() *SourceCriterion {
+	if in == nil {
+		return nil
+	}
+	out := new(SourceCriterion)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Sticky) DeepCopyInto(out *Sticky) {
 	*out = *in
@@ -1295,6 +1300,11 @@ func (in Users) DeepCopy() Users {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *WRRService) DeepCopyInto(out *WRRService) {
 	*out = *in
+	if in.Weight != nil {
+		in, out := &in.Weight, &out.Weight
+		*out = new(int)
+		**out = **in
+	}
 	return
 }
 
@@ -1314,7 +1324,9 @@ func (in *WeightedRoundRobin) DeepCopyInto(out *WeightedRoundRobin) {
 	if in.Services != nil {
 		in, out := &in.Services, &out.Services
 		*out = make([]WRRService, len(*in))
-		copy(*out, *in)
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
 	}
 	if in.Sticky != nil {
 		in, out := &in.Sticky, &out.Sticky
diff --git a/pkg/config/file/fixtures/sample.toml b/pkg/config/file/fixtures/sample.toml
index 94dd9e694..920ee573b 100644
--- a/pkg/config/file/fixtures/sample.toml
+++ b/pkg/config/file/fixtures/sample.toml
@@ -287,9 +287,14 @@
           key = "foobar"
           insecureSkipVerify = true
     [http.middlewares.Middleware16]
-      [http.middlewares.Middleware16.maxConn]
+      [http.middlewares.Middleware16.inFlightReq]
         amount = 42
-        extractorFunc = "foobar"
+        [http.middlewares.Middleware16.inFlightReq.sourceCriterion]
+          requestHeaderName = "foobar"
+          requestHost = true
+          [http.middlewares.Middleware16.inFlightReq.sourceCriterion.ipStrategy]
+            depth = 42
+            excludedIPs = ["foobar", "foobar"]
     [http.middlewares.Middleware17]
       [http.middlewares.Middleware17.buffering]
         maxRequestBodyBytes = 42
diff --git a/pkg/ip/strategy.go b/pkg/ip/strategy.go
index dcb9f8b91..260c5021b 100644
--- a/pkg/ip/strategy.go
+++ b/pkg/ip/strategy.go
@@ -1,6 +1,7 @@
 package ip
 
 import (
+	"net"
 	"net/http"
 	"strings"
 )
@@ -17,9 +18,13 @@ type Strategy interface {
 // RemoteAddrStrategy a strategy that always return the remote address
 type RemoteAddrStrategy struct{}
 
-// GetIP return the selected IP
+// GetIP returns the selected IP
 func (s *RemoteAddrStrategy) GetIP(req *http.Request) string {
-	return req.RemoteAddr
+	ip, _, err := net.SplitHostPort(req.RemoteAddr)
+	if err != nil {
+		return req.RemoteAddr
+	}
+	return ip
 }
 
 // DepthStrategy a strategy based on the depth inside the X-Forwarded-For from right to left
diff --git a/pkg/middlewares/extractor.go b/pkg/middlewares/extractor.go
new file mode 100644
index 000000000..75b700523
--- /dev/null
+++ b/pkg/middlewares/extractor.go
@@ -0,0 +1,49 @@
+package middlewares
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+
+	"github.com/containous/traefik/v2/pkg/config/dynamic"
+	"github.com/containous/traefik/v2/pkg/log"
+	"github.com/vulcand/oxy/utils"
+)
+
+// GetSourceExtractor returns the SourceExtractor function corresponding to the given sourceMatcher.
+// It defaults to a RemoteAddrStrategy IPStrategy if need be.
+func GetSourceExtractor(ctx context.Context, sourceMatcher *dynamic.SourceCriterion) (utils.SourceExtractor, error) {
+	if sourceMatcher == nil ||
+		sourceMatcher.IPStrategy == nil &&
+			sourceMatcher.RequestHeaderName == "" && !sourceMatcher.RequestHost {
+		sourceMatcher = &dynamic.SourceCriterion{
+			IPStrategy: &dynamic.IPStrategy{},
+		}
+	}
+
+	logger := log.FromContext(ctx)
+	if sourceMatcher.IPStrategy != nil {
+		strategy, err := sourceMatcher.IPStrategy.Get()
+		if err != nil {
+			return nil, err
+		}
+
+		logger.Debug("Using IPStrategy")
+		return utils.ExtractorFunc(func(req *http.Request) (string, int64, error) {
+			return strategy.GetIP(req), 1, nil
+		}), nil
+	}
+
+	if sourceMatcher.RequestHeaderName != "" {
+		logger.Debug("Using RequestHeaderName")
+		return utils.NewExtractor(fmt.Sprintf("request.header.%s", sourceMatcher.RequestHeaderName))
+	}
+
+	if sourceMatcher.RequestHost {
+		logger.Debug("Using RequestHost")
+		return utils.NewExtractor("request.host")
+	}
+
+	return nil, errors.New("no SourceCriterion criterion defined")
+}
diff --git a/pkg/middlewares/inflightreq/inflight_req.go b/pkg/middlewares/inflightreq/inflight_req.go
new file mode 100644
index 000000000..4be488bd8
--- /dev/null
+++ b/pkg/middlewares/inflightreq/inflight_req.go
@@ -0,0 +1,57 @@
+package inflightreq
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+
+	"github.com/containous/traefik/v2/pkg/config/dynamic"
+	"github.com/containous/traefik/v2/pkg/log"
+	"github.com/containous/traefik/v2/pkg/middlewares"
+	"github.com/containous/traefik/v2/pkg/tracing"
+	"github.com/opentracing/opentracing-go/ext"
+	"github.com/vulcand/oxy/connlimit"
+)
+
+const (
+	typeName = "InFlightReq"
+)
+
+type inFlightReq struct {
+	handler http.Handler
+	name    string
+}
+
+// New creates a max request middleware.
+func New(ctx context.Context, next http.Handler, config dynamic.InFlightReq, name string) (http.Handler, error) {
+	ctxLog := log.With(ctx, log.Str(log.MiddlewareName, name), log.Str(log.MiddlewareType, typeName))
+	log.FromContext(ctxLog).Debug("Creating middleware")
+
+	if config.SourceCriterion == nil ||
+		config.SourceCriterion.IPStrategy == nil &&
+			config.SourceCriterion.RequestHeaderName == "" && !config.SourceCriterion.RequestHost {
+		config.SourceCriterion = &dynamic.SourceCriterion{
+			RequestHost: true,
+		}
+	}
+
+	sourceMatcher, err := middlewares.GetSourceExtractor(ctxLog, config.SourceCriterion)
+	if err != nil {
+		return nil, fmt.Errorf("error creating requests limiter: %v", err)
+	}
+
+	handler, err := connlimit.New(next, sourceMatcher, config.Amount)
+	if err != nil {
+		return nil, fmt.Errorf("error creating connection limit: %v", err)
+	}
+
+	return &inFlightReq{handler: handler, name: name}, nil
+}
+
+func (i *inFlightReq) GetTracingInformation() (string, ext.SpanKindEnum) {
+	return i.name, tracing.SpanKindNoneEnum
+}
+
+func (i *inFlightReq) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
+	i.handler.ServeHTTP(rw, req)
+}
diff --git a/pkg/middlewares/maxconnection/max_connection.go b/pkg/middlewares/maxconnection/max_connection.go
deleted file mode 100644
index 9e3660fa0..000000000
--- a/pkg/middlewares/maxconnection/max_connection.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package maxconnection
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-
-	"github.com/containous/traefik/v2/pkg/config/dynamic"
-	"github.com/containous/traefik/v2/pkg/middlewares"
-	"github.com/containous/traefik/v2/pkg/tracing"
-	"github.com/opentracing/opentracing-go/ext"
-	"github.com/vulcand/oxy/connlimit"
-	"github.com/vulcand/oxy/utils"
-)
-
-const (
-	typeName = "MaxConnection"
-)
-
-type maxConnection struct {
-	handler http.Handler
-	name    string
-}
-
-// New creates a max connection middleware.
-func New(ctx context.Context, next http.Handler, maxConns dynamic.MaxConn, name string) (http.Handler, error) {
-	middlewares.GetLogger(ctx, name, typeName).Debug("Creating middleware")
-
-	extractFunc, err := utils.NewExtractor(maxConns.ExtractorFunc)
-	if err != nil {
-		return nil, fmt.Errorf("error creating connection limit: %v", err)
-	}
-
-	handler, err := connlimit.New(next, extractFunc, maxConns.Amount)
-	if err != nil {
-		return nil, fmt.Errorf("error creating connection limit: %v", err)
-	}
-
-	return &maxConnection{handler: handler, name: name}, nil
-}
-
-func (mc *maxConnection) GetTracingInformation() (string, ext.SpanKindEnum) {
-	return mc.name, tracing.SpanKindNoneEnum
-}
-
-func (mc *maxConnection) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
-	mc.handler.ServeHTTP(rw, req)
-}
diff --git a/pkg/middlewares/ratelimiter/rate_limiter.go b/pkg/middlewares/ratelimiter/rate_limiter.go
index 157d7c8c5..73682c200 100644
--- a/pkg/middlewares/ratelimiter/rate_limiter.go
+++ b/pkg/middlewares/ratelimiter/rate_limiter.go
@@ -1,54 +1,146 @@
+// Package ratelimiter implements a rate limiting and traffic shaping middleware with a set of token buckets.
 package ratelimiter
 
 import (
 	"context"
+	"fmt"
 	"net/http"
+	"sync"
 	"time"
 
 	"github.com/containous/traefik/v2/pkg/config/dynamic"
+	"github.com/containous/traefik/v2/pkg/log"
 	"github.com/containous/traefik/v2/pkg/middlewares"
 	"github.com/containous/traefik/v2/pkg/tracing"
+	"github.com/mailgun/ttlmap"
 	"github.com/opentracing/opentracing-go/ext"
-	"github.com/vulcand/oxy/ratelimit"
 	"github.com/vulcand/oxy/utils"
+	"golang.org/x/time/rate"
 )
 
 const (
-	typeName = "RateLimiterType"
+	typeName   = "RateLimiterType"
+	maxSources = 65536
 )
 
+// rateLimiter implements rate limiting and traffic shaping with a set of token buckets;
+// one for each traffic source. The same parameters are applied to all the buckets.
 type rateLimiter struct {
-	handler http.Handler
-	name    string
+	name  string
+	rate  rate.Limit // reqs/s
+	burst int64
+	// maxDelay is the maximum duration we're willing to wait for a bucket reservation to become effective, in nanoseconds.
+	// For now it is somewhat arbitrarily set to 1/rate.
+	maxDelay      time.Duration
+	sourceMatcher utils.SourceExtractor
+	next          http.Handler
+
+	bucketsMu sync.Mutex
+	buckets   *ttlmap.TtlMap // actual buckets, keyed by source.
 }
 
-// New creates rate limiter middleware.
+// New returns a rate limiter middleware.
 func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name string) (http.Handler, error) {
-	middlewares.GetLogger(ctx, name, typeName).Debug("Creating middleware")
+	ctxLog := log.With(ctx, log.Str(log.MiddlewareName, name), log.Str(log.MiddlewareType, typeName))
+	log.FromContext(ctxLog).Debug("Creating middleware")
 
-	extractFunc, err := utils.NewExtractor(config.ExtractorFunc)
-	if err != nil {
-		return nil, err
+	if config.SourceCriterion == nil ||
+		config.SourceCriterion.IPStrategy == nil &&
+			config.SourceCriterion.RequestHeaderName == "" && !config.SourceCriterion.RequestHost {
+		config.SourceCriterion = &dynamic.SourceCriterion{
+			IPStrategy: &dynamic.IPStrategy{},
+		}
 	}
 
-	rateSet := ratelimit.NewRateSet()
-	for _, rate := range config.RateSet {
-		if err = rateSet.Add(time.Duration(rate.Period), rate.Average, rate.Burst); err != nil {
-			return nil, err
-		}
+	sourceMatcher, err := middlewares.GetSourceExtractor(ctxLog, config.SourceCriterion)
+	if err != nil {
+		return nil, err
 	}
 
-	rl, err := ratelimit.New(next, extractFunc, rateSet)
+	buckets, err := ttlmap.NewMap(maxSources)
 	if err != nil {
 		return nil, err
 	}
-	return &rateLimiter{handler: rl, name: name}, nil
+
+	burst := config.Burst
+	if burst <= 0 {
+		burst = 1
+	}
+
+	// Logically, we should set maxDelay to ~infinity when config.Average == 0 (because it means to rate limiting),
+	// but since the reservation will give us a delay = 0 anyway in this case, we're good even with any maxDelay >= 0.
+	var maxDelay time.Duration
+	if config.Average != 0 {
+		maxDelay = time.Second / time.Duration(config.Average*2)
+	}
+
+	return &rateLimiter{
+		name:          name,
+		rate:          rate.Limit(config.Average),
+		burst:         burst,
+		maxDelay:      maxDelay,
+		next:          next,
+		sourceMatcher: sourceMatcher,
+		buckets:       buckets,
+	}, nil
 }
 
-func (r *rateLimiter) GetTracingInformation() (string, ext.SpanKindEnum) {
-	return r.name, tracing.SpanKindNoneEnum
+func (rl *rateLimiter) GetTracingInformation() (string, ext.SpanKindEnum) {
+	return rl.name, tracing.SpanKindNoneEnum
+}
+
+func (rl *rateLimiter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	logger := middlewares.GetLogger(r.Context(), rl.name, typeName)
+
+	source, amount, err := rl.sourceMatcher.Extract(r)
+	if err != nil {
+		logger.Errorf("could not extract source of request: %v", err)
+		http.Error(w, "could not extract source of request", http.StatusInternalServerError)
+		return
+	}
+
+	if amount != 1 {
+		logger.Infof("ignoring token bucket amount > 1: %d", amount)
+	}
+
+	rl.bucketsMu.Lock()
+	defer rl.bucketsMu.Unlock()
+
+	var bucket *rate.Limiter
+	if rlSource, exists := rl.buckets.Get(source); exists {
+		bucket = rlSource.(*rate.Limiter)
+	} else {
+		bucket = rate.NewLimiter(rl.rate, int(rl.burst))
+		if err := rl.buckets.Set(source, bucket, int(rl.maxDelay)*10+1); err != nil {
+			logger.Errorf("could not insert bucket: %v", err)
+			http.Error(w, "could not insert bucket", http.StatusInternalServerError)
+			return
+		}
+	}
+
+	res := bucket.Reserve()
+	if !res.OK() {
+		http.Error(w, "No bursty traffic allowed", http.StatusTooManyRequests)
+		return
+	}
+
+	delay := res.Delay()
+	if delay > rl.maxDelay {
+		res.Cancel()
+		rl.serveDelayError(w, r, delay)
+		return
+	}
+
+	time.Sleep(delay)
+	rl.next.ServeHTTP(w, r)
 }
 
-func (r *rateLimiter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
-	r.handler.ServeHTTP(rw, req)
+func (rl *rateLimiter) serveDelayError(w http.ResponseWriter, r *http.Request, delay time.Duration) {
+	w.Header().Set("Retry-After", fmt.Sprintf("%.0f", delay.Seconds()))
+	w.Header().Set("X-Retry-In", delay.String())
+	w.WriteHeader(http.StatusTooManyRequests)
+
+	if _, err := w.Write([]byte(http.StatusText(http.StatusTooManyRequests))); err != nil {
+		middlewares.GetLogger(r.Context(), rl.name, typeName).Errorf("could not serve 429: %v", err)
+	}
 }
diff --git a/pkg/server/middleware/middlewares.go b/pkg/server/middleware/middlewares.go
index ea8254f32..4fd747780 100644
--- a/pkg/server/middleware/middlewares.go
+++ b/pkg/server/middleware/middlewares.go
@@ -17,9 +17,10 @@ import (
 	"github.com/containous/traefik/v2/pkg/middlewares/compress"
 	"github.com/containous/traefik/v2/pkg/middlewares/customerrors"
 	"github.com/containous/traefik/v2/pkg/middlewares/headers"
+	"github.com/containous/traefik/v2/pkg/middlewares/inflightreq"
 	"github.com/containous/traefik/v2/pkg/middlewares/ipwhitelist"
-	"github.com/containous/traefik/v2/pkg/middlewares/maxconnection"
 	"github.com/containous/traefik/v2/pkg/middlewares/passtlsclientcert"
+	"github.com/containous/traefik/v2/pkg/middlewares/ratelimiter"
 	"github.com/containous/traefik/v2/pkg/middlewares/redirect"
 	"github.com/containous/traefik/v2/pkg/middlewares/replacepath"
 	"github.com/containous/traefik/v2/pkg/middlewares/replacepathregex"
@@ -122,7 +123,7 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
 	}
 
 	// Buffering
-	if config.Buffering != nil && config.MaxConn.Amount != 0 {
+	if config.Buffering != nil && config.InFlightReq.Amount != 0 {
 		if middleware != nil {
 			return nil, badConf
 		}
@@ -211,13 +212,13 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
 		}
 	}
 
-	// MaxConn
-	if config.MaxConn != nil && config.MaxConn.Amount != 0 {
+	// InFlightReq
+	if config.InFlightReq != nil && config.InFlightReq.Amount != 0 {
 		if middleware != nil {
 			return nil, badConf
 		}
 		middleware = func(next http.Handler) (http.Handler, error) {
-			return maxconnection.New(ctx, next, *config.MaxConn, middlewareName)
+			return inflightreq.New(ctx, next, *config.InFlightReq, middlewareName)
 		}
 	}
 
@@ -231,16 +232,15 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
 		}
 	}
 
-	// TODO: disable temporarily (rateLimit)
 	// RateLimit
-	// if config.RateLimit != nil {
-	// 	if middleware != nil {
-	// 		return nil, badConf
-	// 	}
-	// 	middleware = func(next http.Handler) (http.Handler, error) {
-	// 		return ratelimiter.New(ctx, next, *config.RateLimit, middlewareName)
-	// 	}
-	// }
+	if config.RateLimit != nil {
+		if middleware != nil {
+			return nil, badConf
+		}
+		middleware = func(next http.Handler) (http.Handler, error) {
+			return ratelimiter.New(ctx, next, *config.RateLimit, middlewareName)
+		}
+	}
 
 	// RedirectRegex
 	if config.RedirectRegex != nil {


Test patch:
diff --git a/integration/integration_test.go b/integration/integration_test.go
index 2ec9a448e..934da96d8 100644
--- a/integration/integration_test.go
+++ b/integration/integration_test.go
@@ -54,8 +54,7 @@ func init() {
 		check.Suite(&LogRotationSuite{})
 		check.Suite(&MarathonSuite{})
 		check.Suite(&MarathonSuite15{})
-		// TODO: disable temporarily
-		// check.Suite(&RateLimitSuite{})
+		check.Suite(&RateLimitSuite{})
 		check.Suite(&RestSuite{})
 		check.Suite(&RetrySuite{})
 		check.Suite(&SimpleSuite{})
diff --git a/integration/ratelimit_test.go b/integration/ratelimit_test.go
index bd43117a9..32a9c12c3 100644
--- a/integration/ratelimit_test.go
+++ b/integration/ratelimit_test.go
@@ -37,29 +37,19 @@ func (s *RateLimitSuite) TestSimpleConfiguration(c *check.C) {
 	err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("ratelimit"))
 	c.Assert(err, checker.IsNil)
 
-	err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
-	c.Assert(err, checker.IsNil)
-	err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
-	c.Assert(err, checker.IsNil)
-	err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests))
-	c.Assert(err, checker.IsNil)
-
-	// sleep for 4 seconds to be certain the configured time period has elapsed
-	// then test another request and verify a 200 status code
-	time.Sleep(4 * time.Second)
-	err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
-	c.Assert(err, checker.IsNil)
-
-	// continue requests at 3 second intervals to test the other rate limit time period
-	time.Sleep(3 * time.Second)
-	err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
-	c.Assert(err, checker.IsNil)
-
-	time.Sleep(3 * time.Second)
-	err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
-	c.Assert(err, checker.IsNil)
-
-	time.Sleep(3 * time.Second)
-	err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusTooManyRequests))
-	c.Assert(err, checker.IsNil)
+	start := time.Now()
+	count := 0
+	for {
+		err = try.GetRequest("http://127.0.0.1:8081/", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
+		c.Assert(err, checker.IsNil)
+		count++
+		if count > 100 {
+			break
+		}
+	}
+	stop := time.Now()
+	elapsed := stop.Sub(start)
+	if elapsed < time.Second*99/100 {
+		c.Fatalf("requests throughput was too fast wrt to rate limiting: 100 requests in %v", elapsed)
+	}
 }
diff --git a/pkg/config/label/label_test.go b/pkg/config/label/label_test.go
index 0edc8bc31..1fedf3046 100644
--- a/pkg/config/label/label_test.go
+++ b/pkg/config/label/label_test.go
@@ -11,112 +11,113 @@ import (
 
 func TestDecodeConfiguration(t *testing.T) {
 	labels := map[string]string{
-		"traefik.http.middlewares.Middleware0.addprefix.prefix":                                "foobar",
-		"traefik.http.middlewares.Middleware1.basicauth.headerfield":                           "foobar",
-		"traefik.http.middlewares.Middleware1.basicauth.realm":                                 "foobar",
-		"traefik.http.middlewares.Middleware1.basicauth.removeheader":                          "true",
-		"traefik.http.middlewares.Middleware1.basicauth.users":                                 "foobar, fiibar",
-		"traefik.http.middlewares.Middleware1.basicauth.usersfile":                             "foobar",
-		"traefik.http.middlewares.Middleware2.buffering.maxrequestbodybytes":                   "42",
-		"traefik.http.middlewares.Middleware2.buffering.maxresponsebodybytes":                  "42",
-		"traefik.http.middlewares.Middleware2.buffering.memrequestbodybytes":                   "42",
-		"traefik.http.middlewares.Middleware2.buffering.memresponsebodybytes":                  "42",
-		"traefik.http.middlewares.Middleware2.buffering.retryexpression":                       "foobar",
-		"traefik.http.middlewares.Middleware3.chain.middlewares":                               "foobar, fiibar",
-		"traefik.http.middlewares.Middleware4.circuitbreaker.expression":                       "foobar",
-		"traefik.http.middlewares.Middleware5.digestauth.headerfield":                          "foobar",
-		"traefik.http.middlewares.Middleware5.digestauth.realm":                                "foobar",
-		"traefik.http.middlewares.Middleware5.digestauth.removeheader":                         "true",
-		"traefik.http.middlewares.Middleware5.digestauth.users":                                "foobar, fiibar",
-		"traefik.http.middlewares.Middleware5.digestauth.usersfile":                            "foobar",
-		"traefik.http.middlewares.Middleware6.errors.query":                                    "foobar",
-		"traefik.http.middlewares.Middleware6.errors.service":                                  "foobar",
-		"traefik.http.middlewares.Middleware6.errors.status":                                   "foobar, fiibar",
-		"traefik.http.middlewares.Middleware7.forwardauth.address":                             "foobar",
-		"traefik.http.middlewares.Middleware7.forwardauth.authresponseheaders":                 "foobar, fiibar",
-		"traefik.http.middlewares.Middleware7.forwardauth.tls.ca":                              "foobar",
-		"traefik.http.middlewares.Middleware7.forwardauth.tls.caoptional":                      "true",
-		"traefik.http.middlewares.Middleware7.forwardauth.tls.cert":                            "foobar",
-		"traefik.http.middlewares.Middleware7.forwardauth.tls.insecureskipverify":              "true",
-		"traefik.http.middlewares.Middleware7.forwardauth.tls.key":                             "foobar",
-		"traefik.http.middlewares.Middleware7.forwardauth.trustforwardheader":                  "true",
-		"traefik.http.middlewares.Middleware8.headers.accesscontrolallowcredentials":           "true",
-		"traefik.http.middlewares.Middleware8.headers.allowedhosts":                            "foobar, fiibar",
-		"traefik.http.middlewares.Middleware8.headers.accesscontrolallowheaders":               "X-foobar, X-fiibar",
-		"traefik.http.middlewares.Middleware8.headers.accesscontrolallowmethods":               "GET, PUT",
-		"traefik.http.middlewares.Middleware8.headers.accesscontrolalloworigin":                "foobar",
-		"traefik.http.middlewares.Middleware8.headers.accesscontrolexposeheaders":              "X-foobar, X-fiibar",
-		"traefik.http.middlewares.Middleware8.headers.accesscontrolmaxage":                     "200",
-		"traefik.http.middlewares.Middleware8.headers.addvaryheader":                           "true",
-		"traefik.http.middlewares.Middleware8.headers.browserxssfilter":                        "true",
-		"traefik.http.middlewares.Middleware8.headers.contentsecuritypolicy":                   "foobar",
-		"traefik.http.middlewares.Middleware8.headers.contenttypenosniff":                      "true",
-		"traefik.http.middlewares.Middleware8.headers.custombrowserxssvalue":                   "foobar",
-		"traefik.http.middlewares.Middleware8.headers.customframeoptionsvalue":                 "foobar",
-		"traefik.http.middlewares.Middleware8.headers.customrequestheaders.name0":              "foobar",
-		"traefik.http.middlewares.Middleware8.headers.customrequestheaders.name1":              "foobar",
-		"traefik.http.middlewares.Middleware8.headers.customresponseheaders.name0":             "foobar",
-		"traefik.http.middlewares.Middleware8.headers.customresponseheaders.name1":             "foobar",
-		"traefik.http.middlewares.Middleware8.headers.forcestsheader":                          "true",
-		"traefik.http.middlewares.Middleware8.headers.framedeny":                               "true",
-		"traefik.http.middlewares.Middleware8.headers.hostsproxyheaders":                       "foobar, fiibar",
-		"traefik.http.middlewares.Middleware8.headers.isdevelopment":                           "true",
-		"traefik.http.middlewares.Middleware8.headers.publickey":                               "foobar",
-		"traefik.http.middlewares.Middleware8.headers.referrerpolicy":                          "foobar",
-		"traefik.http.middlewares.Middleware8.headers.featurepolicy":                           "foobar",
-		"traefik.http.middlewares.Middleware8.headers.sslforcehost":                            "true",
-		"traefik.http.middlewares.Middleware8.headers.sslhost":                                 "foobar",
-		"traefik.http.middlewares.Middleware8.headers.sslproxyheaders.name0":                   "foobar",
-		"traefik.http.middlewares.Middleware8.headers.sslproxyheaders.name1":                   "foobar",
-		"traefik.http.middlewares.Middleware8.headers.sslredirect":                             "true",
-		"traefik.http.middlewares.Middleware8.headers.ssltemporaryredirect":                    "true",
-		"traefik.http.middlewares.Middleware8.headers.stsincludesubdomains":                    "true",
-		"traefik.http.middlewares.Middleware8.headers.stspreload":                              "true",
-		"traefik.http.middlewares.Middleware8.headers.stsseconds":                              "42",
-		"traefik.http.middlewares.Middleware9.ipwhitelist.ipstrategy.depth":                    "42",
-		"traefik.http.middlewares.Middleware9.ipwhitelist.ipstrategy.excludedips":              "foobar, fiibar",
-		"traefik.http.middlewares.Middleware9.ipwhitelist.sourcerange":                         "foobar, fiibar",
-		"traefik.http.middlewares.Middleware10.maxconn.amount":                                 "42",
-		"traefik.http.middlewares.Middleware10.maxconn.extractorfunc":                          "foobar",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.notafter":                "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.notbefore":               "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.sans":                    "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.commonname":      "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.country":         "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.domaincomponent": "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.locality":        "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.organization":    "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.province":        "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.serialnumber":    "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.commonname":       "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.country":          "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.domaincomponent":  "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.locality":         "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.organization":     "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.province":         "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.serialnumber":     "true",
-		"traefik.http.middlewares.Middleware11.passtlsclientcert.pem":                          "true",
-		// TODO: disable temporarily (rateLimit)
-		// "traefik.http.middlewares.Middleware12.ratelimit.extractorfunc":                        "foobar",
-		// "traefik.http.middlewares.Middleware12.ratelimit.rateset.Rate0.average":                "42",
-		// "traefik.http.middlewares.Middleware12.ratelimit.rateset.Rate0.burst":                  "42",
-		// "traefik.http.middlewares.Middleware12.ratelimit.rateset.Rate0.period":                 "42",
-		// "traefik.http.middlewares.Middleware12.ratelimit.rateset.Rate1.average":                "42",
-		// "traefik.http.middlewares.Middleware12.ratelimit.rateset.Rate1.burst":                  "42",
-		// "traefik.http.middlewares.Middleware12.ratelimit.rateset.Rate1.period":                 "42",
-		"traefik.http.middlewares.Middleware13.redirectregex.permanent":      "true",
-		"traefik.http.middlewares.Middleware13.redirectregex.regex":          "foobar",
-		"traefik.http.middlewares.Middleware13.redirectregex.replacement":    "foobar",
-		"traefik.http.middlewares.Middleware13b.redirectscheme.scheme":       "https",
-		"traefik.http.middlewares.Middleware13b.redirectscheme.port":         "80",
-		"traefik.http.middlewares.Middleware13b.redirectscheme.permanent":    "true",
-		"traefik.http.middlewares.Middleware14.replacepath.path":             "foobar",
-		"traefik.http.middlewares.Middleware15.replacepathregex.regex":       "foobar",
-		"traefik.http.middlewares.Middleware15.replacepathregex.replacement": "foobar",
-		"traefik.http.middlewares.Middleware16.retry.attempts":               "42",
-		"traefik.http.middlewares.Middleware17.stripprefix.prefixes":         "foobar, fiibar",
-		"traefik.http.middlewares.Middleware18.stripprefixregex.regex":       "foobar, fiibar",
-		"traefik.http.middlewares.Middleware19.compress":                     "true",
+		"traefik.http.middlewares.Middleware0.addprefix.prefix":                                    "foobar",
+		"traefik.http.middlewares.Middleware1.basicauth.headerfield":                               "foobar",
+		"traefik.http.middlewares.Middleware1.basicauth.realm":                                     "foobar",
+		"traefik.http.middlewares.Middleware1.basicauth.removeheader":                              "true",
+		"traefik.http.middlewares.Middleware1.basicauth.users":                                     "foobar, fiibar",
+		"traefik.http.middlewares.Middleware1.basicauth.usersfile":                                 "foobar",
+		"traefik.http.middlewares.Middleware2.buffering.maxrequestbodybytes":                       "42",
+		"traefik.http.middlewares.Middleware2.buffering.maxresponsebodybytes":                      "42",
+		"traefik.http.middlewares.Middleware2.buffering.memrequestbodybytes":                       "42",
+		"traefik.http.middlewares.Middleware2.buffering.memresponsebodybytes":                      "42",
+		"traefik.http.middlewares.Middleware2.buffering.retryexpression":                           "foobar",
+		"traefik.http.middlewares.Middleware3.chain.middlewares":                                   "foobar, fiibar",
+		"traefik.http.middlewares.Middleware4.circuitbreaker.expression":                           "foobar",
+		"traefik.http.middlewares.Middleware5.digestauth.headerfield":                              "foobar",
+		"traefik.http.middlewares.Middleware5.digestauth.realm":                                    "foobar",
+		"traefik.http.middlewares.Middleware5.digestauth.removeheader":                             "true",
+		"traefik.http.middlewares.Middleware5.digestauth.users":                                    "foobar, fiibar",
+		"traefik.http.middlewares.Middleware5.digestauth.usersfile":                                "foobar",
+		"traefik.http.middlewares.Middleware6.errors.query":                                        "foobar",
+		"traefik.http.middlewares.Middleware6.errors.service":                                      "foobar",
+		"traefik.http.middlewares.Middleware6.errors.status":                                       "foobar, fiibar",
+		"traefik.http.middlewares.Middleware7.forwardauth.address":                                 "foobar",
+		"traefik.http.middlewares.Middleware7.forwardauth.authresponseheaders":                     "foobar, fiibar",
+		"traefik.http.middlewares.Middleware7.forwardauth.tls.ca":                                  "foobar",
+		"traefik.http.middlewares.Middleware7.forwardauth.tls.caoptional":                          "true",
+		"traefik.http.middlewares.Middleware7.forwardauth.tls.cert":                                "foobar",
+		"traefik.http.middlewares.Middleware7.forwardauth.tls.insecureskipverify":                  "true",
+		"traefik.http.middlewares.Middleware7.forwardauth.tls.key":                                 "foobar",
+		"traefik.http.middlewares.Middleware7.forwardauth.trustforwardheader":                      "true",
+		"traefik.http.middlewares.Middleware8.headers.accesscontrolallowcredentials":               "true",
+		"traefik.http.middlewares.Middleware8.headers.allowedhosts":                                "foobar, fiibar",
+		"traefik.http.middlewares.Middleware8.headers.accesscontrolallowheaders":                   "X-foobar, X-fiibar",
+		"traefik.http.middlewares.Middleware8.headers.accesscontrolallowmethods":                   "GET, PUT",
+		"traefik.http.middlewares.Middleware8.headers.accesscontrolalloworigin":                    "foobar",
+		"traefik.http.middlewares.Middleware8.headers.accesscontrolexposeheaders":                  "X-foobar, X-fiibar",
+		"traefik.http.middlewares.Middleware8.headers.accesscontrolmaxage":                         "200",
+		"traefik.http.middlewares.Middleware8.headers.addvaryheader":                               "true",
+		"traefik.http.middlewares.Middleware8.headers.browserxssfilter":                            "true",
+		"traefik.http.middlewares.Middleware8.headers.contentsecuritypolicy":                       "foobar",
+		"traefik.http.middlewares.Middleware8.headers.contenttypenosniff":                          "true",
+		"traefik.http.middlewares.Middleware8.headers.custombrowserxssvalue":                       "foobar",
+		"traefik.http.middlewares.Middleware8.headers.customframeoptionsvalue":                     "foobar",
+		"traefik.http.middlewares.Middleware8.headers.customrequestheaders.name0":                  "foobar",
+		"traefik.http.middlewares.Middleware8.headers.customrequestheaders.name1":                  "foobar",
+		"traefik.http.middlewares.Middleware8.headers.customresponseheaders.name0":                 "foobar",
+		"traefik.http.middlewares.Middleware8.headers.customresponseheaders.name1":                 "foobar",
+		"traefik.http.middlewares.Middleware8.headers.forcestsheader":                              "true",
+		"traefik.http.middlewares.Middleware8.headers.framedeny":                                   "true",
+		"traefik.http.middlewares.Middleware8.headers.hostsproxyheaders":                           "foobar, fiibar",
+		"traefik.http.middlewares.Middleware8.headers.isdevelopment":                               "true",
+		"traefik.http.middlewares.Middleware8.headers.publickey":                                   "foobar",
+		"traefik.http.middlewares.Middleware8.headers.referrerpolicy":                              "foobar",
+		"traefik.http.middlewares.Middleware8.headers.featurepolicy":                               "foobar",
+		"traefik.http.middlewares.Middleware8.headers.sslforcehost":                                "true",
+		"traefik.http.middlewares.Middleware8.headers.sslhost":                                     "foobar",
+		"traefik.http.middlewares.Middleware8.headers.sslproxyheaders.name0":                       "foobar",
+		"traefik.http.middlewares.Middleware8.headers.sslproxyheaders.name1":                       "foobar",
+		"traefik.http.middlewares.Middleware8.headers.sslredirect":                                 "true",
+		"traefik.http.middlewares.Middleware8.headers.ssltemporaryredirect":                        "true",
+		"traefik.http.middlewares.Middleware8.headers.stsincludesubdomains":                        "true",
+		"traefik.http.middlewares.Middleware8.headers.stspreload":                                  "true",
+		"traefik.http.middlewares.Middleware8.headers.stsseconds":                                  "42",
+		"traefik.http.middlewares.Middleware9.ipwhitelist.ipstrategy.depth":                        "42",
+		"traefik.http.middlewares.Middleware9.ipwhitelist.ipstrategy.excludedips":                  "foobar, fiibar",
+		"traefik.http.middlewares.Middleware9.ipwhitelist.sourcerange":                             "foobar, fiibar",
+		"traefik.http.middlewares.Middleware10.inflightreq.amount":                                 "42",
+		"traefik.http.middlewares.Middleware10.inflightreq.sourcecriterion.ipstrategy.depth":       "42",
+		"traefik.http.middlewares.Middleware10.inflightreq.sourcecriterion.ipstrategy.excludedips": "foobar, fiibar",
+		"traefik.http.middlewares.Middleware10.inflightreq.sourcecriterion.requestheadername":      "foobar",
+		"traefik.http.middlewares.Middleware10.inflightreq.sourcecriterion.requesthost":            "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.notafter":                    "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.notbefore":                   "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.sans":                        "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.commonname":          "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.country":             "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.domaincomponent":     "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.locality":            "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.organization":        "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.province":            "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.subject.serialnumber":        "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.commonname":           "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.country":              "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.domaincomponent":      "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.locality":             "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.organization":         "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.province":             "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.info.issuer.serialnumber":         "true",
+		"traefik.http.middlewares.Middleware11.passtlsclientcert.pem":                              "true",
+		"traefik.http.middlewares.Middleware12.ratelimit.average":                                  "42",
+		"traefik.http.middlewares.Middleware12.ratelimit.burst":                                    "42",
+		"traefik.http.middlewares.Middleware12.ratelimit.sourcecriterion.requestheadername":        "foobar",
+		"traefik.http.middlewares.Middleware12.ratelimit.sourcecriterion.requesthost":              "true",
+		"traefik.http.middlewares.Middleware12.ratelimit.sourcecriterion.ipstrategy.depth":         "42",
+		"traefik.http.middlewares.Middleware12.ratelimit.sourcecriterion.ipstrategy.excludedips":   "foobar, foobar",
+		"traefik.http.middlewares.Middleware13.redirectregex.permanent":                            "true",
+		"traefik.http.middlewares.Middleware13.redirectregex.regex":                                "foobar",
+		"traefik.http.middlewares.Middleware13.redirectregex.replacement":                          "foobar",
+		"traefik.http.middlewares.Middleware13b.redirectscheme.scheme":                             "https",
+		"traefik.http.middlewares.Middleware13b.redirectscheme.port":                               "80",
+		"traefik.http.middlewares.Middleware13b.redirectscheme.permanent":                          "true",
+		"traefik.http.middlewares.Middleware14.replacepath.path":                                   "foobar",
+		"traefik.http.middlewares.Middleware15.replacepathregex.regex":                             "foobar",
+		"traefik.http.middlewares.Middleware15.replacepathregex.replacement":                       "foobar",
+		"traefik.http.middlewares.Middleware16.retry.attempts":                                     "42",
+		"traefik.http.middlewares.Middleware17.stripprefix.prefixes":                               "foobar, fiibar",
+		"traefik.http.middlewares.Middleware18.stripprefixregex.regex":                             "foobar, fiibar",
+		"traefik.http.middlewares.Middleware19.compress":                                           "true",
 
 		"traefik.http.routers.Router0.entrypoints": "foobar, fiibar",
 		"traefik.http.routers.Router0.middlewares": "foobar, fiibar",
@@ -273,9 +274,16 @@ func TestDecodeConfiguration(t *testing.T) {
 					},
 				},
 				"Middleware10": {
-					MaxConn: &dynamic.MaxConn{
-						Amount:        42,
-						ExtractorFunc: "foobar",
+					InFlightReq: &dynamic.InFlightReq{
+						Amount: 42,
+						SourceCriterion: &dynamic.SourceCriterion{
+							IPStrategy: &dynamic.IPStrategy{
+								Depth:       42,
+								ExcludedIPs: []string{"foobar", "fiibar"},
+							},
+							RequestHeaderName: "foobar",
+							RequestHost:       true,
+						},
 					},
 				},
 				"Middleware11": {
@@ -306,24 +314,20 @@ func TestDecodeConfiguration(t *testing.T) {
 						},
 					},
 				},
-				// TODO: disable temporarily (rateLimit)
-				// "Middleware12": {
-				// 	RateLimit: &dynamic.RateLimit{
-				// 		RateSet: map[string]*dynamic.Rate{
-				// 			"Rate0": {
-				// 				Period:  types.Duration(42 * time.Second),
-				// 				Average: 42,
-				// 				Burst:   42,
-				// 			},
-				// 			"Rate1": {
-				// 				Period:  types.Duration(42 * time.Second),
-				// 				Average: 42,
-				// 				Burst:   42,
-				// 			},
-				// 		},
-				// 		ExtractorFunc: "foobar",
-				// 	},
-				// },
+				"Middleware12": {
+					RateLimit: &dynamic.RateLimit{
+						Average: 42,
+						Burst:   42,
+						SourceCriterion: &dynamic.SourceCriterion{
+							IPStrategy: &dynamic.IPStrategy{
+								Depth:       42,
+								ExcludedIPs: []string{"foobar", "foobar"},
+							},
+							RequestHeaderName: "foobar",
+							RequestHost:       true,
+						},
+					},
+				},
 				"Middleware13": {
 					RedirectRegex: &dynamic.RedirectRegex{
 						Regex:       "foobar",
@@ -674,9 +678,16 @@ func TestEncodeConfiguration(t *testing.T) {
 					},
 				},
 				"Middleware10": {
-					MaxConn: &dynamic.MaxConn{
-						Amount:        42,
-						ExtractorFunc: "foobar",
+					InFlightReq: &dynamic.InFlightReq{
+						Amount: 42,
+						SourceCriterion: &dynamic.SourceCriterion{
+							IPStrategy: &dynamic.IPStrategy{
+								Depth:       42,
+								ExcludedIPs: []string{"foobar", "fiibar"},
+							},
+							RequestHeaderName: "foobar",
+							RequestHost:       true,
+						},
 					},
 				},
 				"Middleware11": {
@@ -706,24 +717,20 @@ func TestEncodeConfiguration(t *testing.T) {
 						},
 					},
 				},
-				// TODO: disable temporarily (rateLimit)
-				// "Middleware12": {
-				// 	RateLimit: &dynamic.RateLimit{
-				// 		RateSet: map[string]*dynamic.Rate{
-				// 			"Rate0": {
-				// 				Period:  types.Duration(42 * time.Nanosecond),
-				// 				Average: 42,
-				// 				Burst:   42,
-				// 			},
-				// 			"Rate1": {
-				// 				Period:  types.Duration(42 * time.Nanosecond),
-				// 				Average: 42,
-				// 				Burst:   42,
-				// 			},
-				// 		},
-				// 		ExtractorFunc: "foobar",
-				// 	},
-				// },
+				"Middleware12": {
+					RateLimit: &dynamic.RateLimit{
+						Average: 42,
+						Burst:   42,
+						SourceCriterion: &dynamic.SourceCriterion{
+							IPStrategy: &dynamic.IPStrategy{
+								Depth:       42,
+								ExcludedIPs: []string{"foobar", "foobar"},
+							},
+							RequestHeaderName: "foobar",
+							RequestHost:       true,
+						},
+					},
+				},
 				"Middleware13": {
 					RedirectRegex: &dynamic.RedirectRegex{
 						Regex:       "foobar",
@@ -975,112 +982,113 @@ func TestEncodeConfiguration(t *testing.T) {
 	require.NoError(t, err)
 
 	expected := map[string]string{
-		"traefik.HTTP.Middlewares.Middleware0.AddPrefix.Prefix":                                "foobar",
-		"traefik.HTTP.Middlewares.Middleware1.BasicAuth.HeaderField":                           "foobar",
-		"traefik.HTTP.Middlewares.Middleware1.BasicAuth.Realm":                                 "foobar",
-		"traefik.HTTP.Middlewares.Middleware1.BasicAuth.RemoveHeader":                          "true",
-		"traefik.HTTP.Middlewares.Middleware1.BasicAuth.Users":                                 "foobar, fiibar",
-		"traefik.HTTP.Middlewares.Middleware1.BasicAuth.UsersFile":                             "foobar",
-		"traefik.HTTP.Middlewares.Middleware2.Buffering.MaxRequestBodyBytes":                   "42",
-		"traefik.HTTP.Middlewares.Middleware2.Buffering.MaxResponseBodyBytes":                  "42",
-		"traefik.HTTP.Middlewares.Middleware2.Buffering.MemRequestBodyBytes":                   "42",
-		"traefik.HTTP.Middlewares.Middleware2.Buffering.MemResponseBodyBytes":                  "42",
-		"traefik.HTTP.Middlewares.Middleware2.Buffering.RetryExpression":                       "foobar",
-		"traefik.HTTP.Middlewares.Middleware3.Chain.Middlewares":                               "foobar, fiibar",
-		"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.Expression":                       "foobar",
-		"traefik.HTTP.Middlewares.Middleware5.DigestAuth.HeaderField":                          "foobar",
-		"traefik.HTTP.Middlewares.Middleware5.DigestAuth.Realm":                                "foobar",
-		"traefik.HTTP.Middlewares.Middleware5.DigestAuth.RemoveHeader":                         "true",
-		"traefik.HTTP.Middlewares.Middleware5.DigestAuth.Users":                                "foobar, fiibar",
-		"traefik.HTTP.Middlewares.Middleware5.DigestAuth.UsersFile":                            "foobar",
-		"traefik.HTTP.Middlewares.Middleware6.Errors.Query":                                    "foobar",
-		"traefik.HTTP.Middlewares.Middleware6.Errors.Service":                                  "foobar",
-		"traefik.HTTP.Middlewares.Middleware6.Errors.Status":                                   "foobar, fiibar",
-		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.Address":                             "foobar",
-		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.AuthResponseHeaders":                 "foobar, fiibar",
-		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CA":                              "foobar",
-		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CAOptional":                      "true",
-		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Cert":                            "foobar",
-		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.InsecureSkipVerify":              "true",
-		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Key":                             "foobar",
-		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TrustForwardHeader":                  "true",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowCredentials":           "true",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowHeaders":               "X-foobar, X-fiibar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowMethods":               "GET, PUT",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowOrigin":                "foobar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlExposeHeaders":              "X-foobar, X-fiibar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlMaxAge":                     "200",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.AddVaryHeader":                           "true",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.AllowedHosts":                            "foobar, fiibar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.BrowserXSSFilter":                        "true",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.ContentSecurityPolicy":                   "foobar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.ContentTypeNosniff":                      "true",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.CustomBrowserXSSValue":                   "foobar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.CustomFrameOptionsValue":                 "foobar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.CustomRequestHeaders.name0":              "foobar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.CustomRequestHeaders.name1":              "foobar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.CustomResponseHeaders.name0":             "foobar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.CustomResponseHeaders.name1":             "foobar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.ForceSTSHeader":                          "true",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.FrameDeny":                               "true",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.HostsProxyHeaders":                       "foobar, fiibar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.IsDevelopment":                           "true",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.PublicKey":                               "foobar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.ReferrerPolicy":                          "foobar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.FeaturePolicy":                           "foobar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.SSLForceHost":                            "true",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.SSLHost":                                 "foobar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.SSLProxyHeaders.name0":                   "foobar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.SSLProxyHeaders.name1":                   "foobar",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.SSLRedirect":                             "true",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.SSLTemporaryRedirect":                    "true",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.STSIncludeSubdomains":                    "true",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.STSPreload":                              "true",
-		"traefik.HTTP.Middlewares.Middleware8.Headers.STSSeconds":                              "42",
-		"traefik.HTTP.Middlewares.Middleware9.IPWhiteList.IPStrategy.Depth":                    "42",
-		"traefik.HTTP.Middlewares.Middleware9.IPWhiteList.IPStrategy.ExcludedIPs":              "foobar, fiibar",
-		"traefik.HTTP.Middlewares.Middleware9.IPWhiteList.SourceRange":                         "foobar, fiibar",
-		"traefik.HTTP.Middlewares.Middleware10.MaxConn.Amount":                                 "42",
-		"traefik.HTTP.Middlewares.Middleware10.MaxConn.ExtractorFunc":                          "foobar",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.NotAfter":                "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.NotBefore":               "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Sans":                    "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Country":         "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Province":        "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Locality":        "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Organization":    "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.CommonName":      "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.SerialNumber":    "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.DomainComponent": "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Country":          "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Province":         "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Locality":         "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Organization":     "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.CommonName":       "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.SerialNumber":     "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.DomainComponent":  "true",
-		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.PEM":                          "true",
-		// TODO: disable temporarily (rateLimit)
-		// "traefik.HTTP.Middlewares.Middleware12.RateLimit.ExtractorFunc":                        "foobar",
-		// "traefik.HTTP.Middlewares.Middleware12.RateLimit.RateSet.Rate0.Average":                "42",
-		// "traefik.HTTP.Middlewares.Middleware12.RateLimit.RateSet.Rate0.Burst":                  "42",
-		// "traefik.HTTP.Middlewares.Middleware12.RateLimit.RateSet.Rate0.Period":                 "42",
-		// "traefik.HTTP.Middlewares.Middleware12.RateLimit.RateSet.Rate1.Average":                "42",
-		// "traefik.HTTP.Middlewares.Middleware12.RateLimit.RateSet.Rate1.Burst":                  "42",
-		// "traefik.HTTP.Middlewares.Middleware12.RateLimit.RateSet.Rate1.Period":                 "42",
-		"traefik.HTTP.Middlewares.Middleware13.RedirectRegex.Regex":          "foobar",
-		"traefik.HTTP.Middlewares.Middleware13.RedirectRegex.Replacement":    "foobar",
-		"traefik.HTTP.Middlewares.Middleware13.RedirectRegex.Permanent":      "true",
-		"traefik.HTTP.Middlewares.Middleware13b.RedirectScheme.Scheme":       "https",
-		"traefik.HTTP.Middlewares.Middleware13b.RedirectScheme.Port":         "80",
-		"traefik.HTTP.Middlewares.Middleware13b.RedirectScheme.Permanent":    "true",
-		"traefik.HTTP.Middlewares.Middleware14.ReplacePath.Path":             "foobar",
-		"traefik.HTTP.Middlewares.Middleware15.ReplacePathRegex.Regex":       "foobar",
-		"traefik.HTTP.Middlewares.Middleware15.ReplacePathRegex.Replacement": "foobar",
-		"traefik.HTTP.Middlewares.Middleware16.Retry.Attempts":               "42",
-		"traefik.HTTP.Middlewares.Middleware17.StripPrefix.Prefixes":         "foobar, fiibar",
-		"traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex":       "foobar, fiibar",
-		"traefik.HTTP.Middlewares.Middleware19.Compress":                     "true",
+		"traefik.HTTP.Middlewares.Middleware0.AddPrefix.Prefix":                                    "foobar",
+		"traefik.HTTP.Middlewares.Middleware1.BasicAuth.HeaderField":                               "foobar",
+		"traefik.HTTP.Middlewares.Middleware1.BasicAuth.Realm":                                     "foobar",
+		"traefik.HTTP.Middlewares.Middleware1.BasicAuth.RemoveHeader":                              "true",
+		"traefik.HTTP.Middlewares.Middleware1.BasicAuth.Users":                                     "foobar, fiibar",
+		"traefik.HTTP.Middlewares.Middleware1.BasicAuth.UsersFile":                                 "foobar",
+		"traefik.HTTP.Middlewares.Middleware2.Buffering.MaxRequestBodyBytes":                       "42",
+		"traefik.HTTP.Middlewares.Middleware2.Buffering.MaxResponseBodyBytes":                      "42",
+		"traefik.HTTP.Middlewares.Middleware2.Buffering.MemRequestBodyBytes":                       "42",
+		"traefik.HTTP.Middlewares.Middleware2.Buffering.MemResponseBodyBytes":                      "42",
+		"traefik.HTTP.Middlewares.Middleware2.Buffering.RetryExpression":                           "foobar",
+		"traefik.HTTP.Middlewares.Middleware3.Chain.Middlewares":                                   "foobar, fiibar",
+		"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.Expression":                           "foobar",
+		"traefik.HTTP.Middlewares.Middleware5.DigestAuth.HeaderField":                              "foobar",
+		"traefik.HTTP.Middlewares.Middleware5.DigestAuth.Realm":                                    "foobar",
+		"traefik.HTTP.Middlewares.Middleware5.DigestAuth.RemoveHeader":                             "true",
+		"traefik.HTTP.Middlewares.Middleware5.DigestAuth.Users":                                    "foobar, fiibar",
+		"traefik.HTTP.Middlewares.Middleware5.DigestAuth.UsersFile":                                "foobar",
+		"traefik.HTTP.Middlewares.Middleware6.Errors.Query":                                        "foobar",
+		"traefik.HTTP.Middlewares.Middleware6.Errors.Service":                                      "foobar",
+		"traefik.HTTP.Middlewares.Middleware6.Errors.Status":                                       "foobar, fiibar",
+		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.Address":                                 "foobar",
+		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.AuthResponseHeaders":                     "foobar, fiibar",
+		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CA":                                  "foobar",
+		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CAOptional":                          "true",
+		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Cert":                                "foobar",
+		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.InsecureSkipVerify":                  "true",
+		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Key":                                 "foobar",
+		"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TrustForwardHeader":                      "true",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowCredentials":               "true",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowHeaders":                   "X-foobar, X-fiibar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowMethods":                   "GET, PUT",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowOrigin":                    "foobar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlExposeHeaders":                  "X-foobar, X-fiibar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlMaxAge":                         "200",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.AddVaryHeader":                               "true",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.AllowedHosts":                                "foobar, fiibar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.BrowserXSSFilter":                            "true",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.ContentSecurityPolicy":                       "foobar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.ContentTypeNosniff":                          "true",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.CustomBrowserXSSValue":                       "foobar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.CustomFrameOptionsValue":                     "foobar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.CustomRequestHeaders.name0":                  "foobar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.CustomRequestHeaders.name1":                  "foobar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.CustomResponseHeaders.name0":                 "foobar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.CustomResponseHeaders.name1":                 "foobar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.ForceSTSHeader":                              "true",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.FrameDeny":                                   "true",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.HostsProxyHeaders":                           "foobar, fiibar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.IsDevelopment":                               "true",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.PublicKey":                                   "foobar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.ReferrerPolicy":                              "foobar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.FeaturePolicy":                               "foobar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.SSLForceHost":                                "true",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.SSLHost":                                     "foobar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.SSLProxyHeaders.name0":                       "foobar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.SSLProxyHeaders.name1":                       "foobar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.SSLRedirect":                                 "true",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.SSLTemporaryRedirect":                        "true",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.STSIncludeSubdomains":                        "true",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.STSPreload":                                  "true",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.STSSeconds":                                  "42",
+		"traefik.HTTP.Middlewares.Middleware9.IPWhiteList.IPStrategy.Depth":                        "42",
+		"traefik.HTTP.Middlewares.Middleware9.IPWhiteList.IPStrategy.ExcludedIPs":                  "foobar, fiibar",
+		"traefik.HTTP.Middlewares.Middleware9.IPWhiteList.SourceRange":                             "foobar, fiibar",
+		"traefik.HTTP.Middlewares.Middleware10.InFlightReq.Amount":                                 "42",
+		"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.IPStrategy.Depth":       "42",
+		"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.IPStrategy.ExcludedIPs": "foobar, fiibar",
+		"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.RequestHeaderName":      "foobar",
+		"traefik.HTTP.Middlewares.Middleware10.InFlightReq.SourceCriterion.RequestHost":            "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.NotAfter":                    "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.NotBefore":                   "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Sans":                        "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Country":             "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Province":            "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Locality":            "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.Organization":        "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.CommonName":          "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.SerialNumber":        "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Subject.DomainComponent":     "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Country":              "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Province":             "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Locality":             "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.Organization":         "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.CommonName":           "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.SerialNumber":         "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.Info.Issuer.DomainComponent":      "true",
+		"traefik.HTTP.Middlewares.Middleware11.PassTLSClientCert.PEM":                              "true",
+		"traefik.HTTP.Middlewares.Middleware12.RateLimit.Average":                                  "42",
+		"traefik.HTTP.Middlewares.Middleware12.RateLimit.Burst":                                    "42",
+		"traefik.HTTP.Middlewares.Middleware12.RateLimit.SourceCriterion.RequestHeaderName":        "foobar",
+		"traefik.HTTP.Middlewares.Middleware12.RateLimit.SourceCriterion.RequestHost":              "true",
+		"traefik.HTTP.Middlewares.Middleware12.RateLimit.SourceCriterion.IPStrategy.Depth":         "42",
+		"traefik.HTTP.Middlewares.Middleware12.RateLimit.SourceCriterion.IPStrategy.ExcludedIPs":   "foobar, foobar",
+		"traefik.HTTP.Middlewares.Middleware13.RedirectRegex.Regex":                                "foobar",
+		"traefik.HTTP.Middlewares.Middleware13.RedirectRegex.Replacement":                          "foobar",
+		"traefik.HTTP.Middlewares.Middleware13.RedirectRegex.Permanent":                            "true",
+		"traefik.HTTP.Middlewares.Middleware13b.RedirectScheme.Scheme":                             "https",
+		"traefik.HTTP.Middlewares.Middleware13b.RedirectScheme.Port":                               "80",
+		"traefik.HTTP.Middlewares.Middleware13b.RedirectScheme.Permanent":                          "true",
+		"traefik.HTTP.Middlewares.Middleware14.ReplacePath.Path":                                   "foobar",
+		"traefik.HTTP.Middlewares.Middleware15.ReplacePathRegex.Regex":                             "foobar",
+		"traefik.HTTP.Middlewares.Middleware15.ReplacePathRegex.Replacement":                       "foobar",
+		"traefik.HTTP.Middlewares.Middleware16.Retry.Attempts":                                     "42",
+		"traefik.HTTP.Middlewares.Middleware17.StripPrefix.Prefixes":                               "foobar, fiibar",
+		"traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex":                             "foobar, fiibar",
+		"traefik.HTTP.Middlewares.Middleware19.Compress":                                           "true",
 
 		"traefik.HTTP.Routers.Router0.EntryPoints": "foobar, fiibar",
 		"traefik.HTTP.Routers.Router0.Middlewares": "foobar, fiibar",
diff --git a/pkg/ip/strategy_test.go b/pkg/ip/strategy_test.go
index 54fe64837..8a05acd63 100644
--- a/pkg/ip/strategy_test.go
+++ b/pkg/ip/strategy_test.go
@@ -16,7 +16,7 @@ func TestRemoteAddrStrategy_GetIP(t *testing.T) {
 	}{
 		{
 			desc:     "Use RemoteAddr",
-			expected: "192.0.2.1:1234",
+			expected: "192.0.2.1",
 		},
 	}
 
diff --git a/pkg/middlewares/ratelimiter/rate_limiter_test.go b/pkg/middlewares/ratelimiter/rate_limiter_test.go
new file mode 100644
index 000000000..94fcedba8
--- /dev/null
+++ b/pkg/middlewares/ratelimiter/rate_limiter_test.go
@@ -0,0 +1,160 @@
+package ratelimiter
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+	"time"
+
+	"github.com/containous/traefik/v2/pkg/config/dynamic"
+	"github.com/containous/traefik/v2/pkg/testhelpers"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/vulcand/oxy/utils"
+)
+
+func TestNewRateLimiter(t *testing.T) {
+	testCases := []struct {
+		desc             string
+		config           dynamic.RateLimit
+		expectedMaxDelay time.Duration
+		expectedSourceIP string
+	}{
+		{
+			desc: "maxDelay computation",
+			config: dynamic.RateLimit{
+				Average: 200,
+				Burst:   10,
+			},
+			expectedMaxDelay: 2500 * time.Microsecond,
+		},
+		{
+			desc: "default SourceMatcher is remote address ip strategy",
+			config: dynamic.RateLimit{
+				Average: 200,
+				Burst:   10,
+			},
+			expectedSourceIP: "127.0.0.1",
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+			h, err := New(context.Background(), next, test.config, "rate-limiter")
+			require.NoError(t, err)
+
+			rtl, _ := h.(*rateLimiter)
+			if test.expectedMaxDelay != 0 {
+				assert.Equal(t, test.expectedMaxDelay, rtl.maxDelay)
+			}
+
+			if test.expectedSourceIP != "" {
+				extractor, ok := rtl.sourceMatcher.(utils.ExtractorFunc)
+				require.True(t, ok, "Not an ExtractorFunc")
+
+				req := http.Request{
+					RemoteAddr: fmt.Sprintf("%s:1234", test.expectedSourceIP),
+				}
+
+				ip, _, err := extractor(&req)
+				assert.NoError(t, err)
+				assert.Equal(t, test.expectedSourceIP, ip)
+			}
+		})
+	}
+}
+
+func TestRateLimit(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		config   dynamic.RateLimit
+		reqCount int
+	}{
+		{
+			desc: "Average is respected",
+			config: dynamic.RateLimit{
+				Average: 100,
+				Burst:   1,
+			},
+			reqCount: 200,
+		},
+		{
+			desc: "Burst is taken into account",
+			config: dynamic.RateLimit{
+				Average: 100,
+				Burst:   200,
+			},
+			reqCount: 300,
+		},
+		{
+			desc: "Zero average ==> no rate limiting",
+			config: dynamic.RateLimit{
+				Average: 0,
+				Burst:   1,
+			},
+			reqCount: 100,
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			reqCount := 0
+			next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				reqCount++
+			})
+
+			h, err := New(context.Background(), next, test.config, "rate-limiter")
+			require.NoError(t, err)
+
+			start := time.Now()
+			for {
+				if reqCount >= test.reqCount {
+					break
+				}
+
+				req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
+				req.RemoteAddr = "127.0.0.1:1234"
+				w := httptest.NewRecorder()
+
+				h.ServeHTTP(w, req)
+				// TODO(mpl): predict and count the 200 VS the 429?
+			}
+
+			stop := time.Now()
+			elapsed := stop.Sub(start)
+			if test.config.Average == 0 {
+				if elapsed > time.Millisecond {
+					t.Fatalf("rate should not have been limited, but: %d requests in %v", reqCount, elapsed)
+				}
+				return
+			}
+
+			// Assume allowed burst is initially consumed in an infinitesimal period of time
+			var expectedDuration time.Duration
+			if test.config.Average != 0 {
+				expectedDuration = time.Duration((int64(test.reqCount)-test.config.Burst+1)/test.config.Average) * time.Second
+			}
+
+			// Allow for a 2% leeway
+			minDuration := expectedDuration * 98 / 100
+			maxDuration := expectedDuration * 102 / 100
+
+			if elapsed < minDuration {
+				t.Fatalf("rate was faster than expected: %d requests in %v", reqCount, elapsed)
+			}
+			if elapsed > maxDuration {
+				t.Fatalf("rate was slower than expected: %d requests in %v", reqCount, elapsed)
+			}
+		})
+	}
+}
diff --git a/pkg/provider/docker/config_test.go b/pkg/provider/docker/config_test.go
index 61636b474..c7c9bbdb5 100644
--- a/pkg/provider/docker/config_test.go
+++ b/pkg/provider/docker/config_test.go
@@ -1007,13 +1007,13 @@ func Test_buildConfiguration(t *testing.T) {
 			},
 		},
 		{
-			desc: "one container with MaxConn in label (default value)",
+			desc: "one container with InFlightReq in label (default value)",
 			containers: []dockerData{
 				{
 					ServiceName: "Test",
 					Name:        "Test",
 					Labels: map[string]string{
-						"traefik.http.middlewares.Middleware1.maxconn.amount": "42",
+						"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
 					},
 					NetworkSettings: networkSettings{
 						Ports: nat.PortMap{
@@ -1054,9 +1054,11 @@ func Test_buildConfiguration(t *testing.T) {
 					},
 					Middlewares: map[string]*dynamic.Middleware{
 						"Middleware1": {
-							MaxConn: &dynamic.MaxConn{
-								Amount:        42,
-								ExtractorFunc: "request.host",
+							InFlightReq: &dynamic.InFlightReq{
+								Amount: 42,
+								SourceCriterion: &dynamic.SourceCriterion{
+									RequestHost: true,
+								},
 							},
 						},
 					},
@@ -1071,7 +1073,7 @@ func Test_buildConfiguration(t *testing.T) {
 					ServiceName: "Test",
 					Name:        "Test",
 					Labels: map[string]string{
-						"traefik.http.middlewares.Middleware1.maxconn.amount": "42",
+						"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
 					},
 					NetworkSettings: networkSettings{
 						Ports: nat.PortMap{
@@ -1090,7 +1092,7 @@ func Test_buildConfiguration(t *testing.T) {
 					ServiceName: "Test",
 					Name:        "Test",
 					Labels: map[string]string{
-						"traefik.http.middlewares.Middleware1.maxconn.amount": "42",
+						"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
 					},
 					NetworkSettings: networkSettings{
 						Ports: nat.PortMap{
@@ -1119,9 +1121,11 @@ func Test_buildConfiguration(t *testing.T) {
 					},
 					Middlewares: map[string]*dynamic.Middleware{
 						"Middleware1": {
-							MaxConn: &dynamic.MaxConn{
-								Amount:        42,
-								ExtractorFunc: "request.host",
+							InFlightReq: &dynamic.InFlightReq{
+								Amount: 42,
+								SourceCriterion: &dynamic.SourceCriterion{
+									RequestHost: true,
+								},
 							},
 						},
 					},
@@ -1151,7 +1155,7 @@ func Test_buildConfiguration(t *testing.T) {
 					ServiceName: "Test",
 					Name:        "Test",
 					Labels: map[string]string{
-						"traefik.http.middlewares.Middleware1.maxconn.amount": "42",
+						"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
 					},
 					NetworkSettings: networkSettings{
 						Ports: nat.PortMap{
@@ -1170,7 +1174,7 @@ func Test_buildConfiguration(t *testing.T) {
 					ServiceName: "Test",
 					Name:        "Test",
 					Labels: map[string]string{
-						"traefik.http.middlewares.Middleware1.maxconn.amount": "41",
+						"traefik.http.middlewares.Middleware1.inflightreq.amount": "41",
 					},
 					NetworkSettings: networkSettings{
 						Ports: nat.PortMap{
@@ -1224,7 +1228,7 @@ func Test_buildConfiguration(t *testing.T) {
 					ServiceName: "Test",
 					Name:        "Test",
 					Labels: map[string]string{
-						"traefik.http.middlewares.Middleware1.maxconn.amount": "42",
+						"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
 					},
 					NetworkSettings: networkSettings{
 						Ports: nat.PortMap{
@@ -1243,7 +1247,7 @@ func Test_buildConfiguration(t *testing.T) {
 					ServiceName: "Test",
 					Name:        "Test",
 					Labels: map[string]string{
-						"traefik.http.middlewares.Middleware1.maxconn.amount": "41",
+						"traefik.http.middlewares.Middleware1.inflightreq.amount": "41",
 					},
 					NetworkSettings: networkSettings{
 						Ports: nat.PortMap{
@@ -1262,7 +1266,7 @@ func Test_buildConfiguration(t *testing.T) {
 					ServiceName: "Test",
 					Name:        "Test",
 					Labels: map[string]string{
-						"traefik.http.middlewares.Middleware1.maxconn.amount": "40",
+						"traefik.http.middlewares.Middleware1.inflightreq.amount": "40",
 					},
 					NetworkSettings: networkSettings{
 						Ports: nat.PortMap{
@@ -1809,7 +1813,7 @@ func Test_buildConfiguration(t *testing.T) {
 					ServiceName: "Test",
 					Name:        "Test",
 					Labels: map[string]string{
-						"traefik.http.middlewares.Middleware1.maxconn.amount": "42",
+						"traefik.http.middlewares.Middleware1.inflightreq.amount": "42",
 					},
 					NetworkSettings: networkSettings{
 						Ports: nat.PortMap{},
diff --git a/pkg/provider/marathon/config_test.go b/pkg/provider/marathon/config_test.go
index 8bc35e958..296490f08 100644
--- a/pkg/provider/marathon/config_test.go
+++ b/pkg/provider/marathon/config_test.go
@@ -595,13 +595,13 @@ func TestBuildConfiguration(t *testing.T) {
 					appID("/app"),
 					appPorts(80, 81),
 					withTasks(localhostTask(taskPorts(80, 81))),
-					withLabel("traefik.http.middlewares.Middleware1.maxconn.amount", "42"),
+					withLabel("traefik.http.middlewares.Middleware1.inflightreq.amount", "42"),
 				),
 				application(
 					appID("/app2"),
 					appPorts(80, 81),
 					withTasks(localhostTask(taskPorts(80, 81))),
-					withLabel("traefik.http.middlewares.Middleware1.maxconn.amount", "42"),
+					withLabel("traefik.http.middlewares.Middleware1.inflightreq.amount", "42"),
 				)),
 			expected: &dynamic.Configuration{
 				TCP: &dynamic.TCPConfiguration{
@@ -621,9 +621,11 @@ func TestBuildConfiguration(t *testing.T) {
 					},
 					Middlewares: map[string]*dynamic.Middleware{
 						"Middleware1": {
-							MaxConn: &dynamic.MaxConn{
-								Amount:        42,
-								ExtractorFunc: "request.host",
+							InFlightReq: &dynamic.InFlightReq{
+								Amount: 42,
+								SourceCriterion: &dynamic.SourceCriterion{
+									RequestHost: true,
+								},
 							},
 						},
 					},
@@ -659,13 +661,13 @@ func TestBuildConfiguration(t *testing.T) {
 					appID("/app"),
 					appPorts(80, 81),
 					withTasks(localhostTask(taskPorts(80, 81))),
-					withLabel("traefik.http.middlewares.Middleware1.maxconn.amount", "42"),
+					withLabel("traefik.http.middlewares.Middleware1.inflightreq.amount", "42"),
 				),
 				application(
 					appID("/app2"),
 					appPorts(80, 81),
 					withTasks(localhostTask(taskPorts(80, 81))),
-					withLabel("traefik.http.middlewares.Middleware1.maxconn.amount", "41"),
+					withLabel("traefik.http.middlewares.Middleware1.inflightreq.amount", "41"),
 				)),
 			expected: &dynamic.Configuration{
 				TCP: &dynamic.TCPConfiguration{