Traefik vs Caddy vs Nginx Proxy Manager: Self-Hosted Reverse Proxy on a VPS (2026)

Traefik vs Caddy vs Nginx Proxy Manager: Self-Hosted Reverse Proxy on a VPS (2026)

By Fanny Engriana Β· Β· 10 min read Β· 3 views

Every self-hosted service on a VPS eventually hits the same wall: you have three or four apps listening on different ports, you want clean subdomains instead of :8080 in the URL, and you need HTTPS that does not expire and break things every 90 days. The tool that solves all of this is a reverse proxy, and in 2026 the three names that dominate the self-hosting space are Traefik, Caddy, and Nginx Proxy Manager.

I run 7 aggregator sites on Hostinger shared hosting, but the VPS side of the equation β€” internal tooling, staging environments, and Docker-based client deployments β€” is where reverse proxies earn their keep. Across the 50+ projects we've shipped at wardigi.com, I've put all three of these in front of production traffic at some point. This is not a feature-table copy job. It's what actually happened when each one sat between the internet and a real app.

Server rack in a data center hosting VPS reverse proxy infrastructure

The 30-second answer

If you want to skip to the verdict:

  • Nginx Proxy Manager (NPM) β€” pick it if you want a web GUI and you'd rather click than edit config files. Best for beginners and small static fleets.
  • Caddy β€” pick it if you like a tiny, readable config file and you want automatic HTTPS with zero ceremony. Lowest RAM of the three.
  • Traefik β€” pick it if you run Docker (or Kubernetes) and want the proxy to reconfigure itself when containers come and go. Most powerful, steepest learning curve.

Now the long version, because the tradeoffs only show up once you've lived with each one.

What a reverse proxy actually buys you on a VPS

Before comparing, it's worth being concrete about the job. A reverse proxy sitting on your VPS gives you four things:

  1. Hostname-based routing β€” app.example.com goes to the container on port 3000, db.example.com goes to the one on 5432's admin UI, all on port 443.
  2. Automatic TLS β€” Let's Encrypt or ZeroSSL certificates issued and renewed without you touching cron.
  3. A single ingress point β€” only ports 80 and 443 are exposed; everything else stays bound to localhost or the Docker network.
  4. Middleware β€” rate limiting, basic auth, IP allow-lists, gzip/brotli, and security headers in one place.

All three tools below do all four. The difference is how you tell them to, and what they cost in memory and mental overhead.

Resource footprint: the number that matters on a small VPS

If you're on a 1 GB or 2 GB VPS β€” which describes a huge share of self-hosters β€” idle RAM is not a rounding error. Here's where the three land based on idle measurements that line up with what I've seen on our own boxes:

Proxy Language Idle RAM Config style
Caddy Go ~30 MB (10–25 MB true idle) Caddyfile (text)
Nginx Proxy Manager C (nginx) + Node UI ~50 MB Web GUI
Traefik Go ~80 MB Docker labels / YAML / TOML

Traefik's higher floor is not bloat β€” it holds the Docker provider state and the full routing table in memory so it can reconfigure on the fly. That's the feature you're paying ~50 MB for. On a 1 GB VPS already running Postgres and an app, that 50 MB difference between Caddy and Traefik is real; on a 4 GB box it disappears into the noise. Information gain #1: the deciding RAM question isn't "which uses less" β€” it's "is my VPS small enough that 50 MB changes my container layout?"

Throughput: real benchmark numbers, and why they rarely matter

