There is a genre of blog post that puts Caddy, Nginx, and Traefik in a table, assigns each a memory figure in megabytes, and declares a winner for “small VPS.” The tables rarely include the config files, the backend, the payload, or the kernel version. They almost never test on a 2 GB box. Treat them as entertainment.
The useful question on a 2 GB VPS is not which proxy has the lowest idle RSS in someone else’s container. It is which proxy’s operational model fits the way you actually add, remove, and reload services on a one-to-ten-machine fleet — and then how you measure the memory and latency consequences on your own hardware, because nobody else’s numbers transfer.
Why 2 GB changes the question
On a 2 GB VPS the reverse proxy is not the workload. It is one process among mail, DNS, a monitoring agent, a backup timer, and whatever the proxy is fronting. The proxy’s idle footprint matters because it competes with everything else for the same page cache and the same OOM killer attention. Its restart behavior matters because a config reload that briefly doubles memory can be the difference between a clean reload and a killed process.
That reframes the comparison. Peak throughput at 5,000 requests per second is irrelevant if your box serves forty. What matters is: how much memory does the process hold when nothing is happening, how much does it grow under your real request rate, and what happens to in-flight connections when you change a route.
No retrieved source tests any of these proxies at 2 GB specifically. That is the honest starting point. The numbers you need are the numbers you measure.
What the official documentation actually commits to
Strip away the marketing and each project documents a distinct operational model. That model, not a benchmark table, is what you live with at this scale.
Nginx: a static config file and a worker model
Nginx’s core documentation describes a master process and worker processes. The defaults are conservative: worker_processes defaults to 1, and the docs suggest setting it to the number of available CPU cores when in doubt. worker_connections defaults to 512, and the documentation is explicit that this count includes connections to proxied servers, not only client connections — a detail that trips people up when sizing a proxy in front of several backends.
accept_mutex is off by default, and the docs note it is unnecessary on systems supporting the EPOLLEXCLUSIVE flag or when using reuseport. These are the knobs that determine how Nginx behaves on a small box, and they are documented precisely. The operational model is a config file, a syntax check, and a reload. There is no service discovery built in; you write a server block per site.
Caddy: config file or API, with a documented service lifecycle
Caddy’s documentation describes it as a server of servers, configured natively in JSON but most commonly through the Caddyfile via config adapters. It compiles for all major platforms and has no runtime dependencies. The configuration can be driven dynamically through its API.
The running documentation is unusually specific about lifecycle: on systemd Linux, use the official unit files, with separate units for Caddyfile-driven and API-driven configuration. And the docs are blunt about reloads: “Do not stop the service to change Caddy’s configuration. Stopping the server will incur downtime. Use the reload command instead.” That single sentence tells you more about operating Caddy on a small box than any latency chart: the intended workflow is graceful reload, not restart.
Traefik: discovery as the primary model
Traefik’s documentation frames the product around four concepts: entrypoints, routers, services, and providers. The provider is the important one. In the docs’ words, providers are infrastructure components — orchestrators, container engines, cloud providers, key-value stores — and Traefik “queries the provider APIs in order to find relevant information about routing, and when Traefik detects a change, it dynamically updates the routes.”
That is a fundamentally different operating model from a static config file. You do not edit a proxy config to add a service; you tell the provider about the service and Traefik notices. Whether that is an advantage depends entirely on how often you add services and whether they live in containers.
The three operational models in practice
For a one-to-ten-machine fleet, the decision usually comes down to this:
- Static config (Nginx). Every new site is an edit, a syntax check, and a reload. Verbose, but the state of the proxy is fully described by files you can read, diff, and back up. If you add a service twice a year, this is not a burden.
- Config file or API (Caddy). The Caddyfile is compact, and the API path allows programmatic changes. The documented reload workflow is graceful. If you want a small, readable config and a service manager that handles restarts, this model is coherent.
- Discovery (Traefik). The proxy config is largely derived from your infrastructure. If you run a Docker Compose stack and add containers regularly, the proxy config stops being a thing you maintain. If you run three systemd services on one box, you have adopted a discovery layer to solve a problem you do not have.
A hypothetical worth sitting with: a 2 GB VPS running mail, DNS, and a monitoring agent might leave only a few hundred megabytes of comfortable headroom. In that situation, a proxy whose idle footprint you have measured on that exact box beats one whose idle footprint a blog post claims. The measurement is cheap; the wrong choice is not.
Another: if you add a new service weekly, a discovery-driven proxy may save more operator time than a static-config proxy saves milliseconds. If you add a service twice a year, the reverse is likely true. Neither statement is a benchmark result. Both are scheduling arithmetic.
A reproducible benchmark plan for your own VPS
Published benchmarks are useful as methodology templates, not verdicts. Two open-source repositories are worth reading for their harness design even if you never run them as-is.
The reverse-proxy-benchmark repository compares Nginx, Caddy, Traefik, and HAProxy under Vegeta across HTTP/1.1, HTTPS/1.1, and HTTPS/2. Its design choices are the instructive part: a fixed backend, a fixed payload, a configurable request rate and duration, a warmup period, and shuffled run order to reduce fixed-order bias. It also runs a restricted mode where each proxy is limited to 2 CPU cores and 4 GB RAM via Docker Compose — still not 2 GB, but the shape of the constraint is right.
The benchmark-servers repository takes a different approach: wrk against a CPU-intensive endpoint, with each proxy limited to 1 CPU and 512 MB RAM. That is closer to the memory pressure of a small VPS, though the workload is synthetic.
Neither repository gives you a number you should adopt. What they give you is a harness shape. Copy it:
- Fix the backend. One service, one response size, one behavior. Do not benchmark against a static file server if your real traffic hits an application.
- Fix the payload and the rate. Use your actual request rate, not the maximum the box can survive. On a 2 GB VPS that is probably tens of requests per second, not thousands.
- Measure RSS, not just latency. Sample the proxy process’s resident set size at idle, after warmup, and at your target rate. The idle figure is what competes with mail and DNS; the under-load figure is what determines whether a reload is safe.
- Measure p50 and p99 separately. Tail latency is where small boxes hurt. A proxy that adds two milliseconds at the median and forty at the tail behaves very differently from one that adds five and ten.
- Test the reload. Change a route while traffic is flowing and watch memory and error rates. Caddy’s docs tell you to reload rather than stop; verify what that costs on your box.
- Run each proxy more than once, in shuffled order. Thermal state, page cache, and background timers all drift.
This is an afternoon of work. It produces numbers that apply to your hardware, your kernel, and your co-tenants — which is more than any published table can offer.
Decision checklist for small operators
Work through these in order. The first few usually settle it before you get to performance.
- How many services, and how often do they change? Two or three services that change quarterly: a static config file is fine. A rotating set of containers: discovery earns its complexity.
- Are containers in play? If yes, Traefik’s provider model matches the infrastructure. If everything is systemd units, it adds a layer you will maintain for no routing benefit.
- How do you want certificates and reloads handled? Read each project’s running documentation and decide whether the documented workflow matches your tolerance for moving parts. Caddy’s docs are explicit that reload, not stop, is the intended path; Nginx’s model is a config test and a reload; Traefik’s model is that the provider tells it what changed.
- What is your memory headroom before you start? Enumerate what else runs on the box. If the answer is “not much,” measure idle RSS for each candidate on that box before committing. Do not pick a proxy and hope it fits.
- What is your tolerance for config verbosity? This is a real cost at 3 a.m. A config you can read without documentation is worth something.
What to ignore
Treat “best reverse proxy” claims that lack published configs, hardware details, and reproducible scripts as entertainment. This includes the table you may have read that assigns Nginx 12–25 MB idle, Caddy 40–80 MB, and Traefik 60–120 MB. Those numbers may or may not be accurate on some machine, but without the config files, the backend, the payload, and the kernel, they are not evidence about your VPS. The same applies to percentage claims about throughput and latency. The retrieved benchmark repositories report findings, but their numbers are environment-specific and are not a basis for a general rule.
The one thing the documentation does establish is the operating model. Nginx gives you a static config and a worker model with documented defaults. Caddy gives you a config file or API and a documented reload discipline. Traefik gives you discovery through providers. On a 2 GB VPS with one to ten machines, that model — not a millisecond — is what you will spend your time on.
FAQ
Is there a documented memory figure for any of these proxies on a 2 GB VPS?
Not in the sources reviewed here. The official documentation describes configuration and process models, not memory footprints. The benchmark repositories impose resource limits (2 CPU / 4 GB in one, 1 CPU / 512 MB in the other) but do not report a 2 GB VPS result. Measure on your own hardware.
Which proxy is fastest?
The retrieved benchmark repositories report different leaders in different scenarios, which is the expected outcome when the workload, payload, and resource limits differ. There is no basis in these sources for a general ranking. At the request rates a 2 GB VPS typically serves, the difference is unlikely to be the constraint you notice first.
Do I need service discovery for three services?
Probably not. Traefik’s provider model is documented as querying infrastructure APIs and updating routes dynamically. That is valuable when the set of services changes often and is described by an orchestrator. For three systemd units that change twice a year, a static config file is simpler to reason about and back up.
How should I apply a config change without downtime?
Follow each project’s documented workflow. Caddy’s running documentation is explicit: do not stop the service to change configuration; use the reload command. Nginx’s model is a config test followed by a reload. Traefik’s model is that the provider detects the change. In all three cases, verify on your own box what the reload does to memory and in-flight connections.
Can I trust a benchmark I found online?
As a methodology template, yes — look at how it fixes the backend, payload, rate, and run order. As a verdict for your hardware, no. The retrieved repositories are useful precisely because they publish their harness and invite you to run it yourself.