Repository: traefik/traefik (language: go)

Issue:
some TCP services with proxyProtocol.trustedIPs broken in 3.0.0-rc1
### Welcome!

- [X] Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any.
- [X] Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any.

### What did you do?

I expose a mariadb instance from Traefik. It's running on a Nomad cluster, and the dynamic config is the consul catalog. I use a dedicated entrypoint on Traefik. The particularity is that this mariadb is reached through the consul service mesh. The service is exposed using these tags

```
        "traefik.enable=true",
        "traefik.tcp.routers.mariadb.entrypoints=mariadb",
        "traefik.tcp.routers.mariadb.rule=HostSNI(`*`)"
```

With Traefik 3.0.0-beta5, everything is working nicely. With rc1, I can't connect to my mariadb service (timeout. Traefik is responding to the TCP connection, but it doesn't seem to route it through the service mesh, mariadb sees nothing)


### What did you see instead?

Timeout when trying to connect to my mariadb service. Only TCP services seem affected, I expose a lot of other HTTP services through the mesh without any issue.

### What version of Traefik are you using?

```
Version:      3.0.0-rc1
Codename:     cheddar
Go version:   go1.22.0
Built:        2024-02-13T13:41:20Z
OS/Arch:      linux/amd64
```


### What is your environment & configuration?

I have this in the static config of Traefik
```yaml
providers:
  consulCatalog:
    prefix: traefik
    exposedByDefault: False
    connectAware: True
    connectByDefault: True
[...]
```
The entrypoint is configured like this
```yaml
entryPoints:
  mariadb:
    address: ":3306"
    proxyProtocol:
      trustedIPs:
        - 10.99.9.1
    transport:
      lifeCycle:
        requestAcceptGraceTimeout: 4
```


### If applicable, please paste the log output in DEBUG level

_No response_


Discussion / hints:
Forgot to add : even with rc1, both the router and the service appear in Traefik dashboard as if everything were OK. Only the routing is broken
mmm, after some more debugging, the problem is not with the service mesh support. But with proxyProtocol.trustedIPs on the entrypoint (I set this as 10.99.9.1 is my HAProxy which can expose services from the outside, working as an L4 proxy, so I need the proxyProtocol support to get the real client IP).

Not sure what's special about it, because the same proxyProtocol.trustedIPs is also used on other entrypoints (like one for a postgres service, and several http services), but removing it makes my mariadb instance accessible again
Hello @dani,

Thanks for reporting this!

We are unsure about a few things to be able to reproduce the issue:
> mmm, after some more debugging, the problem is not with the service mesh support. But with proxyProtocol.trustedIPs on the entrypoint (I set this as 10.99.9.1 is my HAProxy which can expose services from the outside, working as an L4 proxy, so I need the proxyProtocol support to get the real client IP).

When you are trying to connect to MariaDB is this through HAPRoxy?

> Not sure what's special about it, because the same proxyProtocol.trustedIPs is also used on other entrypoints (like one for a postgres service, and several http services), but removing it makes my mariadb instance accessible again

Did you remove the whole proxyProtocol configuration or just the `trustedIPs` configuration?

Can you please share the full DEBUG logs of Traefik when reproducing the problem?
Ok, so here are some more info
- I encounter the issue when I connect directly, not using HAProxy (this particular service is not yet exposed by HAProxy, I just configure all my entrypoints like this so if I need to expose it, everything is ready)
- trustedIPs is the only attribute under proxyProtocol, so I have to remove the whole proxyProtocol node (as specifying an empty node as proxyProtocol: {} is invalid and Traefik fails to start)
- The only logs in DEBUG are (filtered, as this Traefik handles quite a lot of other, unrelated trafic)
```
2024-02-19T16:15:25+01:00 DBG github.com/traefik/traefik/v3/pkg/middlewares/ipallowlist/ip_allowlist.go:85 > Accepting IP 192.168.7.32 middlewareName=ip-trusted@file middlewareType=IPAllowLister
2024-02-19T16:15:26+01:00 DBG github.com/traefik/traefik/v3/pkg/tcp/proxy.go:41 > Handling TCP connection address=10.99.9.11:23953 remoteAddr=10.99.20.2:35772
2024-02-19T16:15:31+01:00 DBG github.com/traefik/traefik/v3/pkg/server/server_entrypoint_tcp.go:452 > IP 10.99.20.2 is not in trusted IPs list, ignoring ProxyProtocol Headers and bypass connection entryPointName=mariadb
```