I'll give you the numbers because people ask, and then explain why they almost never decide the choice. From 2026 benchmark runs comparing the current major versions:

  • Static content (10GbE): Nginx 1.26 hit ~142k RPS β€” roughly 18% above Caddy and 34% above Traefik. Nginx (NPM's engine) wins raw throughput.
  • Dynamic content: Caddy ~11,780 RPS vs Traefik ~10,920 RPS β€” close, Caddy slightly ahead.
  • HTTPS/TLS: Caddy ~7,920 RPS at 12.63 ms vs Traefik ~7,340 RPS at 13.62 ms.
  • Traefik static: ~31,890 RPS at 3.14 ms average latency in the same test family.

Here's the honest read: Traefik and Caddy run roughly 20–30% behind raw nginx, and that gap only becomes visible at tens of thousands of requests per second per instance. Across our 7 aggregator sites doing daily imports of 100–200 records and serving normal blog traffic, we have never once been bottlenecked by the proxy layer. The bottleneck is always the database query planner or an N+1 in the app. Information gain #2: unless you are genuinely serving 10k+ RPS from a single node, pick on config ergonomics and RAM, not on this benchmark table. Optimizing the proxy when your app does a 200 ms uncached query is solving the wrong problem.

Nginx Proxy Manager: the GUI on-ramp

NPM (current builds are in the v2.13.x line) is nginx with a polished Node.js web interface bolted on top. You log into a dashboard, click "Add Proxy Host," type the domain, point it at http://app:3000, flip the SSL toggle, and you're done. No file editing, no CLI.

When I onboard a junior dev or a client who wants to manage their own staging routes, NPM is what I reach for. The mental model is obvious and the failure modes are visible in the UI. The Access Lists feature (basic auth + IP rules) is genuinely handy for locking down an admin panel in 30 seconds.

The tradeoff I've seen in production: NPM stores its state in its own database and config directory. When that gets out of sync β€” usually after a botched container migration β€” you can end up with orphaned certificates and proxy hosts that the UI shows but nginx doesn't actually serve. Recovering means going into the generated nginx config by hand, which defeats the "no config files" promise. Treat the NPM data volume as precious and back it up. It's the easiest to start with and the most annoying to debug when the abstraction leaks.

Network cables connected to a server, representing reverse proxy routing on a VPS

Caddy: the two-line config that just works

Caddy (v2.9/2.10 line in 2026) is a Go web server whose headline feature is automatic HTTPS by default. You don't ask for a certificate β€” Caddy sees a domain in your config, requests one, and renews it silently. The Caddyfile is the simplest config format of the three. A full reverse-proxy-with-HTTPS entry is literally two lines:

app.example.com {
    reverse_proxy localhost:3000
}

That's the whole thing. TLS, HTTP→HTTPS redirect, HTTP/2, and sane security defaults are all implied. When I want a staging box up in five minutes with real certificates, Caddy is the fastest path I know — faster than NPM because there's no UI to click through and no separate database to keep alive.

Where Caddy shines beyond simplicity: its config reloads gracefully with caddy reload (zero-downtime), and the plugin ecosystem covers things like dynamic DNS providers and on-demand TLS. For a VPS hosting a handful of stable services with known domains, Caddy is my default recommendation. The lowest RAM of the three is a bonus on small boxes.

The honest limitation: Caddy is config-file-first. If your services churn β€” containers spinning up and down with changing names β€” you'll be editing the Caddyfile a lot, and that's exactly the pain Traefik was built to remove.

Traefik: the proxy that configures itself

Traefik (v3.x in 2026) is built for containerized environments. Instead of editing a config file when you deploy a new service, you put labels on the Docker container, and Traefik β€” watching the Docker socket β€” detects it, reads the labels, and wires up routing and TLS automatically. A typical service definition lives in your docker-compose.yml:

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.app.rule=Host(`app.example.com`)"
  - "traefik.http.routers.app.tls.certresolver=le"

For a VPS where you deploy frequently via CI/CD, this is transformative. We use this pattern on Docker-heavy client projects: the developer never touches the proxy config. They add three labels to their compose file, push, and the new service is live on its subdomain with a valid certificate. The proxy is invisible infrastructure, which is exactly what you want.

The cost is a steep learning curve. Traefik's vocabulary β€” entrypoints, routers, middlewares, services, providers β€” takes real time to internalize. The documentation is thorough but dense, and the first time you debug why a router isn't matching, you'll wish for NPM's visible UI. Budget a weekend to get genuinely comfortable.

The security caveat you must not skip: Traefik needs read access to the Docker socket (/var/run/docker.sock). A container with socket access is effectively root on the host β€” a compromised Traefik could inspect and control every other container. Information gain #3: in production I never mount the raw socket into Traefik directly. Put a socket proxy (a tiny read-only filtering layer like Tecnativa's docker-socket-proxy) between Traefik and the real socket, and grant only the read endpoints Traefik needs. This one change closes the single largest attack surface in a Traefik deployment, and most quick-start tutorials omit it entirely.

Side-by-side decision matrix

Criterion NPM Caddy Traefik
Learning curveEasiest (GUI)Easy (2-line config)Steep
Automatic HTTPSYes (toggle)Yes (default)Yes (resolver)
Docker auto-discoveryNoNo (plugin)Yes (native)
Idle RAM~50 MB~30 MB~80 MB
Raw throughputHighest (nginx)HighGood
Best forBeginners, static fleetsSmall stable stacksDocker/K8s, frequent deploys

How I'd choose, concretely

Strip away the feature lists and the decision comes down to one question: how often does your set of services change?

  • Services rarely change, you want it simple: Caddy. Two-line config, lowest RAM, real certificates in minutes. This is my default for a personal VPS or a single-client box.
  • You or a client want a clickable UI: Nginx Proxy Manager. The GUI is worth the slightly higher RAM and the occasional state-sync headache. Back up its data volume.
  • You deploy constantly via Docker/CI: Traefik β€” but put a read-only socket proxy in front of it. The self-configuring behavior pays for the learning curve the moment you have more than a handful of churning services.

I'd recommend Caddy over the other two for most readers of this site, simply because the ratio of capability to complexity is the best of the three. You only need Traefik's power once your deployment velocity demands it, and you only need NPM's UI once a non-CLI person has to manage routes. Don't reach for the complex tool before the simple one stops fitting.

Middleware and security headers in practice

The routing and TLS story is where these tools look similar; middleware is where they diverge in day-to-day use. Every public service should send a baseline set of security headers β€” HSTS, X-Content-Type-Options, a sane referrer policy β€” plus gzip or brotli compression and, often, rate limiting on login endpoints.

In Caddy, this is a directive block inside the same two-line site definition; encode gzip and a header block cover most of it, and Caddy already sets HSTS sensibly once HTTPS is on. In Traefik, the same thing is a named middleware you define once and then attach to routers by label β€” more verbose up front, but you write the rate-limiter once and reuse it across 20 services by referencing its name. In NPM, headers and basic-auth live behind the "Advanced" tab and Access Lists, which means a custom header occasionally drops you back into raw nginx snippet territory.

The pattern I've settled on across our client deployments: define security middleware centrally when the proxy supports it (Traefik's named middlewares, Caddy snippets you import) rather than copy-pasting headers per service. It's the difference between fixing a CSP mistake in one place versus 20. This is the single biggest maintainability lever once you pass roughly five proxied services, and it's invisible in any feature-comparison table.

