What belongs in front of your services is not the fastest reverse proxy but the one whose defaults you are willing to live with for three years. Defaults, not feature tables, decide what happens to you on a Thursday evening. Below is what holds up against primary sources as of 19 September 2026: who owns your certificates, what tears on a config reload, what your vulnerability scanner will not show you, and what each of these choices costs to operate.
A word on the genre first. This is not a point-by-point comparison and it does not end with “pick this one”. It ends with three questions whose answers narrow the field to a single option, and with the price of each answer. Every version, date and vulnerability identifier here was checked against release pages, documentation and project advisories, and the links are at the end.
- Half of these installs solve a problem you do not have
- Choosing a proxy means choosing who owns your certificates
- You are choosing defaults, not software
- The real benchmark happens when you reload the config
- The certificate became a consumable, and your proxy has to know it
- Two replicas is the dividing line
- The proxy control plane is a door with no lock
- The proxy lies to your backend, and that is a whole defect class
- HTTP/3 is the softest spot on the perimeter, and Caddy turns it on by default
- One project has gone silent for 108 days, the other patches every eleven, and both answers are bad
- Your vulnerability scanner will not show you some of these holes
- It is not a two-way race: nginx and HAProxy learned the thing they were replaced for
- The benchmark measures something other than what hurts
- I choose with three questions, not a comparison table
- What I deliberately do not do
- The stack
- Why this matters if you are paying
- Frequently asked questions
- What should I pick for one server and a dozen services?
- Is it true that you cannot run two Traefik instances with Let’s Encrypt?
- Do I need to move to short-lived certificates now?
- How alarming are Traefik’s 48 security advisories in 2026?
- Can I leave the Caddy admin API enabled?
- Is mounting the container daemon socket for auto-discovery worth it?
- nginx cannot do certificates on its own, so why is it in this comparison?
- Sources
- Need a consultation?
Half of these installs solve a problem you do not have

