In this guide you will build a working HTTP load balancer that distributes incoming requests across two upstream backends —Documentation Index
Fetch the complete documentation index at: https://mintlify.com/cloudflare/pingora/llms.txt
Use this file to discover all available pages before exploring further.
1.1.1.1:443 and 1.0.0.1:443 — in a round-robin fashion. Along the way you will add TCP health checks so that a broken backend is automatically removed from rotation, wire up CLI argument parsing, write a configuration file, and perform a zero-downtime binary upgrade. By the end you will have a solid foundation you can adapt for your own upstream services.
Create a new Cargo project
Cargo.toml and add the two required dependencies. The lb feature flag pulls in pingora-load-balancing and pingora-proxy; openssl activates the OpenSSL TLS backend.Create a Pingora Server
A Pingora This compiles and runs cleanly, but does nothing useful yet — there are no services attached.
Server is a process that can host one or many services. It handles configuration and CLI argument parsing, daemonisation, OS signal handling, and graceful restart or shutdown. The idiomatic pattern is to construct it in main() and call run_forever() at the end, which spawns the Tokio runtime threads and blocks until the server is ready to exit.Replace the contents of src/main.rs with:Implement ProxyHttp for your load balancer
Define the
LB struct that wraps a LoadBalancer<RoundRobin> and implement the ProxyHttp trait for it. The only required method is upstream_peer(), which returns the address to proxy each request to. The LoadBalancer::select() call advances the round-robin cursor and returns the next healthy backend.Add the upstream_request_filter hook
The 1.1.1.1 demo backends require a
Host header to be present. The upstream_request_filter() callback fires after the upstream connection is established but before the request headers are sent — the right place to inject or rewrite headers.Add this method inside the same impl ProxyHttp for LB block:Create the proxy service and wire it to the server
http_proxy_service() wraps your LB in a Service that speaks HTTP proxy protocol. add_tcp() tells it which address and port to listen on. Finally, add_service() registers the service with the Server so it is started when run_forever() is called.Replace your main() function with the complete version:Test It
Start the load balancer:Adapting to Your Own Upstreams
The example above is deliberately tuned for the 1.1.1.1 demo backends. Pointing the load balancer at your own services usually requires two changes. 1. Change the backend addresses inmain():
upstream_peer(). Pass false for the tls argument and an empty string for the SNI — it is ignored for plain-HTTP connections:
Host header override in upstream_request_filter(). The one.one.one.one value is specific to the demo backends; set it to whatever hostname your upstream expects, or delete the method entirely to let Pingora forward the original Host header from the client.
Add Health Checks
Without health checks, a dead backend is still selected by the round-robin scheduler — every third request (in a three-peer pool) will fail with a502. Pingora’s TcpHealthCheck probes each backend in the background and removes unhealthy peers from rotation automatically.
First, add a third backend that is guaranteed to be broken so you can observe the effect:
127.0.0.1:343 will be marked unhealthy within one second of startup and will never receive traffic. If it later becomes reachable it will be re-added to rotation automatically within the next health-check cycle.
CLI Options and Daemon Mode
PassingSome(Opt::parse_args()) to Server::new() enables Pingora’s built-in CLI argument parsing:
-h to see all available flags:
-d / --daemon flag:
SIGTERM for a graceful shutdown (the server stops accepting new connections but finishes all in-flight requests before exiting):
SIGQUIT to initiate a graceful upgrade (the server hands its listening sockets to a new process — see Graceful Upgrade below):
Configuration File
Pingora can read runtime settings from a YAML configuration file. Createconf.yaml in your project directory:
| Key | Description |
|---|---|
version | Config schema version — always 1 for now. |
threads | Number of Tokio worker threads. |
pid_file | Path where the daemon writes its PID. |
error_log | Path for the error log file. |
upgrade_sock | Unix socket used to hand over listening sockets during a graceful upgrade. |
error_log:
Graceful Upgrade
Pingora supports zero-downtime binary upgrades on Linux. The old process listens forSIGQUIT, then hands its open listening sockets to the new process over the upgrade_sock Unix socket. The old process continues handling its in-flight requests until they are all complete before exiting. From a client’s perspective the listening socket is never closed.
To upgrade a running daemon to a newly compiled binary:
-u / --upgrade flag tells the new server to connect to upgrade_sock and receive the listening sockets from the old server rather than binding them fresh.