Note : 
- I did set a IPAllowList middleware on this service (omitted when I opened the issue because it's not relevant, the issue is the same with or without this middleware)
- 10.99.20.2 is the IP of the client trying to connect (and getting a timeout)
- 10.99.9.11:23953 is the correct address for the backend service (IP:port of the envoy proxy in the service mesh)

Thanks for your feedback!

We are able to reproduce the problem, configuring the [`readTimout`](https://doc.traefik.io/traefik/v3.0/routing/entrypoints/#respondingtimeouts) option on the entryPoint should do the trick.

Stay tuned, we are working on a fix.

Fix patch:
diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md
index 0f3114fc30..0a80cf43dc 100644
--- a/docs/content/migration/v2.md
+++ b/docs/content/migration/v2.md
@@ -577,3 +577,35 @@ the maximum user-defined router priority value is:
 
 - `(MaxInt32 - 1000)` for 32-bit platforms,
 - `(MaxInt64 - 1000)` for 64-bit platforms.
+
+### <EntryPoint>.Transport.RespondingTimeouts.<Timeout>
+
+Starting with `v2.11.1` the following timeout options are deprecated:
+
+- `<entryPoint>.transport.respondingTimeouts.readTimeout`
+- `<entryPoint>.transport.respondingTimeouts.writeTimeout`
+- `<entryPoint>.transport.respondingTimeouts.idleTimeout`
+
+They have been replaced by:
+
+- `<entryPoint>.transport.respondingTimeouts.http.readTimeout`
+- `<entryPoint>.transport.respondingTimeouts.http.writeTimeout`
+- `<entryPoint>.transport.respondingTimeouts.http.idleTimeout`
+
+### <EntryPoint>.Transport.RespondingTimeouts.TCP.LingeringTimeout
+
+Starting with `v2.11.1` a new `lingeringTimeout` entryPoints option has been introduced, with a default value of 2s.
+
+The lingering timeout defines the maximum duration between each TCP read operation on the connection.
+As a layer 4 timeout, it applies during HTTP handling but respects the configured HTTP server `readTimeout`.
+
+This change avoids Traefik instances with the default configuration hanging while waiting for bytes to be read on the connection.
+
+We suggest to adapt this value accordingly to your situation.
+The new default value is purposely narrowed and can close the connection too early.
+
+Increasing the `lingeringTimeout` value could be the solution notably if you are dealing with the following errors:
+
+- TCP: `Error while handling TCP connection: readfrom tcp X.X.X.X:X->X.X.X.X:X: read tcp X.X.X.X:X->X.X.X.X:X: i/o timeout`
+- HTTP: `'499 Client Closed Request' caused by: context canceled`
+- HTTP: `ReverseProxy read error during body copy: read tcp X.X.X.X:X->X.X.X.X:X: use of closed network connection`
diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md
index 60a8c6513f..e3ae10e2a4 100644
--- a/docs/content/reference/static-configuration/cli-ref.md
+++ b/docs/content/reference/static-configuration/cli-ref.md
@@ -183,15 +183,27 @@ Duration to give active requests a chance to finish before Traefik stops. (Defau
 `--entrypoints.<name>.transport.lifecycle.requestacceptgracetimeout`:  
 Duration to keep accepting requests before Traefik initiates the graceful shutdown procedure. (Default: ```0```)
 
-`--entrypoints.<name>.transport.respondingtimeouts.idletimeout`:  
+`--entrypoints.<name>.transport.respondingtimeouts.http.idletimeout`:  
 IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set. (Default: ```180```)
 
-`--entrypoints.<name>.transport.respondingtimeouts.readtimeout`:  
+`--entrypoints.<name>.transport.respondingtimeouts.http.readtimeout`:  
 ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set. (Default: ```0```)
 
-`--entrypoints.<name>.transport.respondingtimeouts.writetimeout`:  
+`--entrypoints.<name>.transport.respondingtimeouts.http.writetimeout`:  
 WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```)
 
+`--entrypoints.<name>.transport.respondingtimeouts.idletimeout`:  
+(Deprecated) IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set. (Default: ```0```)
+
+`--entrypoints.<name>.transport.respondingtimeouts.readtimeout`:  
+(Deprecated) ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set. (Default: ```0```)
+
+`--entrypoints.<name>.transport.respondingtimeouts.tcp.lingeringtimeout`:  
+LingeringTimeout is the maximum duration between each TCP read operation on the connection. If zero, no timeout is set. (Default: ```2```)
+
+`--entrypoints.<name>.transport.respondingtimeouts.writetimeout`:  
+(Deprecated) WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```)
+
 `--entrypoints.<name>.udp.timeout`:  
 Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```)
 
diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md
index 849601f63c..9ae961694f 100644
--- a/docs/content/reference/static-configuration/env-ref.md
+++ b/docs/content/reference/static-configuration/env-ref.md
@@ -183,15 +183,27 @@ Duration to give active requests a chance to finish before Traefik stops. (Defau
 `TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_LIFECYCLE_REQUESTACCEPTGRACETIMEOUT`:  
 Duration to keep accepting requests before Traefik initiates the graceful shutdown procedure. (Default: ```0```)
 
-`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_IDLETIMEOUT`:  
+`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_HTTP_IDLETIMEOUT`:  
 IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set. (Default: ```180```)
 
-`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_READTIMEOUT`:  
+`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_HTTP_READTIMEOUT`:  
 ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set. (Default: ```0```)
 
-`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_WRITETIMEOUT`:  
+`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_HTTP_WRITETIMEOUT`:  
 WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```)
 
+`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_IDLETIMEOUT`:  
+(Deprecated) IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set. (Default: ```0```)
+
+`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_READTIMEOUT`:  
+(Deprecated) ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set. (Default: ```0```)
+
+`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_TCP_LINGERINGTIMEOUT`:  
+LingeringTimeout is the maximum duration between each TCP read operation on the connection. If zero, no timeout is set. (Default: ```2```)
+
+`TRAEFIK_ENTRYPOINTS_<NAME>_TRANSPORT_RESPONDINGTIMEOUTS_WRITETIMEOUT`:  
+(Deprecated) WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. (Default: ```0```)
+
 `TRAEFIK_ENTRYPOINTS_<NAME>_UDP_TIMEOUT`:  
 Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```)
 
diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml
index 42c4e6fdf1..bb402467cd 100644
--- a/docs/content/reference/static-configuration/file.toml
+++ b/docs/content/reference/static-configuration/file.toml
@@ -26,6 +26,12 @@
         readTimeout = "42s"
         writeTimeout = "42s"
         idleTimeout = "42s"
+        [entryPoints.EntryPoint0.transport.respondingTimeouts.http]
+          readTimeout = "42s"
+          writeTimeout = "42s"
+          idleTimeout = "42s"
+        [entryPoints.EntryPoint0.transport.respondingTimeouts.tcp]
+          lingeringTimeout = "42s"
     [entryPoints.EntryPoint0.proxyProtocol]
       insecure = true
       trustedIPs = ["foobar", "foobar"]
diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml
index abb8e05d88..d5265aa1d9 100644
--- a/docs/content/reference/static-configuration/file.yaml
+++ b/docs/content/reference/static-configuration/file.yaml
@@ -24,6 +24,12 @@ entryPoints:
         readTimeout: 42s
         writeTimeout: 42s
         idleTimeout: 42s
+        http:
+          readTimeout: 42s
+          writeTimeout: 42s
+          idleTimeout: 42s
+        tcp:
+          lingeringTimeout: 42s
       keepAliveMaxTime: 42s
       keepAliveMaxRequests: 42
     proxyProtocol:
diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md
index 70ce11cf5f..c6a79ba8e7 100644
--- a/docs/content/routing/entrypoints.md
+++ b/docs/content/routing/entrypoints.md
@@ -397,10 +397,11 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward
 
 #### `respondingTimeouts`
 
-`respondingTimeouts` are timeouts for incoming requests to the Traefik instance.
-Setting them has no effect for UDP entryPoints.
+##### `http`
 
-??? info "`transport.respondingTimeouts.readTimeout`"
+`respondingTimeouts.http` are timeouts for incoming requests to the Traefik instance.
+
+??? info "`transport.respondingTimeouts.http.readTimeout`"
 
     _Optional, Default=0s_
 
@@ -417,7 +418,8 @@ Setting them has no effect for UDP entryPoints.
         address: ":8888"
         transport:
           respondingTimeouts:
-            readTimeout: 42
+            http:
+              readTimeout: 42
     ```
 
     ```toml tab="File (TOML)"
@@ -425,18 +427,17 @@ Setting them has no effect for UDP entryPoints.
     [entryPoints]
       [entryPoints.name]
         address = ":8888"
-        [entryPoints.name.transport]
-          [entryPoints.name.transport.respondingTimeouts]
-            readTimeout = 42
+        [entryPoints.name.transport.respondingTimeouts.http]
+          readTimeout = 42
     ```
 
     ```bash tab="CLI"
     ## Static configuration
     --entryPoints.name.address=:8888
-    --entryPoints.name.transport.respondingTimeouts.readTimeout=42
+    --entryPoints.name.transport.respondingTimeouts.http.readTimeout=42
     ```
 
-??? info "`transport.respondingTimeouts.writeTimeout`"
+??? info "`transport.respondingTimeouts.http.writeTimeout`"
 
     _Optional, Default=0s_
 
@@ -454,7 +455,8 @@ Setting them has no effect for UDP entryPoints.
         address: ":8888"
         transport:
           respondingTimeouts:
-            writeTimeout: 42
+            http:
+              writeTimeout: 42
     ```
 
     ```toml tab="File (TOML)"
@@ -462,18 +464,17 @@ Setting them has no effect for UDP entryPoints.
     [entryPoints]
       [entryPoints.name]
         address = ":8888"
-        [entryPoints.name.transport]
-          [entryPoints.name.transport.respondingTimeouts]
-            writeTimeout = 42
+        [entryPoints.name.transport.respondingTimeouts.http]
+          writeTimeout = 42
     ```
 
     ```bash tab="CLI"
     ## Static configuration
     --entryPoints.name.address=:8888
-    --entryPoints.name.transport.respondingTimeouts.writeTimeout=42
+    --entryPoints.name.transport.respondingTimeouts.http.writeTimeout=42
     ```
 
-??? info "`transport.respondingTimeouts.idleTimeout`"
+??? info "`transport.respondingTimeouts.http.idleTimeout`"
 
     _Optional, Default=180s_
 
@@ -490,7 +491,8 @@ Setting them has no effect for UDP entryPoints.
         address: ":8888"
         transport:
           respondingTimeouts:
-            idleTimeout: 42
+            http:
+              idleTimeout: 42
     ```
 
     ```toml tab="File (TOML)"
@@ -498,15 +500,54 @@ Setting them has no effect for UDP entryPoints.
     [entryPoints]
       [entryPoints.name]
         address = ":8888"
-        [entryPoints.name.transport]
-          [entryPoints.name.transport.respondingTimeouts]
-            idleTimeout = 42
+        [entryPoints.name.transport.respondingTimeouts.http]
+          idleTimeout = 42
     ```
 
     ```bash tab="CLI"
     ## Static configuration
     --entryPoints.name.address=:8888
-    --entryPoints.name.transport.respondingTimeouts.idleTimeout=42
+    --entryPoints.name.transport.respondingTimeouts.http.idleTimeout=42
+
+##### `tcp`
+
+`respondingTimeouts.tcp` are timeouts for client connections to the Traefik instance.
+
+??? info "`transport.respondingTimeouts.tcp.lingeringTimeout`"
+
+    _Optional, Default=2s_
+
+    `lingeringTimeout` is the maximum duration between each TCP read operation on the connection.
+    As a layer 4 timeout, it also applies during HTTP handling, but respect the configured HTTP server `readTimeout`.
+
+    If zero, the lingering is disabled.  
+    Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
+    If no units are provided, the value is parsed assuming seconds.
+
+    ```yaml tab="File (YAML)"
+    ## Static configuration
+    entryPoints:
+      name:
+        address: ":8888"
+        transport:
+          respondingTimeouts:
+            tcp:
+              lingeringTimeout: 42
+    ```
+
+    ```toml tab="File (TOML)"
+    ## Static configuration
+    [entryPoints]
+      [entryPoints.name]
+        address = ":8888"
+        [entryPoints.name.transport.respondingTimeouts.tcp]
+          lingeringTimeout = 42
+    ```
+
+    ```bash tab="CLI"
+    ## Static configuration
+    --entryPoints.name.address=:8888
+    --entryPoints.name.transport.respondingTimeouts.tcp.lingeringTimeout=42
     ```
 
 #### `lifeCycle`
diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go
index dcf9b3dbab..2a47763a03 100644
--- a/pkg/config/static/static_config.go
+++ b/pkg/config/static/static_config.go
@@ -56,6 +56,9 @@ const (
 	// DefaultUDPTimeout defines how long to wait by default on an idle session,
 	// before releasing all resources related to that session.
 	DefaultUDPTimeout = 3 * time.Second
+
+	// defaultLingeringTimeout defines the default maximum duration between each read operation on the connection.
+	defaultLingeringTimeout = 2 * time.Second
 )
 
 // Configuration is the static configuration.
@@ -118,16 +121,44 @@ func (a *API) SetDefaults() {
 	a.Dashboard = true
 }
 
-// RespondingTimeouts contains timeout configurations for incoming requests to the Traefik instance.
+// RespondingTimeouts contains timeout configurations.
 type RespondingTimeouts struct {
-	ReadTimeout  ptypes.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set." json:"readTimeout,omitempty" toml:"readTimeout,omitempty" yaml:"readTimeout,omitempty" export:"true"`
-	WriteTimeout ptypes.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set." json:"writeTimeout,omitempty" toml:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty" export:"true"`
-	IdleTimeout  ptypes.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set." json:"idleTimeout,omitempty" toml:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty" export:"true"`
+	// Deprecated: please use `respondingTimeouts.http.readTimeout` instead.
+	ReadTimeout *ptypes.Duration `description:"(Deprecated) ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set." json:"readTimeout,omitempty" toml:"readTimeout,omitempty" yaml:"readTimeout,omitempty" export:"true"`
+	// Deprecated: please use `respondingTimeouts.http.writeTimeout` instead.
+	WriteTimeout *ptypes.Duration `description:"(Deprecated) WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set." json:"writeTimeout,omitempty" toml:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty" export:"true"`
+	// Deprecated: please use `respondingTimeouts.http.idleTimeout` instead.
+	IdleTimeout *ptypes.Duration `description:"(Deprecated) IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set." json:"idleTimeout,omitempty" toml:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty" export:"true"`
+
+	HTTP *HTTPRespondingTimeouts `description:"Defines the HTTP responding timeouts." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"`
+	TCP  *TCPRespondingTimeouts  `description:"Defines the TCP responding timeouts." json:"tcp,omitempty" toml:"tcp,omitempty" yaml:"tcp,omitempty" export:"true"`
+}
+
+// HTTPRespondingTimeouts contains HTTP timeout configurations for incoming requests to the Traefik instance.
+type HTTPRespondingTimeouts struct {
+	ReadTimeout  *ptypes.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set." json:"readTimeout,omitempty" toml:"readTimeout,omitempty" yaml:"readTimeout,omitempty" export:"true"`
+	WriteTimeout *ptypes.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set." json:"writeTimeout,omitempty" toml:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty" export:"true"`
+	IdleTimeout  *ptypes.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set." json:"idleTimeout,omitempty" toml:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty" export:"true"`
+}
+
+// TCPRespondingTimeouts contains TCP timeout configurations for client connections to the Traefik instance.
+type TCPRespondingTimeouts struct {
+	LingeringTimeout ptypes.Duration `description:"LingeringTimeout is the maximum duration between each TCP read operation on the connection. If zero, no timeout is set." json:"lingeringTimeout,omitempty" toml:"lingeringTimeout,omitempty" yaml:"lingeringTimeout,omitempty" export:"true"`
 }
 
 // SetDefaults sets the default values.
 func (a *RespondingTimeouts) SetDefaults() {
-	a.IdleTimeout = ptypes.Duration(DefaultIdleTimeout)
+	noTimeout := ptypes.Duration(0)
+	defaultIdleTimeout := ptypes.Duration(DefaultIdleTimeout)
+	a.HTTP = &HTTPRespondingTimeouts{
+		ReadTimeout:  &noTimeout,
+		WriteTimeout: &noTimeout,
+		IdleTimeout:  &defaultIdleTimeout,
+	}
+
+	a.TCP = &TCPRespondingTimeouts{
+		LingeringTimeout: ptypes.Duration(defaultLingeringTimeout),
+	}
 }
 
 // ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers.
