tower-http has three timeout layers. TimeoutLayer puts one deadline on the whole request and returns a configured status, such as 408. RequestBodyTimeoutLayer and ResponseBodyTimeoutLayer reset their deadline each time a frame arrives, so they catch idle or stalled peers rather than slow total time. Use both kinds together.
The three layers
From the tower-http documentation:
| Layer | What it times | Behaviour |
|---|---|---|
| TimeoutLayer | The request as handled by the inner service | On timeout the request is aborted and a response with an empty body and a custom status code is returned |
| RequestBodyTimeoutLayer | Reading the request body | "Resets its deadline each time a frame is received", meant for detecting idle connections |
| ResponseBodyTimeoutLayer | Writing the response body | Same per frame reset |
The docs give an example that sets the status with TimeoutLayer::with_status_code and StatusCode::REQUEST_TIMEOUT. They do not state a status code for the two body layers, so check how your server maps their errors before relying on a specific response.
Why the difference matters
Think of two bad clients.
The first sends a request body very slowly, one small chunk every few seconds. A total deadline will end that eventually, but a per frame deadline ends it after the first long gap. The second sends bytes at a steady, painfully slow rate that never goes idle for long. A per frame timeout never fires on that client, but a total deadline does.
You want both protections. The per frame layers detect stalls fast. The total deadline puts an upper bound on any request, however steady its trickle.
Example
use std::time::Duration;
use axum::http::StatusCode;
use tower_http::timeout::{RequestBodyTimeoutLayer, TimeoutLayer};
let app = Router::new()
.route("/api/items", post(create_item))
.layer(RequestBodyTimeoutLayer::new(Duration::from_secs(10)))
.layer(TimeoutLayer::with_status_code(
StatusCode::REQUEST_TIMEOUT,
Duration::from_secs(30),
));
Values here are illustrative. Choose them from your slowest valid request and measure under load.
Do not forget the connection level
Layers act on requests that the server has already accepted. Header reading happens earlier, in the HTTP server library. See the post on hyper's header read timeout and its timer requirement for that layer.
Choosing values
Start from data. Look at the 99th percentile duration of each route class and set the total deadline comfortably above it. For the per frame layers, ask how long a healthy client could be silent. Mobile clients can pause for a few seconds on weak links, so a per frame value of a few seconds is often too tight for uploads and fine for small JSON requests. Different route groups can carry different layers, so apply the strictest ones where payloads are smallest.
Steps
- Decide a total deadline per route class, such as 30 seconds for APIs.
- Add TimeoutLayer with an explicit status code.
- Add RequestBodyTimeoutLayer on routes that read large bodies.
- Add ResponseBodyTimeoutLayer for streaming responses.
- Test with a client that sends one byte every few seconds, and one that sends steadily at a very low rate.
- Combine with a body size limit.
Try it yourself
Click through it at your own pace, or open it full screen. Look at how an API scan reports missing limits and weak handling of slow or large requests.
Quick check
Can I use only TimeoutLayer?
It gives you a hard upper bound, but a stalled upload holds resources until the full deadline. Adding the body layers frees them sooner.
Do these layers replace a reverse proxy timeout?
No. They are another layer. Keep proxy limits and application limits, since traffic can reach the service by more than one path.
Safeguard's dependency scanning and SBOM generation read Rust lock files, so the crates you ship are inventoried and checked against advisories, and the free scan is at gold.safeguard.sh/scan.
Sources
Last reviewed: 19 October 2026.