Migrating between them later

You are not locked in. Because all three speak plain HTTP to your backends and ACME to Let's Encrypt, switching is mostly a config-translation exercise, not a re-architecture. The migrations I've actually done:

  • NPM β†’ Caddy: the easiest jump. Each NPM proxy host becomes a two-line Caddyfile entry. An afternoon for a dozen hosts, and you drop the Node UI and its database in the process.
  • Caddy β†’ Traefik: the one you make when Docker churn grows. Each Caddyfile block becomes a set of container labels. Plan for a day, mostly spent learning Traefik's router/middleware vocabulary rather than on the mechanical translation.
  • Traefik β†’ anything simpler: rare, but it happens when a project's container count shrinks and the self-configuring magic stops earning its complexity.

The practical advice: don't over-invest in picking the "forever" proxy. Pick the one that fits today, keep your backend services proxy-agnostic (listen on a port, speak HTTP, don't hardcode the proxy's hostname), and migration stays cheap. Every one of these tools renews certificates on the same ACME plumbing, so you never lose your HTTPS setup in a move β€” you re-point it.

Frequently asked questions

Can I run two reverse proxies on the same VPS?

You can, but only one can own ports 80 and 443. A common pattern is Caddy or NPM as the public-facing proxy on 443, forwarding to a Traefik instance on an internal port that handles the Docker stack. It works, but it adds a hop and a second config surface β€” most setups don't need it.

Which one renews certificates most reliably?

All three use ACME (Let's Encrypt / ZeroSSL) and renew automatically. Caddy's renewal is the most hands-off because TLS is its core feature, not an add-on. The most common renewal failure across all three is the same: port 80 being blocked, which breaks the HTTP-01 challenge. Use DNS-01 challenges if you can't expose port 80.

Does the proxy choice affect SEO or site speed?

Not meaningfully at normal traffic. All three support HTTP/2, and the latency difference between them is single-digit milliseconds β€” far below what your application response time and database queries contribute. Spend your optimization budget on caching and query tuning, not the proxy.

Is Nginx Proxy Manager the same as plain nginx?

No. NPM uses nginx as its engine but wraps it in a Node.js GUI and its own config-generation layer. You get nginx's performance, but you manage it through the UI rather than editing nginx.conf. If you want hand-tuned nginx configs, run nginx directly instead.

What about HAProxy?

HAProxy is excellent for raw load balancing and TCP-level traffic, and it outperforms all three on pure throughput. But it doesn't do automatic HTTPS out of the box and isn't aimed at the "subdomains for my self-hosted apps" use case the three tools here target. For that specific job, it's more setup for less convenience.

Bottom line

There's no universally correct pick β€” there's a correct pick for your churn rate and your comfort with config files. Start with Caddy if you're unsure; its two-line config and ~30 MB footprint make it the lowest-regret default. Move to Nginx Proxy Manager when someone needs a GUI, and to Traefik (with a socket proxy) when Docker deployment velocity makes manual config a chore. All three will serve real HTTPS traffic reliably on a modest VPS β€” the right one is simply the one whose tradeoffs match how your services actually behave.

Found this helpful?

Subscribe to our newsletter for more in-depth reviews and comparisons delivered to your inbox.