# Proxy Overview

> Two ways to use rws as a reverse proxy — zero-code config-driven mode and programmatic library mode.

Source: https://rws8.tech/docs/proxy/overview/ · Last updated 2026-11-15

rws proxies requests to your apps from config alone: a `[[route]]` matches host and path, an `[[upstream]]` lists the backends, and rws load balances, health checks and retries them — no code. When you need more control, the same proxy stack is a library, so there are two ways to use it.

## Mode 1 — Config-driven proxy (no code)

Drop a `rws.config.toml` file next to the binary and run `rws`. As soon as the file contains at least one `[[route]]` or `[[upstream]]` section, the server automatically switches into proxy mode. No Rust code is required.

```toml
# rws.config.toml
[[upstream]]
name = "api"
backends = ["localhost:3000", "localhost:3001"]

[[route]]
name = "api-proxy"

[route.match]
path = "/api/*"

[route.action]
type = "proxy"

[route.action.proxy]
upstream = "api"
```

```bash
rws          # proxy mode activated automatically
```

**Best for:** ops teams, sidecar containers, API gateways, and situations where you just need to route traffic without touching Rust.

## Mode 2 — Library / programmatic

Add `rust-web-server` as a dependency and compose the proxy middleware stack in code.

```rust
use rust_web_server::app::App;
use rust_web_server::core::New;
use rust_web_server::proxy::ReverseProxy;
use rust_web_server::server::Server;

let app = App::new()
    .wrap(ReverseProxy::new(["http://backend:3000"])
        .path_prefix("/api"));

let (listener, pool) = Server::setup().unwrap();
Server::run(listener, pool, app); // async builds: Server::run_tls(listener, pool, app).await
```

**Best for:** services that need custom routing logic, per-request auth, conditional proxying, or tight integration with application code.

## Side-by-side comparison

| | Config-driven | Library |
|---|---|---|
| Requires Rust code | No | Yes |
| Hot-reload via SIGHUP | Yes (routes + upstreams) | No |
| Health checks | Yes (built-in) | Manual (`BackendPool` + `CircuitBreaker`) |
| L4 TCP / UDP / WS proxies | Yes (config sections) | Yes (standalone structs) |
| Path rewriting | Yes (`strip_path_prefix`, `add_path_prefix`) | Yes (`RewriteLayer`) |
| Per-route rate limiting | Yes | Yes (`RateLimitLayer`) |
| Auth | Yes (bearer token from env var, JWT, htpasswd basic) | Yes (`JwtLayer`, `BasicAuthLayer`) |
| Response caching | Yes (`[route.middleware.cache]`) | Yes (`CacheLayer`) |
| gRPC | Yes (`type = "grpc"`) | Yes (`GrpcProxy`, requires `http2` feature) |
| Canary / traffic splitting | Yes (`weights` per backend) | Yes (`CanaryLayer`) |
| Service discovery | No (Gateway API mode reads Kubernetes endpoints) | Yes (`BackendPool`) |
| Circuit breaker / retry | Yes (`[upstream.circuit_breaker]`, retries) | Yes (`CircuitBreaker`, `RetryLayer`) |

## Behind an L4 load balancer (PROXY protocol)

A TCP load balancer (HAProxy in `mode tcp`, AWS NLB, GCP's TCP proxy) hides
the client's address: rws sees the balancer's. With the PROXY protocol the
balancer prepends the client's address to each connection; turn it on in rws
(since 18.3.0):

```toml
proxy_protocol = true   # RWS_CONFIG_PROXY_PROTOCOL
```

Every connection to the main listener must then start with a version 1 or
version 2 header — before the TLS handshake on HTTPS — and the address in it
is the client address: in access logs, rate limits, IP filters and
`X-Forwarded-For` to upstreams. A connection without a valid header within
10 seconds is closed, so only enable it when every client comes through the
balancer. The balancer's own health checks (v2 `LOCAL`, v1 `UNKNOWN`) keep
its address. The setting is read at startup.

```text
# HAProxy
backend rws
    mode tcp
    server rws1 10.0.0.5:443 send-proxy-v2   # or send-proxy for version 1
```

HTTP/3 (QUIC) has no PROXY protocol; it is unaffected.

## Detection logic

At startup `main()` calls `ProxyConfig::is_proxy_mode()`, which reads `rws.config.toml` (or the path in `RWS_CONFIG_FILE`) and returns `true` if the text contains `[[route]]` or `[[upstream]]`. When `true`, `build_from_file()` compiles the config into a `ConfigDrivenApp` that replaces the default `App`.

## What to read next

- [Config-Driven Proxy](/proxy/config-driven/) — full `rws.config.toml` reference
- [Reverse Proxy](/proxy/reverse-proxy/) — `ReverseProxy` middleware API
- [Load Balancing](/proxy/load-balancing/) — round-robin, random, IP-hash and least-connections
- [Health Checks](/proxy/health-checks/) — automatic backend health monitoring
- [Circuit Breaker](/proxy/circuit-breaker/) — failure isolation and retry
- [Canary](/proxy/canary/) — weighted traffic splitting
- [Service Discovery](/proxy/service-discovery/) — dynamic backend pools
- [TCP Proxy](/proxy/tcp-proxy/) — L4 TCP tunnel
- [UDP Proxy](/proxy/udp-proxy/) — datagram proxy
- [WebSocket Proxy](/proxy/websocket-proxy/) — WS upgrade tunneling
- [gRPC Proxy](/proxy/grpc-proxy/) — HTTP/2 gRPC forwarding