@@ -211,6 +242,39 @@ func (c *Configuration) SetEffectiveConfiguration() {
 		c.EntryPoints["http"] = ep
 	}
 
+	for _, entrypoint := range c.EntryPoints {
+		if entrypoint.Transport == nil ||
+			entrypoint.Transport.RespondingTimeouts == nil {
+			continue
+		}
+
+		respondingTimeouts := entrypoint.Transport.RespondingTimeouts
+
+		if respondingTimeouts.ReadTimeout != nil &&
+			respondingTimeouts.HTTP != nil &&
+			respondingTimeouts.HTTP.ReadTimeout == nil {
+			log.WithoutContext().Warnf("Option `respondingTimeouts.readTimeout` is deprecated, please use `respondingTimeouts.http.readTimeout` instead.")
+			respondingTimeouts.HTTP.ReadTimeout = respondingTimeouts.ReadTimeout
+			respondingTimeouts.ReadTimeout = nil
+		}
+
+		if respondingTimeouts.WriteTimeout != nil &&
+			respondingTimeouts.HTTP != nil &&
+			respondingTimeouts.HTTP.WriteTimeout == nil {
+			log.WithoutContext().Warnf("Option `respondingTimeouts.writeTimeout` is deprecated, please use `respondingTimeouts.http.writeTimeout` instead.")
+			respondingTimeouts.HTTP.WriteTimeout = respondingTimeouts.WriteTimeout
+			respondingTimeouts.WriteTimeout = nil
+		}
+
+		if respondingTimeouts.IdleTimeout != nil &&
+			respondingTimeouts.HTTP != nil &&
+			respondingTimeouts.HTTP.IdleTimeout == nil {
+			log.WithoutContext().Warnf("Option `respondingTimeouts.idleTimeout` is deprecated, please use `respondingTimeouts.http.idleTimeout` instead.")
+			respondingTimeouts.HTTP.IdleTimeout = respondingTimeouts.IdleTimeout
+			respondingTimeouts.IdleTimeout = nil
+		}
+	}
+
 	// Creates the internal traefik entry point if needed
 	if (c.API != nil && c.API.Insecure) ||
 		(c.Ping != nil && !c.Ping.ManualRouting && c.Ping.EntryPoint == DefaultInternalEntryPointName) ||