A separate reverse proxy earns its place through exactly three things: several services sharing one address; certificates somebody has to renew without you; and headers about the client that need to be trusted in one place rather than in every application. With one service and one domain you are not buying convenience, you are buying another process on the perimeter and another stream of advisories to read.
This layer sits below and beside what I covered in the piece on cluster ingress. There the ground moved sharply: ingress-nginx was retired in March 2026 and the Ingress API itself is frozen. Tellingly, Traefik’s own documentation now says so too, from version 3.7.13 onward. But this article is about the proxy outside a cluster, on the edge of ordinary infrastructure, where the decisions run differently.
And immediately, what neither product contains, because half of all disappointment starts here. A standard Caddy build ships 39 directives, and none of them is a cache, a rate limiter, a web application firewall or layer 4 handling. Open-source Traefik has no HTTP caching, no static file serving and no application firewall either. If that is what you came for, you came to the wrong layer.
Choosing a proxy means choosing who owns your certificates
The real dividing line is not performance. It is three questions: where the private key lives, who renews it, and what happens when renewal fails. The two products answer them almost oppositely, and both answers are uncomfortable.
Caddy accepts traffic before it has a certificate, then keeps trying: the next challenge type, the next issuer, a backoff stretching to a day, and it will go on doing that for thirty days. Its storage is handled carefully: atomic writes, 0600 permissions, a global cache of ten thousand certificates. The price of that behaviour is that the site is already serving while a valid certificate does not yet exist.
Traefik puts everything in a single acme.json and checks its permissions: if any group or other bits are set, the resolver silently drops out of the list while the process carries on living. When issuance fails, Traefik serves a self-signed TRAEFIK DEFAULT CERT, and for new domains that state persists until a restart. So one of them serves traffic on a certificate it has not got yet, and the other looks healthy, logs very little, and hands the browser a warning.
You are choosing defaults, not software
This is the core section. A config copied from somebody’s blog post carries their threat model with it, and the defaults in both products diverge from what people expect of them.
What surprises people in Caddy:
- the order of ACME challenges is random rather than fixed, so somebody else’s issuance story cannot be reproduced;
- enabling the DNS challenge disables the other two, and that is behaviour rather than a documentation slip;
- the ZeroSSL fallback issuer only works when an email address is configured; without one there is no fallback at all;
- load balancing defaults to
random, not round-robin as people usually assume; reverse_proxyhas neither active nor passive health checks andlb_retriesis zero, so a dead backend keeps receiving requests;- there are no response read timeouts whatsoever; the only one set is a three-second
dial_timeout; - inbound
X-Forwarded-*headers are ignored, so behind a CDN you will not see the real client address withouttrusted_proxies; - 0-RTT is on by default and conflicts with matching on the client address.
What surprises people in Traefik:
exposedByDefaultis true: the provider publishes every container until you say otherwise;api.dashboardcomes up together with the API, so the dashboard arrives as a side effect;writeTimeoutis disabled;- the rate limiter’s
averageis zero, and in that state it limits nothing at all while looking enabled in the config; - all seven
encodedCharactersswitches permit dangerous encoded characters by default; - an IP allow list with
depthfilters on the client-suppliedX-Forwarded-For, and withoutipv6Subnetit is bypassed over IPv6.
None of these is a developer mistake; each has its own logic. The problem is that together they form a behaviour profile you acquire silently, and half of it appears in no installation guide anywhere.
The real benchmark happens when you reload the config
An edge proxy gets reloaded often: a new domain, a new service, a new certificate. That second is where the architecture shows its price, not under a synthetic load. Four products, four different mechanics.
- Caddy brings the whole new config up before tearing the old one down, so both live in memory for a while. The grace period is infinite by default, yet proxied streams are cut immediately because
stream_close_delayis zero. Reloading is only possible through the admin API, SIGHUP is ignored, and the reload itself aborts in-flight ACME transactions. - Traefik throttles provider events by two seconds, which is cheap. Changing the install configuration, however, requires a full restart with a
graceTimeOutof ten seconds. - nginx forks a new generation of workers, and
worker_shutdown_timeoutis not set by default at all: the old workers live as long as their connections do. - HAProxy is the only one that documents the price as a number: roughly one lost connection per ten thousand new connections per second.
And one detail that says a great deal about how mature this subject really is: graceful configuration reload in Traefik is sold as a feature of the paid product. Something widely assumed to be table stakes turns out to be a line on a price list.
The certificate became a consumable, and your proxy has to know it
The industry clock is already running, and it changes what you need from automation. Maximum certificate lifetime: 200 days from 15 March 2026, 100 days from 15 March 2027, 47 days from 15 March 2029. Let’s Encrypt is running ahead of that schedule and reaches 45 days by February 2028.
Three surrounding things changed too, and all of them break habits. Expiry emails stopped on 4 June 2025, so watching the clock is now entirely your job. OCSP responders were switched off on 6 August 2025, which means the ssl_stapling blocks lovingly copied out of posts written between 2019 and 2023 are dead code today. Multi-perspective validation is mandatory, and from 15 March 2026 so is DNSSEC validation.
Then comes the price of each implementation. Caddy renews at the last third of the actual lifetime and has supported renewal hints from the certificate authority since version 2.8, so it follows the authority’s schedule. Traefik computes from a fixed table driven by certificatesDuration, still does not support those hints, requests RSA-4096 by default, and renews even certificates nobody is using.
Which produces the most expensive trap in this section. Enabling the short-lived profile in Traefik while leaving certificatesDuration alone makes the renewal condition true on every daily pass. Renewal fires again and again until you hit the duplicate-certificate limit for that set of names. This follows from the code and the published limits rather than from something I watched happen, and testing it on a live perimeter is not advice I would give.
Two replicas is the dividing line
While there is one instance, the difference between these products is a matter of taste. The moment there are two, it becomes architectural.
Traefik’s documentation says it outright: you cannot run several instances with Let’s Encrypt enabled. It points you at either the commercial edition or an external certificate controller. The wording there still refers to Traefik 2.0 while the current branch is 3.7, which is itself a statement about how long ago anyone looked into that corner.
Caddy’s answer reads better on paper: clustering reduces to shared storage, with a file lock refreshed every five seconds and considered stale after ten. But the standard build carries exactly one storage module, the local filesystem, and all twenty registered storage modules are third-party and of varying quality. So you buy high availability either way: from Traefik with money or an extra component, from Caddy with somebody else’s module that you now maintain.
The proxy control plane is a door with no lock
Caddy’s admin API is on by default at localhost:2019 and has no authentication whatsoever. The entire defence is the interface binding. Through it the whole configuration can be dumped and replaced and the process stopped, and turning it off completely costs you the caddy reload command. CVE-2026-27589 was precisely about the origin check not firing in every case. Remote administration is marked experimental, requires mutual authentication, and both May 2026 holes landed exactly in its access control.
In Traefik the same door looks different: api.insecure brings up an unauthenticated API and dashboard on port 8080 across all interfaces. The documentation itself advises against enabling that in production, yet the option is still there. And container auto-discovery is bought by mounting the container daemon socket into a process that faces the internet. That is root-equivalent access on the host, and read-only mode is not a mitigation here.
In fairness, this class is not a young-project problem: nginx ships its Control API in 1.31.5 without authentication too.
The proxy lies to your backend, and that is a whole defect class
A proxy tells the application two things: which path was requested and who the client is. It can lie about both, and these are not isolated bugs but a recurring class with a dense repair history.
First: authorisation is decided before the path is rewritten. In 83 days, between 24 April and 16 July 2026, Traefik fixed four separate path rewriters. Caddy has the same class in another form: case and escape-sequence normalisation inside matchers, CVE-2026-27587.
Second: the identity header. Caddy had three independent forward_auth defects in six months, and at the time of checking the fix for the third was sitting unreleased in the main branch. In Traefik, identity spoofing through a non-canonical spelling of a header name took five separate attempts to fix, trailers included, while trustForwardHeader twice failed to do what it promised and is now deprecated.
The practical conclusion is not the obvious one. Fix the backend first: every path bypass works only against an application that normalises the path itself and then trusts the normalised form. Traefik’s threat model states it honestly: trust is decided once, at the entry point. So the entry point is what you test, rather than hoping the next patch closes the subject.
HTTP/3 is the softest spot on the perimeter, and Caddy turns it on by default
Caddy opens a UDP socket on 443 by default, so you get HTTP/3 without making a decision. Across 2026, nine Traefik security advisories relate to HTTP/3 or QUIC. Among them the single September critical, CVE-2026-88007 at 9.1, and honesty is required here: it has three mandatory preconditions, so reading it as “everyone is on fire” is wrong. On the nginx side, CVE-2026-90439 was closed on 15 September 2026, four days before I am writing this.
The upside is not guaranteed either. On fast links QUIC loses to TCP with HTTP/2 by as much as 45.2 percent: that measurement is not mine, but unlike most numbers in review posts it comes with a published methodology. Hence the default I consider correct: if you have no measured benefit from HTTP/3, switch it off and remove an entire layer of this year’s defects from your perimeter.
One project has gone silent for 108 days, the other patches every eleven, and both answers are bad
This is the sharpest contrast in the whole set, and it is about operations rather than code.
Caddy is quiet. The last release, v2.11.4, shipped on 3 June 2026: as of 19 September that is 108 days without a release, against a hundred commits on the main branch and four merges from a private security fork. A v2.11.5 milestone exists with no date. There is a published advisory, GHSA-6365-7ppr-5r92, that points to a fix in version v2.11.5, which does not exist. The forward_auth race fix sat unreleased for 71 days.
Traefik is not quiet at all. Twelve published patches on the 3.7 branch across 122 days, nine of them within 86 days at an average interval of about eleven days. Which means upgrading the proxy becomes a fortnightly operation, and it belongs in a maintenance schedule rather than in spare time.
Branch lifetimes sit on top of that. A Traefik minor lives six months: active support for 3.7 expires around 5 November 2026, and security support for the 2.11 branch ended on 7 September 2026, twelve days before this text was written. Five advisories were published on that same day. Worth knowing separately: Caddy’s security patches are sometimes breaking, and the project says so plainly in the release itself.
Neither answer is a good one. Silence means fixes exist but have not reached you. A high cadence means you have acquired standing work. The choice is between those two, not between calm and busy.
Your vulnerability scanner will not show you some of these holes
Advisory counts mislead in both directions, and it is worth learning to read them. Across 2026, Traefik has 48 repository advisories against Caddy’s 17. But the severity profile matters more than the count: Traefik has 2 critical and 25 high, Caddy has no criticals at all in the same year.
Now the correction in Traefik’s favour, without which the comparison is dishonest. Twenty-six of those forty-eight entries concern Kubernetes providers and do not address a proxy running outside a cluster at all. On top of that Traefik publishes dependency vulnerabilities, which Caddy does not: part of the gap is simply a different disclosure policy.
And here is the genuinely unpleasant part. Seven Traefik advisories and three of Caddy’s carry no CVE identifier at all. A critical digestAuth bypass scored at 9.3 is absent from the global database and is tied only to commit ranges: govulncheck, Dependabot and Trivy will none of them show it to you. Two severity scores for one and the same defect disagree inside GitHub itself. The conclusion for anyone building a process: a green scanner report means the scanner found nothing, not that the perimeter is closed.
It is not a two-way race: nginx and HAProxy learned the thing they were replaced for
The main argument for Caddy and Traefik used to be that they issue certificates themselves. Over the last year that argument stopped being exclusive.
nginx has had a native ACME module since 2025, and it supports both renewal hints from the certificate authority and issuance profiles. So on that mechanism the laggard is not “everyone against Caddy”, it is Traefik alone out of three. The limits are honest: no DNS challenge means no wildcards; a 256k zone holds roughly fifty certificates; and with state_path off everything is lost on restart.
HAProxy added ACME in 3.2 with the HTTP challenge only, the DNS challenge in 3.3, and a persistent variant plus profiles in 3.4, all still behind an experimental-directives flag. In exchange it is the only one that can swap a certificate transactionally at runtime with an atomic rollback, and it offers five years of long-term support on even-numbered branches. One more detail that fuels a lot of arguments: the claim about nginx permanently caching a container’s IP address went out of date on 26 November 2024.
The benchmark measures something other than what hurts
Requests-per-second and memory figures from 2026 review posts disagree with each other by a factor of twelve on Traefik’s memory alone. Where a methodology is described at all, it amounts to reading docker stats with no load generator, no versions and no configs. Numbers like that can be neither refuted nor reproduced, so they do not appear here.
Far more interesting is what never makes it into those posts. The largest genuine performance regression of recent years, a 56 percent drop in handshakes on OpenSSL 3.x, is a property of the library. Caddy and Traefik are immune to it simply because they link against the TLS implementation that ships with Go. The flip side of the same coin: kernel TLS offload was accepted as a Go proposal back in 2021 and as of September 2026 is still not implemented, while nginx has been able to send files through the kernel since 2021 and HAProxy gained the capability in 3.3.
Caddy publishes no official benchmarks of its own, which is to its credit. What deserves comparison is not throughput but behaviour under failure: what happens when a backend dies, when issuance fails, when the config is reloaded under load. None of those is measured by a load-testing tool.
I choose with three questions, not a comparison table
Question one: do you need wildcards and the DNS challenge? If yes, Traefik ships the lego library with well over two hundred DNS providers out of the box, while Caddy requires a custom build through xcaddy. The price of the second option is concrete: plugins are compiled into the binary, so upgrading stops being a one-command operation, and caddy upgrade does not restart the server.
Question two: will there be a second replica? If yes, there are exactly two roads: Caddy with shared storage on a third-party module you maintain yourself, or moving certificates out of the proxy entirely, into a separate controller or your own ACME server. Caddy can act as an internal certificate authority itself through acme_server, with one caveat from the next section.
Question three: who applies patches, and how quickly? If an upgrade costs you an approval cycle, Traefik’s eleven-day cadence is beyond your budget. If upgrades are cheap but a project going quiet for three months unsettles you, then Caddy is the expensive one. This is a question about your team rather than about code, and it decides more often than the first two.
There is deliberately no default recommendation here. Three answers produce three different choices, and any of them is defensible once its price is named.
What I deliberately do not do
- I do not mount the container daemon socket into a proxy for auto-discovery. That is root-level access handed to a process on the perimeter; read-only mode is not a mitigation, and
exposedByDefaultwill publish everything anyway. - I do not enable the dashboard or the API on the perimeter. An unauthenticated API and dashboard on every interface is not debugging, it is an open door.
- I do not move Caddy’s admin API off the local interface. It has no authentication at all, the remote mode is experimental, and it has already taken two access-control bypasses during 2026.
- I do not leave HTTP/3 on without a measured benefit. Caddy opens it by default, and that path holds most of this year’s serious defects.
- I do not enable on-demand certificate issuance without limits. Caddy will not refuse to start in that state, it merely logs a warning, and the permission endpoint lands directly in the handshake path with no cache and a ten-second timeout.
- I do not put an internal certificate authority on the host that faces the internet. Caddy keeps the internal root private key as an ordinary PEM file alongside, and moving that key into a hardware module is something step-ca does, not a proxy.
- I do not copy
ssl_staplingblocks out of old posts. For Let’s Encrypt certificates they have been dead since 6 August 2025. - I do not chase the newest minor branch for features. A six-month minor lifetime means picking the wrong branch costs more than skipping a feature.
The stack
Exactly what was checked, and in which versions, as of 19 September 2026.
- Caddy: v2.11.4 of 3 June 2026, built on Go 1.25.1. Debian trixie ships 2.6.2, backports has 2.11.2.
- Traefik: v3.7.13 of 4 September 2026, Go 1.26.0, carrying two lego versions at once. There is no Debian package at all.
- nginx: 1.31.6 of 15 September 2026, with the native ACME module.
- HAProxy: 3.4 LTS of 3 June 2026, supported into the second quarter of 2031.
- Let’s Encrypt: the classic, tlsserver and shortlived profiles.
- step-ca: an internal certificate authority for when the root key has to live in a hardware module.
Why this matters if you are paying
Choosing a reverse proxy is a multi-year maintenance commitment, and it converts into hours. Minor branch lifetime, patch cadence and the presence of long-term support directly determine how much time per quarter goes into the perimeter. The gap between “twice a year” and “every fortnight” is a budget line, not a convenience.
Cutting certificate lifetimes to forty-seven days turns any manual renewal into a scheduled incident. Automation that works today at a one-year lifetime will get it wrong eight times as often at six weeks. And missing support for the authority’s renewal hints in one of the options means the authority’s rate limits become your problem.
Then the part that hurts the budget most. Some perimeter defects are invisible to your scanner because they are not in the databases the scanner reads. So a green report does not equal a closed perimeter, and what deserves funding is an upgrade routine and somebody reading the project’s advisories, not another tool producing another green report. It is the same principle I apply to access: what gets tested is behaviour, not configuration.
Frequently asked questions
What should I pick for one server and a dozen services?
Caddy, if the HTTP and TLS challenges are enough for you: it issues and renews on its own, and it renews against the real lifetime rather than a number in a config file. The price: the admin API is on by default with no authentication, the stock systemd unit carries no restart directive, and the DNS challenge requires building a custom binary. If you need wildcards, the choice shifts to Traefik or to an external ACME client.
Is it true that you cannot run two Traefik instances with Let’s Encrypt?
Yes, the official documentation states it outright, and the wording there still refers to Traefik 2.0 although the current branch is 3.7. It points you at the commercial edition or at an external certificate controller. Caddy solves the same problem with shared storage, but the standard build carries exactly one storage module, the local filesystem, and every other one is third-party.
Do I need to move to short-lived certificates now?
You do not need to move, you need to be sure your automation survives the move. From 15 March 2026 the maximum is 200 days, from 15 March 2027 it becomes 100, and Let’s Encrypt reaches 45 days by February 2028. One specific trap: enabling the short-lived profile in Traefik without touching certificatesDuration means firing renewal on every daily pass and running into the duplicate-certificate limit for that set of names.
How alarming are Traefik’s 48 security advisories in 2026?
The number alone says little: 26 of the 48 entries concern Kubernetes providers and do not address a proxy outside a cluster, and Traefik also publishes dependency vulnerabilities, which Caddy does not. Look at severity and cadence instead: Traefik has 2 critical and 25 high, Caddy has no criticals at all across 17 entries in the same year. And on the other side of the scale: as of 19 September 2026 Caddy had gone 108 days without a release, and one of its fixes sat unreleased for 71 days.
Can I leave the Caddy admin API enabled?
It is already enabled by default on localhost:2019 with no authentication of any kind, and disabling it entirely costs you the caddy reload command. Through it the whole configuration can be dumped and replaced and the process stopped, and CVE-2026-27589 was precisely about the origin check not always firing. The sensible minimum: keep it on the local interface, enable origin enforcement, and never publish port 2019 on any network.
Is mounting the container daemon socket for auto-discovery worth it?
It is root-equivalent access handed to a process that faces the internet, and read-only mode is not a mitigation. Add exposedByDefault set to true: without an explicit opt-out the provider publishes every container. If you genuinely need auto-discovery, put a socket proxy with request filtering between the proxy and the daemon, and turn exposedByDefault off.
nginx cannot do certificates on its own, so why is it in this comparison?
It can now: the native ACME module has been available since 2025 and supports both renewal hints from the certificate authority and issuance profiles, which makes Traefik the laggard on that mechanism. The limits are honest: no DNS challenge means no wildcards, the default zone holds about fifty certificates, and with state_path off the keys are lost on restart. The main argument for Caddy and Traefik stopped being exclusive over the last year.
Sources
- Caddy releases and Caddy security advisories.
- Automatic HTTPS in Caddy, the reverse_proxy directive and the admin API.
- Traefik releases and Traefik security advisories.
- Traefik version support windows and ACME certificate resolvers.
- nginx CHANGES and the nginx ACME module.
- HAProxy branches and support windows.
- Let’s Encrypt issuance profiles, rate limits and the end of OCSP.
- The CA/Browser Forum schedule for reducing certificate validity.
- The Kubernetes committees’ statement on ingress-nginx.
Need a consultation?
If you would rather go through your own perimeter than read about somebody else’s, book a conversation. We will look at what sits on your edge, who owns the certificates, what the upgrade routine is, and what will happen at the next config reload. Related reading: a Kubernetes cluster on your own hardware, a Proxmox cluster as code, SIEM on an open core.


