Cloudflare 开源 Pingora 之后,很多人会问:它能不能替代 Nginx?如果问题只是“能不能把请求转发到后端服务”,答案是能,而且几十行 Rust 就够了。但 Pingora 不是一个读取 nginx.conf 的服务器,它更像一套用来写代理、网关和网络服务的 Rust 框架。
这篇只做一件事:用 Pingora 写一个最小反向代理,让 http://127.0.0.1:6188/ 转发到本机的 127.0.0.1:3000,然后把这个二进制按服务方式部署起来。
本文示例版本核对时间:2026-05-17。示例使用 pingora = "0.8"。
先看等价的 Nginx 配置
如果用 Nginx,最小反代大概长这样:
server {
listen 6188;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
}
}
Pingora 的思路不一样。你不是写配置块,而是实现 ProxyHttp trait:请求进来以后,代码决定它要转发到哪里,转发前要改哪些请求头,最后日志怎么记。
创建项目
cargo new mini-pingora-proxy
cd mini-pingora-proxy
Cargo.toml:
[package]
name = "mini-pingora-proxy"
version = "0.1.0"
edition = "2021"
[dependencies]
async-trait = "0.1"
env_logger = "0.11"
log = "0.4"
pingora = { version = "0.8", features = ["proxy"] }
这里用的是 pingora 聚合 crate,并启用 proxy feature。Pingora 没有默认打开所有能力;如果后面要做负载均衡,需要再启用 lb;如果要监听 HTTPS 或连接 HTTPS upstream,还要选择对应的 TLS feature,比如 openssl、boringssl、s2n 或实验性的 rustls。
实现最小代理
src/main.rs:
use async_trait::async_trait;
use log::info;
use pingora::prelude::*;
struct MiniProxy {
upstream: &'static str,
}
#[async_trait]
impl ProxyHttp for MiniProxy {
type CTX = ();
fn new_ctx(&self) -> Self::CTX {}
async fn upstream_peer(
&self,
_session: &mut Session,
_ctx: &mut Self::CTX,
) -> Result<Box<HttpPeer>> {
let peer = HttpPeer::new(self.upstream, false, String::new());
Ok(Box::new(peer))
}
async fn upstream_request_filter(
&self,
session: &mut Session,
upstream_request: &mut RequestHeader,
_ctx: &mut Self::CTX,
) -> Result<()> {
if let Some(host) = session
.req_header()
.headers
.get("host")
.and_then(|value| value.to_str().ok())
{
upstream_request.insert_header("Host", host).unwrap();
}
if let Some(client_addr) = session.client_addr().and_then(|addr| addr.as_inet()) {
let ip = client_addr.ip().to_string();
let forwarded_for = match session
.req_header()
.headers
.get("x-forwarded-for")
.and_then(|value| value.to_str().ok())
{
Some(existing) if !existing.is_empty() => format!("{existing}, {ip}"),
_ => ip.clone(),
};
upstream_request.insert_header("X-Real-IP", &ip).unwrap();
upstream_request
.insert_header("X-Forwarded-For", forwarded_for)
.unwrap();
}
upstream_request
.insert_header("X-Forwarded-Proto", "http")
.unwrap();
Ok(())
}
async fn logging(
&self,
session: &mut Session,
error: Option<&Error>,
_ctx: &mut Self::CTX,
) {
info!(
"{} error={:?}",
session.request_summary(),
error.map(|e| e.to_string())
);
}
}
fn main() {
env_logger::init();
let mut server = Server::new(None).unwrap();
server.bootstrap();
let proxy = MiniProxy {
upstream: "127.0.0.1:3000",
};
let mut service = http_proxy_service(&server.configuration, proxy);
service.add_tcp("0.0.0.0:6188");
server.add_service(service);
server.run_forever();
}
看到这里,Pingora 的核心就出来了:
upstream_peer()决定请求要去哪个后端,类似 Nginx 的proxy_pass。upstream_request_filter()在转发前补请求头,类似proxy_set_header。http_proxy_service()把MiniProxy变成真正监听 TCP 端口的服务。
HTTP 解析、连接建立、连接复用、请求体和响应体转发这些通用代理工作,Pingora 已经处理了。你写的是“这个请求应该怎么被代理”的业务逻辑。
本地运行
先启动一个后端服务:
python3 -m http.server 3000
再启动 Pingora 代理:
RUST_LOG=info cargo run
测试:
curl -v http://127.0.0.1:6188/
如果能看到 Python 静态服务器返回的目录列表或页面,说明最小反代已经跑通。
编译 release 版本
开发时用 cargo run 就够了,上服务器时应该编译 release 版本:
cargo build --release
复制到服务器上的常见位置:
install -m 755 target/release/mini-pingora-proxy /usr/local/bin/mini-pingora-proxy
如果后端服务和 Pingora 代理都在同一台机器上,确认本机能访问后端:
curl -I http://127.0.0.1:3000/
再直接运行代理:
RUST_LOG=info /usr/local/bin/mini-pingora-proxy
另开一个终端测试:
curl -I http://127.0.0.1:6188/
用 systemd 部署
最简单的 systemd 单元可以这样写。
/etc/systemd/system/mini-pingora-proxy.service:
[Unit]
Description=Mini Pingora Reverse Proxy
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/mini-pingora-proxy
Restart=always
RestartSec=3
Environment=RUST_LOG=info
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
启动:
systemctl daemon-reload
systemctl enable --now mini-pingora-proxy
systemctl status mini-pingora-proxy
看日志:
journalctl -u mini-pingora-proxy -f
到这里,这个最小代理已经能作为一个系统服务运行。它还没有配置文件、没有 HTTPS、没有 Web 管理界面,也没有把 upstream 做成可热更新配置,但作为理解 Pingora 的起点已经足够。
接入 Pingora 的运行配置
如果你想让这个二进制更像正式服务,可以让 Pingora 接管命令行参数:
use pingora::prelude::*;
fn main() {
env_logger::init();
let mut server = Server::new(Some(Opt::parse_args())).unwrap();
server.bootstrap();
// add service...
server.run_forever();
}
这样二进制就可以读取 Pingora 的运行配置。一个典型 conf.yaml:
---
version: 1
threads: 2
pid_file: /run/mini-pingora-proxy.pid
error_log: /var/log/mini-pingora-proxy/error.log
upgrade_sock: /run/mini-pingora-proxy-upgrade.sock
upstream_keepalive_pool_size: 512
work_stealing: true
后台启动:
RUST_LOG=INFO /usr/local/bin/mini-pingora-proxy -c conf.yaml -d
优雅停止:
pkill -SIGTERM mini-pingora-proxy
优雅升级:
pkill -SIGQUIT mini-pingora-proxy
RUST_LOG=INFO /usr/local/bin/mini-pingora-proxy -c conf.yaml -d -u
这种模式更接近传统代理服务的运行方式。新进程可以接管监听 socket,旧进程继续处理已经在途的请求,避免粗暴停启造成连接中断。
如果要多个后端
最小代码只转发到一个后端。真实服务通常还要多个 upstream 和健康检查。Pingora 可以做,但你要继续写代码。
启用负载均衡 feature:
pingora = { version = "0.8", features = ["proxy", "lb"] }
核心逻辑类似这样:
use async_trait::async_trait;
use pingora::prelude::*;
use std::sync::Arc;
struct LB(Arc<LoadBalancer<RoundRobin>>);
#[async_trait]
impl ProxyHttp for LB {
type CTX = ();
fn new_ctx(&self) -> Self::CTX {}
async fn upstream_peer(
&self,
_session: &mut Session,
_ctx: &mut Self::CTX,
) -> Result<Box<HttpPeer>> {
let upstream = self.0.select(b"", 256).unwrap();
let peer = HttpPeer::new(upstream, false, String::new());
Ok(Box::new(peer))
}
}
fn main() {
let mut server = Server::new(None).unwrap();
server.bootstrap();
let upstreams =
LoadBalancer::try_from_iter(["127.0.0.1:3000", "127.0.0.1:3001"]).unwrap();
let mut service = http_proxy_service(&server.configuration, LB(Arc::new(upstreams)));
service.add_tcp("0.0.0.0:6188");
server.add_service(service);
server.run_forever();
}
这对应 Nginx 的:
upstream app {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
server {
listen 6188;
location / {
proxy_pass http://app;
}
}
如果再加主动健康检查,Pingora 官方示例会用 TcpHealthCheck,并把 LoadBalancer 放到 background service 里运行。看到这里,问题就变成了:你到底是想写一个自己的代理程序,还是想部署一个现成的反向代理?
什么时候直接用 Pingora
Pingora 适合下面这些场景:
- 你要学习代理链路,理解请求从 downstream 到 upstream 的生命周期。
- 你要把鉴权、灰度、限流、审计、路由规则写成 Rust 代码。
- 你要做自己的 API Gateway、Ingress、CDN 代理或内部网关产品。
- 你能接受维护自己的配置格式、证书加载、管理接口、日志指标和升级策略。
如果目标只是“把几个域名反代到几个服务,顺便配 HTTPS”,直接写 Pingora 不是最短路径。Pingora 能做到,但 TLS、ACME、配置热更新、访问日志、Web 管理和插件系统都需要你自己补。