@@ -316,6 +380,31 @@ func (c *Configuration) ValidateConfiguration() error {
 		return errors.New("Nomad provider cannot have both namespace and namespaces options configured")
 	}
 
+	for epName, entrypoint := range c.EntryPoints {
+		if entrypoint.Transport == nil ||
+			entrypoint.Transport.RespondingTimeouts == nil ||
+			entrypoint.Transport.RespondingTimeouts.HTTP == nil {
+			continue
+		}
+
+		respondingTimeouts := entrypoint.Transport.RespondingTimeouts
+
+		if respondingTimeouts.ReadTimeout != nil &&
+			respondingTimeouts.HTTP.ReadTimeout != nil {
+			return fmt.Errorf("entrypoint %q has `readTimeout` option is defined multiple times (`respondingTimeouts.readTimeout` is deprecated)", epName)
+		}
+
+		if respondingTimeouts.WriteTimeout != nil &&
+			respondingTimeouts.HTTP.WriteTimeout != nil {
+			return fmt.Errorf("entrypoint %q has `writeTimeout` option is defined multiple times (`respondingTimeouts.writeTimeout` is deprecated)", epName)
+		}
+
+		if respondingTimeouts.IdleTimeout != nil &&
+			respondingTimeouts.HTTP.IdleTimeout != nil {
+			return fmt.Errorf("entrypoint %q has `idleTimeout` option is defined multiple times (`respondingTimeouts.idleTimeout` is deprecated)", epName)
+		}
+	}
+
 	return nil
 }
 
diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go
index 0d85243988..551bb43e74 100644
--- a/pkg/server/router/tcp/router.go
+++ b/pkg/server/router/tcp/router.go
@@ -9,7 +9,6 @@ import (
 	"net"
 	"net/http"
 	"slices"
-	"time"
 
 	"github.com/go-acme/lego/v4/challenge/tlsalpn01"
 	"github.com/traefik/traefik/v2/pkg/log"
@@ -117,17 +116,6 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
 		return
 	}
 
-	// Remove read/write deadline and delegate this to underlying tcp server (for now only handled by HTTP Server)
-	err = conn.SetReadDeadline(time.Time{})
-	if err != nil {
-		log.WithoutContext().Errorf("Error while setting read deadline: %v", err)
-	}
-
-	err = conn.SetWriteDeadline(time.Time{})
-	if err != nil {
-		log.WithoutContext().Errorf("Error while setting write deadline: %v", err)
-	}
-
 	connData, err := tcpmuxer.NewConnData(hello.serverName, conn, hello.protos)
 	if err != nil {
 		log.WithoutContext().Errorf("Error while reading TCP connection data: %v", err)
diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go
index 1fb371d081..b5aaa1ac0b 100644
--- a/pkg/server/server_entrypoint_tcp.go
+++ b/pkg/server/server_entrypoint_tcp.go
@@ -244,24 +244,15 @@ func (e *TCPEntryPoint) Start(ctx context.Context) {
 			panic(err)
 		}
 
-		safe.Go(func() {
-			// Enforce read/write deadlines at the connection level,
-			// because when we're peeking the first byte to determine whether we are doing TLS,
-			// the deadlines at the server level are not taken into account.
-			if e.transportConfiguration.RespondingTimeouts.ReadTimeout > 0 {
-				err := writeCloser.SetReadDeadline(time.Now().Add(time.Duration(e.transportConfiguration.RespondingTimeouts.ReadTimeout)))
-				if err != nil {
-					logger.Errorf("Error while setting read deadline: %v", err)
-				}
-			}
-
-			if e.transportConfiguration.RespondingTimeouts.WriteTimeout > 0 {
-				err = writeCloser.SetWriteDeadline(time.Now().Add(time.Duration(e.transportConfiguration.RespondingTimeouts.WriteTimeout)))
-				if err != nil {
-					logger.Errorf("Error while setting write deadline: %v", err)
-				}
-			}
+		if e.transportConfiguration != nil &&
+			e.transportConfiguration.RespondingTimeouts != nil &&
+			e.transportConfiguration.RespondingTimeouts.TCP != nil &&
+			e.transportConfiguration.RespondingTimeouts.TCP.LingeringTimeout > 0 {
+			lingeringTimeout := time.Duration(e.transportConfiguration.RespondingTimeouts.TCP.LingeringTimeout)
+			writeCloser = newLingeringConnection(writeCloser, lingeringTimeout)
+		}
 
+		safe.Go(func() {
 			e.switcher.ServeTCP(newTrackedConnection(writeCloser, e.tracker))
 		})
 	}
@@ -391,6 +382,55 @@ func writeCloser(conn net.Conn) (tcp.WriteCloser, error) {
 	}
 }
 
+// lingeringConn represents a writeCloser with lingeringTimeout handling.
+type lingeringConn struct {
+	tcp.WriteCloser
+
+	lingeringTimeout time.Duration
+
+	rdlMu sync.RWMutex
+	// readDeadline is the current readDeadline set by an upper caller.
+	// In case of HTTP, the HTTP go server manipulates deadlines on the connection.
+	readDeadline time.Time
+}
+
+// newLingeringConnection returns the given writeCloser augmented with lingeringTimeout handling.
+func newLingeringConnection(conn tcp.WriteCloser, timeout time.Duration) tcp.WriteCloser {
+	return &lingeringConn{
+		WriteCloser:      conn,
+		lingeringTimeout: timeout,
+	}
+}
+
+// Read reads data from the connection and postpones the connection readDeadline according to the lingeringTimeout config.
+// It also ensures that the upper level set readDeadline is enforced.
+func (l *lingeringConn) Read(b []byte) (int, error) {
+	if l.lingeringTimeout > 0 {
+		deadline := time.Now().Add(l.lingeringTimeout)
+
+		l.rdlMu.RLock()
+		if !l.readDeadline.IsZero() && deadline.After(l.readDeadline) {
+			deadline = l.readDeadline
+		}
+		l.rdlMu.RUnlock()
+
+		if err := l.WriteCloser.SetReadDeadline(deadline); err != nil {
+			return 0, err
+		}
+	}
+
+	return l.WriteCloser.Read(b)
+}
+
+// SetReadDeadline sets and save the read deadline.
+func (l *lingeringConn) SetReadDeadline(t time.Time) error {
+	l.rdlMu.Lock()
+	l.readDeadline = t
+	l.rdlMu.Unlock()
+
+	return l.WriteCloser.SetReadDeadline(t)
+}
+
 // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
 // connections.
 type tcpKeepAliveListener struct {
@@ -419,7 +459,7 @@ func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
 }
 
 func buildProxyProtocolListener(ctx context.Context, entryPoint *static.EntryPoint, listener net.Listener) (net.Listener, error) {
-	timeout := entryPoint.Transport.RespondingTimeouts.ReadTimeout
+	timeout := *entryPoint.Transport.RespondingTimeouts.HTTP.ReadTimeout
 	// proxyproto use 200ms if ReadHeaderTimeout is set to 0 and not no timeout
 	if timeout == 0 {
 		timeout = -1
@@ -588,9 +628,9 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
 	serverHTTP := &http.Server{
 		Handler:      handler,
 		ErrorLog:     httpServerLogger,
-		ReadTimeout:  time.Duration(configuration.Transport.RespondingTimeouts.ReadTimeout),
-		WriteTimeout: time.Duration(configuration.Transport.RespondingTimeouts.WriteTimeout),
-		IdleTimeout:  time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout),
+		ReadTimeout:  time.Duration(*configuration.Transport.RespondingTimeouts.HTTP.ReadTimeout),
+		WriteTimeout: time.Duration(*configuration.Transport.RespondingTimeouts.HTTP.WriteTimeout),
+		IdleTimeout:  time.Duration(*configuration.Transport.RespondingTimeouts.HTTP.IdleTimeout),
 	}
 	if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) {
 		serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context {


Test patch:
diff --git a/pkg/api/handler_entrypoint_test.go b/pkg/api/handler_entrypoint_test.go
index d5d6a5d1f6..79b92a65f8 100644
--- a/pkg/api/handler_entrypoint_test.go
+++ b/pkg/api/handler_entrypoint_test.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	ptypes "github.com/traefik/paerser/types"
 	"github.com/traefik/traefik/v2/pkg/config/runtime"
 	"github.com/traefik/traefik/v2/pkg/config/static"
 )
@@ -55,9 +56,11 @@ func TestHandler_EntryPoints(t *testing.T) {
 								GraceTimeOut:              2,
 							},
 							RespondingTimeouts: &static.RespondingTimeouts{
-								ReadTimeout:  3,
-								WriteTimeout: 4,
-								IdleTimeout:  5,
+								HTTP: &static.HTTPRespondingTimeouts{
+									ReadTimeout:  paerserDurationPtr(3),
+									WriteTimeout: paerserDurationPtr(4),
+									IdleTimeout:  paerserDurationPtr(5),
+								},
 							},
 						},
 						ProxyProtocol: &static.ProxyProtocol{
@@ -77,9 +80,11 @@ func TestHandler_EntryPoints(t *testing.T) {
 								GraceTimeOut:              20,
 							},
 							RespondingTimeouts: &static.RespondingTimeouts{
-								ReadTimeout:  30,
-								WriteTimeout: 40,
-								IdleTimeout:  50,
+								HTTP: &static.HTTPRespondingTimeouts{
+									ReadTimeout:  paerserDurationPtr(3),
+									WriteTimeout: paerserDurationPtr(4),
+									IdleTimeout:  paerserDurationPtr(5),
+								},
 							},
 						},
 						ProxyProtocol: &static.ProxyProtocol{
@@ -263,3 +268,8 @@ func generateEntryPoints(nb int) map[string]*static.EntryPoint {
 
 	return eps
 }
+
+func paerserDurationPtr(duration int) *ptypes.Duration {
+	d := ptypes.Duration(duration)
+	return &d
+}
diff --git a/pkg/api/testdata/entrypoints.json b/pkg/api/testdata/entrypoints.json
index d93d07bfcf..93f56a4018 100644
--- a/pkg/api/testdata/entrypoints.json
+++ b/pkg/api/testdata/entrypoints.json
@@ -23,9 +23,11 @@
 				"requestAcceptGraceTimeout": "1ns"
 			},
 			"respondingTimeouts": {
-				"idleTimeout": "5ns",
-				"readTimeout": "3ns",
-				"writeTimeout": "4ns"
+				"http": {
+					"idleTimeout": "5ns",
+					"readTimeout": "3ns",
+					"writeTimeout": "4ns"
+				}
 			}
 		}
 	},
