Pyo Gateway
How web requests and public TCP and UDP ports reach your services, and why the gateway can't read your secrets.
Every web request to your sites, and every connection to a public TCP or UDP port, goes through Pyo Gateway on the server it reaches. For websites, the gateway picks the certificate for the domain and passes the request to one of the service's replicas. Pyo Gateway is built on Envoy. Pyo installs and updates it for you.
How a request travels
- A visitor's request reaches a server in your cluster. Every server runs the gateway on ports 80 and 443, including GPU servers. Build-only servers don't. A cluster with public traffic turned off answers on none of them. With free domains on Tailscale, each server also answers on its Tailscale address, whether public traffic is on or off.
- The gateway answers on HTTPS with the domain's own certificate. That's a Let's Encrypt certificate, a self-signed one for a private domain, or one you uploaded. Plain HTTP redirects to HTTPS if the domain is set to redirect. Otherwise, plain HTTP gets an answer too.
- The gateway sends the request to the service, and the cluster picks a replica. It picks one on the same server, in the same region or anywhere, based on the cluster's traffic routing.
Deleting a service removes its routes and certificates. Every 10 minutes, Pyo also removes any routes left without a service.
The gateway sees the visitor's real address when traffic arrives on one of the addresses the cluster lists for its servers. On the server Pyo runs on, this also works for its tailnet address, and so does every server's Tailscale address while free domains use it. Traffic that reaches a server on any other address seems to come from the cluster's load balancer. Then the firewall, rate limits and analytics can't see the visitor. The gateway never takes the visitor's address from the X-Forwarded-For header, because any client can write any address there. Behind a tunnel, a CDN or another proxy, every visitor seems to come from it. If it can send PROXY protocol, turn that on.
PROXY protocol on websites
If your cluster sits behind your own load balancer or tunnel that sends PROXY protocolA short header a load balancer adds at the start of each connection, saying who the visitor really is., turn on Accept PROXY protocol on web traffic. It's in Traffic on the cluster's Settings tab. Websites then see each visitor's real address, and so do analytics. Connections without the header keep working.
With this on, anyone who can reach your servers directly can claim any address. Only use it when your servers accept traffic from that load balancer and nothing else.
TCP and UDP ports
Public TCP and UDP ports, for a game server or a database for example, go through the gateway too, unless you turn it off for a port.
For both, the gateway counts connections in analytics. For TCP ports, it can also:
- allow or block addresses and ranges
- limit how many connections a port has open at once
- send PROXY protocol, version 1 or 2, to your service, so it learns each visitor's address. Your service must expect it.
- accept PROXY protocol from a load balancer in front of Pyo. This only works for protocols where the client speaks first, like HTTP and TLS. Connections may stall with protocols where the server speaks first, like SSH, SMTP or MySQL, or with very short client messages.
UDP ports go through the gateway and are counted, but have none of these options.
The firewall works on top of these, per service. It also covers UDP ports and ports outside the gateway.
A port with the gateway turned off is opened directly on the servers. Your service sees each visitor's address itself, but the port has no analytics or options.
The problem with most proxies
Most web proxies for clusters, including Traefik and ingress-nginx, work the same way by default. Traefik's standard install, which comes with k3s, is one example. Here's why that matters for Pyo:
- Each site's HTTPS certificate is stored as a SecretWhere the cluster stores passwords, keys and certificates.. The cluster requires it to be in the same namespaceA named area of the cluster. In Pyo, each project has its own. as the route that uses it, which in Pyo is the service's project.
- Pyo keeps each service's environment variables, like database passwords and API keys, as Secrets in that same project.
- The proxy serves every site, so it has to find every certificate. The cluster's permissions can't say "only Secrets that are certificates". So the proxy gets read access to all Secrets in every project it serves, including variables.
- That proxy is the one program that handles public traffic. If it ever has a bug, whatever it can see is within reach. That includes every project's variables. In 2025, a bug in ingress-nginx called IngressNightmare (CVE-2025-1974) showed how this can happen.
- Narrowing the proxy to fewer projects doesn't help. It still serves every project, and each project keeps certificates and variables side by side.
A gateway that holds no keys
Pyo should be secure out of the box, without settings you have to know to turn on. So instead of the default most proxies use, Pyo Gateway makes two changes together:
- Certificates live in one place of their own, separate from every project. Projects then only hold your services' secrets.
- The part that handles public traffic holds no cluster credentials, so it has no way to read Secrets.
Traefik can do the first with the Gateway API, but not the second. It's one program that both handles public traffic and reads certificates from the cluster. Pyo Gateway is built in two parts, so it does both:
- A controller, which never handles public traffic, reads the routes and only the certificates they use.
- The proxies, which handle public traffic, hold no cluster credentials at all. They get their routes and certificates from the controller.
TraefikCan see every project
Envoy GatewaySees no Secrets
The proxies only ever see the certificates they're serving. They can't read Secrets or talk to the cluster.
In Pyo, the controller and the certificates live in their own area, pyo-gateway, and the controller only reads there. It can't read a project's Secrets. The Security page shows this for each cluster as Gateway can't read your secrets.
Pyo Gateway is also built for the Gateway APIThe standard way to describe, for a cluster, which domains and paths go to which services. from the start, not added on later. Each domain of a service gets one route, and certificates are kept in one place. Pyo makes sure a domain belongs to only one service across your whole Pyo, and refuses it for any other.
The trade-off is small. Per server, the gateway uses about as much memory as Traefik. The controller adds about 40 MB, once per cluster.
Compared with Traefik
| Feature | Pyo Gateway | Traefik |
|---|---|---|
| Security | ||
| Part handling public traffic holds cluster credentials | Yes: No | No: Yes |
| Can read project secrets | Yes: No | No: Yes, by default, and still in every project it serves when narrowed |
| Where certificates live | Yes: In one place, read only by the controller | Partly: Next to each service |
| Resources | ||
| Memory | Partly: About 35 MB per traffic server, plus about 40 MB once per cluster. About 20 public ports add about 3 MB. | Yes: About 40 MB per server |
| CPU under load | Yes: Typically lower: about 0.2 of a core at 1,000 HTTPS requests a second | Partly: Typically higher |
| Comes from | Yes: Pyo, updated with Pyo's releases | Yes: Included in k3s |
| Routing | ||
| Gateway API | Yes: Built for it | Partly: Partly |
| TCP ports | Yes: Yes, with IP rules, connection limits and PROXY protocol | Yes: Yes |
| UDP ports | Yes: Yes, with firewall rules but no per-port limits | Yes: Yes |
What stays the same
- Traffic routing: Local, Regional or Global.
- The visitor's real address, also from a load balancer that sends PROXY protocol.
- The CDN certificate check, turned on per cluster.
- Redirecting HTTP to HTTPS, for domains set to redirect.
- Let's Encrypt's checks, which reach your server through the gateway.
- Free domains, and certificates issued for you.
- Analytics, read from the gateway's request log.