A flexible and efficient reverse proxy implementation for Axum web applications. This library provides a simple way to forward HTTP requests from your Axum application to upstream servers. It is intended to be a simple implementation sitting on top of axum and hyper.
The eventual goal would be to benchmark ourselves against common reverse proxy libraries like nginx, traefik, haproxy, etc. We hope to achieve comparable (or better) performance but with significantly better developer ergonomics, using Rust code to configure the proxy instead of various configuration files with their own DSLs.
- 🛣 Path-based routing
- 🔄 Optional retry mechanism with configurable delay
- 📨 Header forwarding (with host header management)
- âš™ Configurable HTTP client settings
- 🔀 Round-robin load balancing across multiple upstreams
- 🔌 Easy integration with Axum's Router
- đź§° Custom client configuration support
- đź”’ HTTPS support
- đź“‹ Optional RFC9110 compliance layer
- đź”§ Full Tower middleware support
Run cargo add axum-reverse-proxy
to add the library to your project.
This crate comes with a couple of optional features:
tls
– enables HTTPS support viahyper-rustls
(this is on by default)dns
– enables DNS-based service discoveryfull
– enables all features (tls
anddns
)
To enable features explicitly in Cargo.toml
:
[dependencies]
axum-reverse-proxy = { version = "*", features = ["dns"] }
Here's a simple example that forwards all requests under /api
to httpbin.org
:
use axum::Router;
use axum_reverse_proxy::ReverseProxy;
// Create a reverse proxy that forwards requests from /api to httpbin.org
let proxy = ReverseProxy::new("/api", "https://httpbin.org");
// Convert the proxy to a router and use it in your Axum application
let app: Router = proxy.into();
use axum::Router;
use axum_reverse_proxy::BalancedProxy;
let proxy = BalancedProxy::new("/api", vec!["https://api1.example.com", "https://api2.example.com"]);
let app: Router = proxy.into();
DiscoverableBalancedProxy
works with any [tower::discover::Discover
] implementation to update its upstream list at runtime. The crate provides DnsDiscovery
and StaticDnsDiscovery
(requires the dns
feature) for automatic resolution of hostnames.
DnsDiscovery
periodically resolves a hostname, while StaticDnsDiscovery
performs a single lookup at startup.
use axum::Router;
use axum_reverse_proxy::{DiscoverableBalancedProxy, DnsDiscovery, DnsDiscoveryConfig};
use std::time::Duration;
let dns_config = DnsDiscoveryConfig::new("api.example.com", 443)
.with_https(true)
.with_refresh_interval(Duration::from_secs(30));
let discovery = DnsDiscovery::new(dns_config).unwrap();
let mut proxy = DiscoverableBalancedProxy::new("/api", discovery);
proxy.start_discovery().await;
let app: Router = Router::new().nest_service("/", proxy);
The proxy integrates seamlessly with Tower middleware. Common use cases include:
- Authentication and authorization
- Rate limiting
- Request validation
- Logging and tracing
- Timeouts and retries
- Caching
- Compression
- Request buffering (via tower-buffer)
Example using tower-buffer for request buffering:
use axum::Router;
use axum_reverse_proxy::ReverseProxy;
use tower::ServiceBuilder;
use tower_buffer::BufferLayer;
let proxy = ReverseProxy::new("/api", "https://api.example.com");
let app: Router = proxy.into();
// Add buffering middleware
let app = app.layer(ServiceBuilder::new().layer(BufferLayer::new(100)));
You can merge the proxy with your existing Axum router:
use axum::{routing::get, Router, response::IntoResponse, extract::State};
use axum_reverse_proxy::ReverseProxy;
#[derive(Clone)]
struct AppState { foo: usize }
async fn root_handler(State(state): State<AppState>) -> impl IntoResponse {
(axum::http::StatusCode::OK, format!("Hello, World! {}", state.foo))
}
let app: Router<AppState> = Router::new()
.route("/", get(root_handler))
.merge(ReverseProxy::new("/api", "https://httpbin.org"))
.with_state(AppState { foo: 42 });
The library includes an optional RFC9110 compliance layer that implements key requirements from RFC9110 (HTTP Semantics). To use it:
use axum_reverse_proxy::{ReverseProxy, Rfc9110Config, Rfc9110Layer};
use std::collections::HashSet;
// Create a config for RFC9110 compliance
let mut server_names = HashSet::new();
server_names.insert("example.com".to_string());
let config = Rfc9110Config {
server_names: Some(server_names), // For loop detection
pseudonym: Some("myproxy".to_string()), // For Via headers
combine_via: true, // Combine Via headers with same protocol
};
// Create a proxy with RFC9110 compliance
let proxy = ReverseProxy::new("/api", "https://api.example.com")
.layer(Rfc9110Layer::with_config(config));
The RFC9110 layer provides:
- Connection Header Processing: Properly handles Connection headers and removes hop-by-hop headers
- Via Header Management: Adds and combines Via headers according to spec, with optional firewall mode
- Max-Forwards Processing: Handles Max-Forwards header for TRACE/OPTIONS methods
- Loop Detection: Detects request loops using Via headers and server names
- End-to-end Header Preservation: Preserves end-to-end headers while removing hop-by-hop headers
For more control over the HTTP client behavior:
use axum_reverse_proxy::ReverseProxy;
use hyper_util::client::legacy::{Client, connect::HttpConnector};
use axum::body::Body;
let mut connector = HttpConnector::new();
connector.set_nodelay(true);
connector.enforce_http(false);
connector.set_keepalive(Some(std::time::Duration::from_secs(60)));
let client = Client::builder(hyper_util::rt::TokioExecutor::new())
.pool_idle_timeout(std::time::Duration::from_secs(60))
.pool_max_idle_per_host(32)
.build(connector);
let proxy = ReverseProxy::new_with_client("/api", "https://api.example.com", client);
The default configuration includes:
- 60-second keepalive timeout
- 10-second connect timeout
- TCP nodelay enabled
- Connection pooling with 32 idle connections per host
- Automatic host header management
By default, this library uses rustls for TLS connections, which provides a pure-Rust, secure, and modern TLS implementation.
[dependencies]
axum-reverse-proxy = "1.0"
# or explicitly enable the default TLS feature
axum-reverse-proxy = { version = "1.0", features = ["tls"] }
If you need to use the system's native TLS implementation (OpenSSL on Linux, Secure Transport on macOS, SChannel on Windows), you can opt into the native-tls
feature:
[dependencies]
axum-reverse-proxy = { version = "1.0", features = ["native-tls"] }
default = ["tls"]
- Uses rustls (recommended)features = ["native-tls"]
- Uses native TLS implementationfeatures = ["tls", "native-tls"]
- Both available, native-tls takes precedencefeatures = ["full"]
- Includestls
(rustls) anddns
featuresfeatures = []
- No TLS support (HTTP only)
Note: When both tls
and native-tls
features are enabled, native-tls
takes precedence since explicit selection of native-tls indicates a preference for the system's TLS implementation. The native-tls
feature is a separate opt-in and is not included in the full
feature set.
Check out the examples directory for more usage examples:
- Basic Proxy - Shows how to set up a basic reverse proxy with path-based routing
- Retry Proxy - Demonstrates enabling retries via
RetryLayer
- Balanced Proxy - Forward to multiple upstream servers with round-robin load balancing
- Note: very large requests may still need buffering depending on the body wrapper's strategy.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.