@@ -53,9 +55,11 @@
 				"requestAcceptGraceTimeout": "10ns"
 			},
 			"respondingTimeouts": {
-				"idleTimeout": "50ns",
-				"readTimeout": "30ns",
-				"writeTimeout": "40ns"
+				"http": {
+					"idleTimeout": "5ns",
+					"readTimeout": "3ns",
+					"writeTimeout": "4ns"
+				}
 			}
 		}
 	}
diff --git a/pkg/redactor/redactor_config_test.go b/pkg/redactor/redactor_config_test.go
index 368f2d9564..4cc0806834 100644
--- a/pkg/redactor/redactor_config_test.go
+++ b/pkg/redactor/redactor_config_test.go
@@ -520,6 +520,8 @@ func TestDo_staticConfiguration(t *testing.T) {
 		},
 	}
 
+	paerserDuration := ptypes.Duration(111 * time.Second)
+
 	config.EntryPoints = static.EntryPoints{
 		"foobar": {
 			Address: "foo Address",
@@ -529,9 +531,14 @@ func TestDo_staticConfiguration(t *testing.T) {
 					GraceTimeOut:              ptypes.Duration(111 * time.Second),
 				},
 				RespondingTimeouts: &static.RespondingTimeouts{
-					ReadTimeout:  ptypes.Duration(111 * time.Second),
-					WriteTimeout: ptypes.Duration(111 * time.Second),
-					IdleTimeout:  ptypes.Duration(111 * time.Second),
+					HTTP: &static.HTTPRespondingTimeouts{
+						ReadTimeout:  &paerserDuration,
+						WriteTimeout: &paerserDuration,
+						IdleTimeout:  &paerserDuration,
+					},
+					TCP: &static.TCPRespondingTimeouts{
+						LingeringTimeout: ptypes.Duration(111 * time.Second),
+					},
 				},
 			},
 			ProxyProtocol: &static.ProxyProtocol{
diff --git a/pkg/redactor/testdata/anonymized-static-config.json b/pkg/redactor/testdata/anonymized-static-config.json
index 09e0d796d8..dd25c1fa32 100644
--- a/pkg/redactor/testdata/anonymized-static-config.json
+++ b/pkg/redactor/testdata/anonymized-static-config.json
@@ -26,9 +26,14 @@
           "graceTimeOut": "1m51s"
         },
         "respondingTimeouts": {
-          "readTimeout": "1m51s",
-          "writeTimeout": "1m51s",
-          "idleTimeout": "1m51s"
+          "http": {
+            "readTimeout": "1m51s",
+            "writeTimeout": "1m51s",
+            "idleTimeout": "1m51s"
+          },
+          "tcp": {
+            "lingeringTimeout": "1m51s"
+          }
         }
       },
       "proxyProtocol": {
diff --git a/pkg/server/server_entrypoint_tcp_test.go b/pkg/server/server_entrypoint_tcp_test.go
index f0b12c8ddb..6b177099f6 100644
--- a/pkg/server/server_entrypoint_tcp_test.go
+++ b/pkg/server/server_entrypoint_tcp_test.go
@@ -69,8 +69,10 @@ func testShutdown(t *testing.T, router *tcprouter.Router) {
 
 	epConfig.LifeCycle.RequestAcceptGraceTimeout = 0
 	epConfig.LifeCycle.GraceTimeOut = ptypes.Duration(5 * time.Second)
-	epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(5 * time.Second)
-	epConfig.RespondingTimeouts.WriteTimeout = ptypes.Duration(5 * time.Second)
+	readTimeout := ptypes.Duration(5 * time.Second)
+	epConfig.RespondingTimeouts.HTTP.ReadTimeout = &readTimeout
+	writeTimeout := ptypes.Duration(5 * time.Second)
+	epConfig.RespondingTimeouts.HTTP.WriteTimeout = &writeTimeout
 
 	entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
 		// We explicitly use an IPV4 address because on Alpine, with an IPV6 address
@@ -157,7 +159,8 @@ func startEntrypoint(entryPoint *TCPEntryPoint, router *tcprouter.Router) (net.C
 func TestReadTimeoutWithoutFirstByte(t *testing.T) {
 	epConfig := &static.EntryPointsTransport{}
 	epConfig.SetDefaults()
-	epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second)
+	readTimeout := ptypes.Duration(2 * time.Second)
+	epConfig.RespondingTimeouts.HTTP.ReadTimeout = &readTimeout
 
 	entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
 		Address:          ":0",
@@ -194,7 +197,84 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) {
 func TestReadTimeoutWithFirstByte(t *testing.T) {
 	epConfig := &static.EntryPointsTransport{}
 	epConfig.SetDefaults()
-	epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second)
+	readTimeout := ptypes.Duration(2 * time.Second)
+	epConfig.RespondingTimeouts.HTTP.ReadTimeout = &readTimeout
+
+	entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
+		Address:          ":0",
+		Transport:        epConfig,
+		ForwardedHeaders: &static.ForwardedHeaders{},
+		HTTP2:            &static.HTTP2Config{},
+	}, nil)
+	require.NoError(t, err)
+
+	router := &tcprouter.Router{}
+	router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+		rw.WriteHeader(http.StatusOK)
+	}))
+
+	conn, err := startEntrypoint(entryPoint, router)
+	require.NoError(t, err)
+
+	_, err = conn.Write([]byte("GET /some HTTP/1.1\r\n"))
+	require.NoError(t, err)
+
+	errChan := make(chan error)
+
+	go func() {
+		b := make([]byte, 2048)
+		_, err := conn.Read(b)
+		errChan <- err
+	}()
+
+	select {
+	case err := <-errChan:
+		require.Equal(t, io.EOF, err)
+	case <-time.Tick(5 * time.Second):
+		t.Error("Timeout while read")
+	}
+}
+
+func TestLingeringTimeoutWithoutFirstByte(t *testing.T) {
+	epConfig := &static.EntryPointsTransport{}
+	epConfig.SetDefaults()
+
+	entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
+		Address:          ":0",
+		Transport:        epConfig,
+		ForwardedHeaders: &static.ForwardedHeaders{},
+		HTTP2:            &static.HTTP2Config{},
+	}, nil)
+	require.NoError(t, err)
+
+	router := &tcprouter.Router{}
+	router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+		rw.WriteHeader(http.StatusOK)
+	}))
+
+	conn, err := startEntrypoint(entryPoint, router)
+	require.NoError(t, err)
+
+	errChan := make(chan error)
+
+	go func() {
+		b := make([]byte, 2048)
+		_, err := conn.Read(b)
+		errChan <- err
+	}()
+
+	select {
+	case err := <-errChan:
+		require.Equal(t, io.EOF, err)
+	case <-time.Tick(5 * time.Second):
+		t.Error("Timeout while read")
+	}
+}
+
+func TestLingeringTimeoutWithFirstByte(t *testing.T) {
+	epConfig := &static.EntryPointsTransport{}
+	epConfig.SetDefaults()
+	epConfig.RespondingTimeouts.TCP.LingeringTimeout = ptypes.Duration(time.Second)
 
 	entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
 		Address:          ":0",