{"slug":"what-is-pingora-rust-proxy-framework","url":"https://tako.sh/blog/what-is-pingora-rust-proxy-framework/","canonical":"https://tako.sh/blog/what-is-pingora-rust-proxy-framework/","title":"What Is Pingora? The Rust Proxy Framework Behind Cloudflare and Tako","date":"2026-05-21T13:52","description":"A plain-language guide to Pingora, Cloudflare's Rust proxy framework, and why Tako uses it to build programmable VPS app routing.","author":null,"image":"92cd16dbe117","imageAlt":null,"headings":[{"depth":2,"slug":"pingora-in-plain-english","text":"Pingora in plain English"},{"depth":2,"slug":"the-core-idea-a-programmable-request-lifecycle","text":"The core idea: a programmable request lifecycle"},{"depth":2,"slug":"what-tako-builds-with-pingora","text":"What Tako builds with Pingora"},{"depth":2,"slug":"why-rust-matters-here","text":"Why Rust matters here"},{"depth":2,"slug":"when-you-should-care-about-pingora","text":"When you should care about Pingora"}],"markdown":"Pingora is easy to misunderstand if you meet it through a headline.\n\nIt is not \"Nginx, but Rust.\" It is not a Caddyfile with different syntax. It is not Traefik with labels. [Pingora](https://github.com/cloudflare/pingora) is a Rust framework for building programmable network services, including HTTP proxies, load balancers, gateways, and custom traffic systems.\n\nThat distinction matters. A normal reverse proxy is something you configure. Pingora is something you build with.\n\nFor Tako, that is the whole point. We do not need a proxy that only knows how to forward `example.com` to `127.0.0.1:3000`. We need a proxy that can participate in deploys, route matching, TLS selection, static asset serving, app health, cold starts, and scale-to-zero. Pingora gives us the request machinery; Tako supplies the app platform around it.\n\nThis is the beginner version. For the lower-level request diagram, read [Cloudflare Pingora Architecture Diagram](/blog/cloudflare-pingora-architecture-diagram/). For the proxy selection story, read [Pingora vs Caddy vs Traefik](/blog/pingora-vs-caddy-vs-traefik/).\n\n## Pingora in plain English\n\nPingora is a set of Rust crates that handle the hard parts of proxying and network services: accepting connections, running an async server, speaking HTTP, choosing upstreams, handling TLS, pooling connections, shutting down gracefully, collecting metrics, and letting your code hook into the request path.\n\nThe important phrase is \"letting your code hook in.\"\n\n| If you want...                             | Use a standalone proxy | Use Pingora                        |\n| ------------------------------------------ | ---------------------- | ---------------------------------- |\n| A config file in front of one app          | Yes                    | Probably too much work             |\n| Automatic HTTPS with minimal setup         | Caddy is great         | You implement the certificate flow |\n| Docker or Kubernetes service discovery     | Traefik is great       | You implement discovery            |\n| A proxy embedded in your Rust platform     | Awkward                | This is the fit                    |\n| Request routing based on app runtime state | External glue          | Put it in the proxy logic          |\n\nIn a traditional setup, the proxy is a separate process with its own config and lifecycle. Your deploy script edits config, reloads the proxy, starts processes somewhere else, and hopes every piece agrees.\n\nWith Pingora, the proxy can be part of your program. That program can keep a route table in memory, ask a process manager which app instances are healthy, serve one request directly, wake a sleeping app for the next request, and forward traffic only when an upstream is ready.\n\nThat is why it feels less like \"install this proxy\" and more like \"build the edge you need.\"\n\n## The core idea: a programmable request lifecycle\n\nPingora's HTTP proxy model is built around the `ProxyHttp` trait. The docs show it as a lifecycle: a new request enters, your code can inspect it, your code chooses an upstream peer, your code can adjust the request before it goes upstream, Pingora forwards it, your code can observe or adjust the response, and logging runs at the end.\n\nHere is the shape without the Rust syntax:\n\n```d2\ndirection: right\n\nrequest: \"incoming request\" {\n  style.fill: \"#FFF9F4\"\n  style.stroke: \"#2F2A44\"\n}\n\nfilter: \"request_filter\\ninspect or answer early\" {\n  style.fill: \"#9BC4B6\"\n}\n\npeer: \"upstream_peer\\nchoose backend\" {\n  style.fill: \"#9BC4B6\"\n}\n\nupstream: \"upstream request\\nrewrite headers\" {\n  style.fill: \"#FFF9F4\"\n  style.stroke: \"#2F2A44\"\n}\n\nresponse: \"response filters\\nobserve or adjust\" {\n  style.fill: \"#FFF9F4\"\n  style.stroke: \"#2F2A44\"\n}\n\nlog: \"logging\\nmetrics and cleanup\" {\n  style.fill: \"#E88783\"\n}\n\nrequest -> filter\nfilter -> peer: \"continue\"\nfilter -> log: \"direct response\"\npeer -> upstream\nupstream -> response\nresponse -> log\n```\n\nThe required part is choosing the upstream. The rest of the hooks are where a platform becomes interesting.\n\nA small gateway might use `request_filter` to block private paths and `upstream_peer` to choose between two backend pools. A feature-flag proxy might parse a header once, store the result in per-request context, then use that context later when picking an upstream. An app platform like Tako uses the same idea for app routing and lifecycle.\n\nThe key difference from a static config is that these decisions can be live Rust code. They can use data structures, async state, metrics, locks, caches, app registries, and whatever rules your platform owns.\n\n## What Tako builds with Pingora\n\nTako users do not configure Pingora directly. They write app config:\n\n```toml\nname = \"api\"\nruntime = \"bun\"\n\n[envs.production]\nroutes = [\"api.example.com\", \"example.com/api/*\"]\nservers = [\"prod\"]\n```\n\nWhen you run [`tako deploy`](/docs/deployment/), Tako uploads the app, prepares the runtime, registers routes, starts instances, waits for SDK readiness, probes health, and shifts traffic. `tako-server` then uses Pingora as the HTTP and HTTPS edge for that app state.\n\nInside Tako, Pingora is the listener and request engine. Tako adds the app decisions:\n\n| Pingora provides          | Tako adds                                               |\n| ------------------------- | ------------------------------------------------------- |\n| HTTP and HTTPS listeners  | Route declarations from [`tako.toml`](/docs/tako-toml/) |\n| Request lifecycle hooks   | App selection by host and path                          |\n| Upstream peer selection   | Healthy native process instances                        |\n| TLS integration points    | SNI certificate lookup and ACME management              |\n| Metrics and logging hooks | App-scoped logs and request metrics                     |\n| Proxy forwarding          | Static asset fast path, channels, images, cold starts   |\n\nThat last row is the part a config-only proxy cannot know by itself. If a request matches `example.com/api/*`, Tako checks the route table, looks for Tako-owned endpoints, serves static assets when possible, and only then resolves a backend process.\n\nIf the app has been scaled to zero, backend resolution can trigger a cold start. The first request becomes the leader, the process starts, the SDK reports its bound port over file descriptor 4, the server probes the status endpoint, and waiting requests continue once the instance is healthy. From Pingora's point of view, Tako is just choosing an upstream. From the user's point of view, a sleeping VPS app woke up on demand.\n\n## Why Rust matters here\n\nPingora being Rust is not just a branding detail. It means the proxy can live in the same language and async runtime as the rest of `tako-server`.\n\nTako's server is Rust. The app registry, load balancer, TLS manager, cold-start manager, static asset handling, image optimizer integration, and Pingora proxy all live in one binary. There is no separate Go plugin, no generated Nginx config, and no sidecar process whose state has to be reconciled after a deploy.\n\nThat does not make Pingora the best tool for every job. It makes it a strong foundation when the proxy is not an accessory. If you are building infrastructure where routing decisions depend on application state, Rust code inside the proxy is simpler than shelling out to another product's admin API.\n\n## When you should care about Pingora\n\nIf you are deploying one personal app and just need HTTPS, you probably do not need to care. Use Caddy, or use Tako and let Tako hide the proxy entirely.\n\nYou should care about Pingora when you want to build a proxy-like system rather than configure one:\n\n| You are building...                | Why Pingora fits                              |\n| ---------------------------------- | --------------------------------------------- |\n| A custom API gateway               | Request hooks are first-class                 |\n| A load balancer with unusual rules | Upstream selection is code                    |\n| A deploy platform                  | Proxy decisions can see app lifecycle state   |\n| A cache or traffic service         | Request and response filters are programmable |\n| A Rust infrastructure daemon       | The proxy can be embedded in the same binary  |\n\nThat is the short answer to \"what is Pingora?\" It is Cloudflare's Rust framework for programmable network services. It gives you the proxy engine, but not the product opinion.\n\nTako is one product opinion built on top: a VPS app platform where [`tako.toml`](/docs/tako-toml/), [`tako deploy`](/docs/deployment/), TLS, routing, process health, scale-to-zero, and local dev through [`tako dev`](/docs/development/) all share the same control plane.\n\nPingora is the framework. Tako is what we built with it."}