Reverse proxies in 2026: revisit NGINX, Caddy and HAProxy before you put another proxy in front of your app

Almost every web application we run has something sitting in front of it. A few virtual machines run the actual code, and one or two small servers in front of them accept HTTPS, keep certificates, spread traffic, hide a dead backend and write the access log. In many teams that box was set up years ago, it still works, and nobody wants to touch it. That is exactly why it deserves a fresh look.

This post is about that front box: the reverse proxy in front of a plain VM or app fleet. It is not about Kubernetes ingress, and it is not about API gateways with keys, quotas and developer portals. We covered those separately in API gateways in 2026: Kong, Apache APISIX and Traefik and in the ingress-nginx retirement post. Certificate tooling has its own post, TLS certificate automation in 2026, and so does checking whether the site is up, uptime monitoring in 2026. Here we stay with the three open source tools that most small and medium teams actually put on that front box: NGINX, Caddy and HAProxy. We also look briefly at the two NGINX forks, freenginx and Angie.

We go through history, licences, current versions, the 2026 security advisories, the configuration model, TLS and ACME, HTTP/2 and HTTP/3, load balancing and health checks, reloads, observability, performance and the cost of running each one. At the end there is a small hands-on drill with Caddy that we ran on our own test machine, and a frank list of what we did not run.

A scenario to keep us honest

Imagine a company in Kozhikode that builds online ordering, invoicing and shipment tracking software for spice traders and exporters in Malabar. The company is invented for this post, but the setup will look familiar to many Indian teams. It has about 35 engineers and a two-person infrastructure team. Customers are traders in Kerala and Karnataka, and buyers who log in from the Gulf and Europe, often on mobile networks.

The setup today:

  • Eight application VMs run a Django app and a small Node.js service for live shipment status.
  • Two edge VMs run NGINX 1.24 from the Ubuntu package, installed in 2023. The configuration was written by an engineer who has since left. It is copied between the two VMs by hand.
  • Certificates come from certbot on a cron job on one edge VM, and are copied to the other with a script.
  • One more VM runs HAProxy 2.4, set up long ago to spread traffic across three report-generation workers.
  • There is no shared view of upstream health. When a Django VM hangs, users see slow pages until somebody notices.

Three things have pushed the team to revisit all of this. First, HAProxy 2.4 is now listed as unmaintained on haproxy.org. Second, NGINX published fixes for a long list of vulnerabilities in 2026, and the team is not sure which of them their packaged 1.24 build has received. Third, a new engineer has suggested replacing everything with Caddy, because “it does HTTPS by itself”. The team lead wants a calm decision, not a rewrite driven by a blog post.

We will come back to Kozhikode in the worked plan.

The short version

  • The front proxy is a security boundary. In 2026 all three projects shipped several security fixes, and many of them were in areas people rarely think about: regex rewrites, HTTP/3, FastCGI, forward authentication and the admin interface. Patching speed matters more than brand.
  • NGINX open source (stable 1.30.6, mainline 1.31.8) is still the general purpose choice when the box also serves static files, caches responses or terminates many virtual hosts. It gained a lot in 2026: sticky sessions in the open source build, keepalive to upstreams turned on by default, and an ACME module that is packaged separately.
  • Caddy (2.11.7) is the simplest way to get correct, automatic HTTPS with a short configuration. Its admin API makes reloads clean. It suits small teams and many small sites, as long as you keep the admin endpoint locked down and read the release notes, because 2.11.6 changed several defaults.
  • HAProxy (3.4 LTS, latest 3.4.7) is the specialist for load balancing, health checks, TCP proxying and runtime changes without reloads. Its LTS branches are supported for about five years, which suits teams that upgrade rarely.
  • Do not change the tool before you fix the basics: configuration in Git, a syntax check before every reload, a patch routine, health checks and logs you actually read.

Where things stand on 28 October 2026

NGINX open source. nginx.org runs two branches. The stable branch is 1.30, which started with 1.30.0 on 14 April 2026; the latest is 1.30.6, released on 14 October 2026. The mainline branch is 1.31; the latest is 1.31.8, released on 21 October 2026. The changelog gives dates only, not times, so we cannot give an IST time. NGINX is distributed under a 2-clause BSD style licence, and the source is maintained by F5, Inc., which also sells the commercial NGINX Plus. The njs scripting module reached 1.0.2 on 14 October 2026. Automatic certificates now come through the separate nginx-acme module (ngx_http_acme_module); its latest release is 0.4.1, published at 20:28 UTC on 1 May 2026, which is 1:58 AM IST on 2 May 2026.

freenginx. freenginx is a fork started in February 2024 by Maxim Dounin, a long-time nginx developer, with the stated aim of keeping nginx development free and open. Its first release under the new name was freenginx 1.25.4, dated 20 February 2024. The latest entry in its changelog is freenginx 1.31.5, dated 27 October 2026, which fixes the same TLS session ticket problem (CVE-2026-97629) that nginx fixed on 14 October. It uses the same 2-clause BSD style licence.

Angie. Angie is a fork maintained by a company formed by former nginx developers, under a BSD licence, with a paid Angie PRO edition. Its site lists Angie 1.12.4 on 16 October 2026, an unscheduled release that fixed five vulnerabilities disclosed in nginx, and 1.12.3 on 13 October 2026, which fixed ACME certificate issuance and upstream keepalive issues. Angie has built-in ACME support.

Caddy. Caddy is written in Go and licensed under Apache 2.0. The latest release is 2.11.7. The GitHub release is stamped 05:58 UTC on 3 October 2026, about 11:28 AM IST. It fixed regressions from 2.11.6 (an HTTP/2 crash and streams cut off at 60 seconds). Version 2.11.6, released on 1 October 2026, is the one to read carefully: it requires Go 1.26 to build, caps request headers at 16 KiB by default, adds one minute idle read and write timeouts against slow clients, drops header fields that contain a dot, and validates configuration more strictly. The release notes list ZeroSSL, Stripe and Railway among sponsors.

HAProxy. haproxy.org lists these branches:

  • 3.4 is the newest LTS, released on 3 June 2026, with end of life planned for the second quarter of 2031. The latest is 3.4.7, released on 14 October 2026.
  • 3.3 is a stable (non-LTS) branch from 26 November 2025, planned to end in the first quarter of 2027. The latest is 3.3.17.
  • 3.2 LTS (28 May 2025) runs to the second quarter of 2030, latest 3.2.26. 3.0 LTS runs to the second quarter of 2029, latest 3.0.30.
  • 3.1 is unmaintained. 2.8 and 2.6 receive critical fixes only. 2.4 became unmaintained in the second quarter of 2026.
  • 3.5 is in development (3.5-dev9), expected around the fourth quarter of 2026.

HAProxy gives dates without times. The core is GPL version 2, and the exportable include files are LGPL, as the project’s LICENSE file explains. HAProxy Technologies sells HAProxy Enterprise and the ALOHA appliance.

Security in 2026

This year was busy for all three. We list what the projects themselves published. CVE numbers are as given by the projects.

NGINX. nginx.org published security fixes in eight batches:

  • 4 February (1.29.5 and 1.28.2): CVE-2026-1642, injection through an SSL upstream connection.
  • 24 March (1.29.7 and 1.28.3): six issues in the dav module with alias, the mp4 module, the mail proxy, auth_http and XCLIENT, and OCSP in the stream module.
  • 13 May (1.31.0 and 1.30.1): six issues, including a heap overflow in the rewrite module that could lead to code execution (CVE-2026-42945), a request injection with proxy_set_body over HTTP/2, a use-after-free in the OCSP resolver, and address spoofing with HTTP/3.
  • 22 May (1.31.1 and 1.30.2): another rewrite issue with overlapping captures, again with potential code execution (CVE-2026-9256).
  • 17 June (1.31.2 and 1.30.3): an HTTP/3 use-after-free rated major, which only affected mainline 1.31.0 and 1.31.1, plus issues in proxy_v2 or gRPC and charset.
  • 15 July (1.31.3 and 1.30.4): a major issue when map is used with regular expressions (CVE-2026-42533), plus the slice and SSI modules.
  • 15 September (1.31.6 and 1.30.5): an HTTP/3 issue when nginx is built with OpenSSL 3.5.0 or earlier.
  • 14 October (1.31.7 and 1.30.6): five issues, including alias with rewrite, script evaluation, HTTP/3 request body injection, an AIO and SSI crash, and TLS session ticket forgery (CVE-2026-97629). njs 1.0.2 fixed two more on the same day.

Caddy. The Caddy security advisories page and release notes show:

  • 2.11.1 (23 February): six CVEs, including one where TLS client authentication could fail open (CVE-2026-27586).
  • 2.11.2 (6 March): identity injection through forward_auth, and a vars_regexp issue.
  • 2.11.3 (12 May) and advisories published on 13 May: execution of non-PHP files through FastCGI path splitting with unsafe Unicode (rated high), a bypass of an earlier fix (CVE-2026-30852, rated high), and two ways to bypass authorisation on the remote admin API.
  • Advisories published on 8 June, after 2.11.4 (3 June): a Windows file_server path bypass and a FastCGI header normalisation bypass in forward_auth copy_headers, both rated high.
  • An advisory published on 10 July covers a forward_auth with reverse_proxy case where a connection could be reused for the wrong upstream (GHSA-6365-7ppr-5r92); the 2.11.6 notes list it among its fixes.
  • A low severity advisory on 13 October covers hop-by-hop headers leaking through.

HAProxy. HAProxy Technologies published:

  • 12 February: CVE-2026-26080 and CVE-2026-26081, two QUIC parsing issues that could crash or hang the process. Fixed community versions are 3.0.16, 3.1.14, 3.2.12 and 3.3.3.
  • June: CVE-2026-55203, an integer overflow in the FastCGI multiplexer that a malicious FastCGI backend could use to desynchronise framing, and CVE-2026-55204, a NULL pointer crash in the HPACK table under memory pressure. Both fixes appear in the 3.4.1 changelog (25 June 2026) and were backported.
  • A CVE record, CVE-2026-90678, describes request smuggling when an HTTP/3 frontend forwards to an HTTP/1.1 backend over reused connections, affecting 3.3.0 to 3.4.4; the fix is in 3.4.5 (24 September). We could only read this record through a CVE aggregator, so check the HAProxy announcement before you rely on the exact ranges.
  • The 3.4.7 changelog (14 October) also lists two “major” bug fixes, one for HTTP/2 PING frames and one for very long chunk-size lines, and a change that rejects the JWT “none” algorithm by default in jwt_verify.

The lessons. Three patterns stand out. First, regular expressions in rewrites and maps were the most dangerous NGINX area this year, and HTTP/3 was the busiest area across all three projects. If you turn on HTTP/3, you are signing up for faster patching. Second, features that trust another system, such as forward authentication, FastCGI and the admin API, kept showing up in Caddy and HAProxy advisories. Third, a distribution package may carry backported fixes under an old version number, so “1.24” on Ubuntu is not automatically unpatched, and “latest” from a third-party repository is not automatically safe. Check your distribution’s security tracker for the exact CVE before you decide. If a certificate change is part of the same work, keep the renewal path from the TLS automation post in mind, so that a proxy upgrade does not break renewals.

The first question: what is the front box actually doing?

Before comparing tools, write down every job the front box does today. In most teams the list is longer than people remember:

  • Terminating TLS and holding certificates.
  • Redirecting HTTP to HTTPS, and old URLs to new ones.
  • Serving static files, uploads or a single page app build.
  • Caching some responses.
  • Spreading requests across backends, perhaps with sticky sessions.
  • Hiding failed backends.
  • Rate limiting or blocking abusive clients.
  • Passing the real client IP to the app.
  • Proxying WebSockets or long polling.
  • Forwarding plain TCP, for example to a database or a mail server.
  • Writing logs that someone actually reads.

Each tool is strong in a different part of this list. If your list is mostly “TLS plus a few sites”, Caddy fits well. If it is mostly “balance and watch many backends”, HAProxy fits well. If it is “a bit of everything, including static files and caching”, NGINX fits well. Very often the honest answer is “keep what we have, but run it properly”.

What each tool is

NGINX

NGINX was originally written by Igor Sysoev. It uses one master process and several worker processes, each handling many connections with an event loop. Configuration is a tree of blocks (http, server, location, upstream) with directives that inherit downwards. Much of its power comes from modules, some built in and some loadable. Besides HTTP, it proxies TCP and UDP through the stream module and mail through the mail module.

The open source build is what this post covers. F5 sells NGINX Plus, which historically added active health checks, a live API and more load balancing options. Some of those have moved into open source in 2026: the 1.29.6 mainline release added sticky session affinity and the route and drain server parameters. In 1.29.7, keepalive to upstreams became the default and proxy_http_version now defaults to 1.1. The 1.31 mainline added least_time balancing, a tunnel module, and in 1.31.5 (2 September 2026) a control API, predicate locations and a JSON module. These mainline features will reach the next stable branch, not 1.30.

freenginx and Angie

Both are nginx forks that keep the same configuration language. freenginx follows a conservative path with mostly fixes and small features. Angie adds more visible features, such as built-in ACME, and has a commercial PRO edition. Our advice is simple. Use a fork only if you have a specific reason, and if you are comfortable with a smaller maintainer team. Track its security fixes as closely as you would track nginx.org.

Caddy

Caddy was started by Matt Holt and first released in 2015. Caddy 2, a full rewrite, came out in 2020. Its native configuration is JSON, loaded through an admin API. The Caddyfile is a friendlier format that Caddy converts to JSON. The headline feature is automatic HTTPS: give it a domain name and it gets and renews certificates through ACME, redirects HTTP to HTTPS and serves HTTP/2 and HTTP/3 by default. It is a single static binary. Plugins are compiled in, using the xcaddy tool or the download page, rather than loaded at run time.

HAProxy

HAProxy was started by Willy Tarreau. haproxy.org lists version 1.1 from March 2002. It is a load balancer and proxy first, and it does not serve files from disk. Configuration is split into global, defaults, frontend, backend and listen sections. It runs as a master process with worker threads and has a runtime API on a socket. Through that API you can drain, disable, re-weight and in recent versions add or remove servers, and even whole backends in 3.4, without a reload. Even-numbered branches are LTS for about five years; odd-numbered ones are maintained for 12 to 18 months.

Side by side

Licence and governance

  • NGINX: 2-clause BSD style, controlled by F5. Source is public on GitHub under nginx/nginx. A commercial edition exists, and some features stay there.
  • freenginx: 2-clause BSD style, run by a small independent team.
  • Angie: BSD, run by one company with a paid edition.
  • Caddy: Apache 2.0, maintained by a core team on GitHub with company sponsors.
  • HAProxy: GPL v2 core with LGPL headers, led by its original author, with HAProxy Technologies selling enterprise builds.

Configuration model

  • NGINX: a nested block language. It is powerful, but inheritance rules (for example, how add_header and proxy_set_header behave when redefined in an inner block) catch people every year. Use nginx -t before every reload.
  • Caddy: a short Caddyfile, or JSON for full control. Sensible defaults mean less to write, but also more that happens without being written down. Use caddy validate or caddy adapt to see the real JSON.
  • HAProxy: flat sections with a strict order of rule processing. It is verbose, but explicit. Use haproxy -c -f before every reload. 3.4 also supports a conf.d directory in its systemd unit.

TLS and ACME

  • NGINX: TLS is mature. ECH (Encrypted Client Hello) arrived in 1.29.4 in December 2025. OpenSSL 4.0 support came in 1.29.8. TLS 1.0 and 1.1 are off by default since 1.27.3. ACME comes from the separate nginx-acme module, which supports HTTP-01 and TLS-ALPN-01, external account binding, profiles and, since 0.4.0, ACME Renewal Information. Many teams still use certbot outside NGINX, and that is fine.
  • Caddy: ACME is built in and on by default, with Let’s Encrypt and ZeroSSL, on-demand TLS for many customer domains and, since 2.11.1, automatic ECH key rotation. This is its strongest point.
  • HAProxy: ACME support started in 3.2. In 3.4 it gained DNS-01 and dns-persist-01 challenges, external account binding and profiles. 3.3 added kTLS and ECH. HAProxy Technologies also ships builds with AWS-LC as an alternative to OpenSSL.

HTTP/2 and HTTP/3

  • NGINX: HTTP/2 is enabled per server with the http2 directive since 1.25.1, which also removed HTTP/2 server push. HTTP/3 over QUIC was added as experimental in 1.25.0 (May 2023). 1.29.4 added proxying to HTTP/2 backends. 1.31.7 added RFC 9218 priorities, and QUIC reloads with quic_bpf.
  • Caddy: HTTP/2 and HTTP/3 are on by default for HTTPS sites. It can proxy to HTTP/2 backends, including h2c.
  • HAProxy: HTTP/2 on both sides, HTTP/3 on the frontend, an experimental QUIC backend since 3.3, and experimental QMux (HTTP/3 semantics over TCP) in 3.4. 3.4 also added limits against HTTP/2 floods.

Load balancing and health checks

  • NGINX open source: round robin, least_conn, hash, ip_hash and random, plus sticky since 1.29.6 and least_time in 1.31 mainline. Health checking is passive, using max_fails and fail_timeout. We did not find active health checks in the open source changelogs up to 1.31.8. If you need them, use NGINX Plus, a fork, or an external checker.
  • Caddy: several policies (random, round robin, least connections, first, IP or header hash, cookie and more), active health checks on a URI with an expected status, and passive checks. Since 2.11.6, active health check state is kept per check configuration.
  • HAProxy: the richest set. It offers many algorithms (3.3 made random with two choices the default), detailed active checks over HTTP, TCP, or agent checks, slow start, backup servers, queues with per-server connection limits, and reusable health check sections in 3.4.

Reloads and zero downtime

  • NGINX: nginx -s reload makes the master read the new configuration. If it is valid, new workers start and old workers finish their current requests. A bad configuration is rejected and the old one keeps running. Long-lived connections such as WebSockets keep old workers alive, so watch for piles of old workers. Binary upgrades without dropping connections are possible with signals.
  • Caddy: caddy reload posts the new configuration to the admin API. Caddy applies it in place and rejects it if it fails to load. In our drill, three reloads during a stream of requests caused no failed requests (details below).
  • HAProxy: reloads use the master-worker model. The new process takes over the listening sockets from the old one, and the old one finishes its connections. Many daily changes need no reload at all, because they can go through the runtime API. 3.3 added stats that persist across reloads.

Observability

  • NGINX open source: access and error logs with a flexible format (log $upstream_response_time and $upstream_addr), plus the basic stub_status counters. Richer metrics usually come from log parsing or an exporter. The new control API in 1.31.5 mainline may change this, but it is too new to judge.
  • Caddy: structured JSON logs, Prometheus metrics, and since 2.11.3 OTLP metrics push.
  • HAProxy: the stats page, a built-in Prometheus exporter, very detailed log lines with timers and termination codes, and in 3.4 an OpenTelemetry filter, replacing the deprecated OpenTracing filter.

Performance

All three are fast enough that, for most of us, the backend app or the database is the bottleneck. We have no independent benchmark to offer and will not invent one. The projects’ own claims, labelled as such:

  • The freenginx feature page, carried over from the original nginx description, states that 10,000 inactive HTTP keep-alive connections take about 2.5 MB of memory. This is the project’s own statement, not a test we ran.
  • HAProxy’s site describes a test of its own on AWS Graviton2 reaching about 2 million requests per second over SSL and 100 Gbps. This is the vendor’s own run, on its own setup.
  • HAProxy Technologies moved its Performance Packages to AWS-LC in December 2025, citing OpenSSL 3.x performance costs. This is the vendor’s own reasoning.
  • Caddy does not publish headline numbers. Being written in Go, it uses more memory per connection than the C-based tools, and for a typical small fleet that rarely matters.

If performance truly matters for you, test with your own traffic shape: TLS handshakes per second, response sizes, keepalive behaviour and HTTP/3 share.

Operations

  • NGINX: distribution packages are often old but patched. nginx.org packages track stable and mainline. Modules must match the build.
  • Caddy: one binary, so upgrades are a file swap and a restart. Plugins mean a custom build, which you must rebuild for every security release.
  • HAProxy: distribution packages vary. Many teams use the community-maintained haproxy.debian.net repositories, or a vendor build, to pick a specific LTS branch.

What happens during a request

It helps to walk one request through the front box, because that is where most surprises live.

  1. The client resolves the name and connects, over TCP for HTTP/1.1 and HTTP/2, or UDP for HTTP/3 after an Alt-Svc hint.
  2. The TLS handshake picks a certificate by SNI. If no certificate matches, NGINX falls back to the default server for that port, so set a default server that returns an error rather than showing one customer’s site for another’s name. Caddy and HAProxy have their own fallbacks; know them.
  3. The proxy parses the request and applies its limits: header size (Caddy 2.11.6 now caps at 16 KiB by default), body size, timeouts.
  4. Routing: NGINX picks a server block and then a location; Caddy matches site and route; HAProxy evaluates frontend rules and picks a backend.
  5. Rewrites, redirects and header changes run. This is where the 2026 regex bugs lived.
  6. A backend server is chosen by the balancing method, skipping servers marked down.
  7. The proxy opens or reuses a connection to the backend and adds X-Forwarded-For and similar headers. Make sure the app trusts these only from the proxy’s address.
  8. The response streams back. Buffering, compression and caching may happen here. For WebSockets and server-sent events, buffering and idle timeouts decide whether the connection survives.
  9. The log line is written. If you do not log the upstream address and timing, you will not be able to tell a slow app from a slow proxy.

A worked plan for Kozhikode

Step 1: Inventory and freeze

The team copies both NGINX configurations and the HAProxy configuration into a Git repository. They diff the two edge VMs and find three small differences, one of them a missing security header. For a week, no change goes onto the boxes except through Git.

Step 2: List the jobs

Using the list above, they find the edge VMs do TLS for eleven hostnames, serve the React build and uploaded invoices, redirect old URLs, proxy WebSockets for live shipment status and rate limit the login page. The HAProxy VM only balances three report workers over HTTP.

Step 3: Check the security position

For NGINX they look up each 2026 CVE in Ubuntu’s security tracker for their exact package. They also check which modules they actually use: rewrite yes, map with regex yes, mp4 and dav no, HTTP/3 no. For HAProxy 2.4 the answer is simple, because the branch is unmaintained.

Step 4: Decide the front box

They compare two paths.

  • Path A: keep NGINX on the edge, move to nginx.org stable packages (1.30.6 at the time of writing), add a default server that rejects unknown names, log upstream timings, and keep certbot for now. Replace the HAProxy 2.4 VM with HAProxy 3.4 LTS, or fold the three report workers into an NGINX upstream with passive checks.
  • Path B: move everything to Caddy, using its automatic HTTPS and active health checks, with the static files served by Caddy’s file_server.

Path B is attractive, but the team has years of NGINX knowledge, a working rewrite list of about 200 old URLs, and no pain with certificates beyond the copy script. They choose Path A for the edge. They choose HAProxy 3.4 LTS for the report workers, because those jobs run for minutes and the team wants queueing and per-server connection limits.

Step 5: Put Caddy where it clearly wins

They have four small internal tools and a marketing site, each currently a separate NGINX server block with a copied certificate. They move these to one small Caddy VM with automatic HTTPS. The admin API stays on localhost, as by default. They pin Caddy to 2.11.7 and read the 2.11.6 breaking changes before the move.

Step 6: Health checks and logs

On the NGINX edge they set max_fails and fail_timeout on each upstream server, and add $upstream_addr and $upstream_response_time to the log format. On HAProxy they add an HTTP health check on each report worker’s /health endpoint and turn on the Prometheus exporter. Their uptime checks from outside stay as they were.

Step 7: Reload drills

On a staging copy they practise three things: a bad configuration (expect rejection and no outage), a reload during a long WebSocket session (expect the old worker to stay until the session ends), and draining one backend through the HAProxy runtime API.

Step 8: Patch routine

They subscribe to the nginx-announce list, HAProxy’s announcements and Caddy’s GitHub releases. Security releases go to staging the same day and to production within a week. HTTP/3 stays off for now. They will revisit it when buyers on mobile networks show a measurable benefit, accepting that it means faster patching.

When each tool is better

NGINX is better when:

  • The box serves static files and caches responses as well as proxying.
  • You host many virtual hosts with complex rewrites.
  • Your team already knows it well.
  • You want the widest choice of packages, modules and community answers.

Caddy is better when:

  • You run many small sites or customer domains and want certificates to simply work, including on-demand TLS.
  • You prefer a short configuration and an API for changes.
  • One binary with no dependencies suits your deployment style.

HAProxy is better when:

  • Load balancing is the main job.
  • You need strong active health checks, queues, connection limits or TCP proxying.
  • You need runtime changes without reloads, or very detailed per-request timing in logs.
  • You want a five year LTS branch.

When not to change

  • If your current proxy is patched, its configuration is in Git and it does its job, a migration adds risk for little gain.
  • If the real problem is certificate renewal, fix renewal first; you may not need a new proxy at all.
  • If the real problem is a slow backend, a new proxy will only measure the slowness more precisely.
  • If you depend on a third-party NGINX module, check that it exists in the target before you plan anything.
  • If your team has no time to learn a new configuration language this quarter, wait.

A short history

  • March 2002: haproxy.org lists HAProxy 1.1, from Willy Tarreau.
  • October 2004: the first public nginx release, by Igor Sysoev.
  • 2011: Nginx, Inc. is formed; the copyright line in the nginx LICENSE file runs from 2011.
  • 2015: Caddy is first released by Matt Holt, with automatic HTTPS as its core idea.
  • 2019: F5 buys NGINX, Inc.
  • 2020: Caddy 2, a full rewrite with the JSON configuration and admin API.
  • 2022: Angie appears as an nginx fork led by former nginx developers.
  • May 2023: nginx 1.25.0 adds experimental HTTP/3.
  • February 2024: freenginx starts; freenginx 1.25.4 on 20 February 2024 is its first release under the new name.
  • May 2025: HAProxy 3.2 LTS, with early ACME support.
  • August 2025: the first nginx-acme module release.
  • April 2026: nginx 1.30 becomes the stable branch.
  • June 2026: HAProxy 3.4 LTS.
  • October 2026: nginx 1.30.6 and 1.31.7 fix five issues on 14 October; Caddy 2.11.6 and 2.11.7 change defaults and fix regressions.

Operational costs

The licences are free. The real costs are time and attention.

  • Patching time. NGINX had eight security batches in 2026, and Caddy and HAProxy had several each. Budget a few hours a month for reading advisories and rolling out fixes, more if HTTP/3 is on.
  • Knowledge. NGINX knowledge is common in Indian teams and easy to hire for. HAProxy knowledge is rarer but deep when present. Caddy is quick to learn, but fewer people have run it at scale.
  • Custom builds. Caddy plugins and NGINX third-party modules both mean you own a build pipeline. Every security release becomes a rebuild.
  • Certificates. Built-in ACME (Caddy, HAProxy 3.2 and later, the nginx-acme module, Angie) removes a cron job but adds state that must be backed up and kept consistent across a pair of boxes. Two Caddy instances need shared storage or a split of domains, or they will both request the same certificates.
  • Monitoring. Whichever tool you pick, someone has to look at error rates, upstream timings and certificate expiry. Tie this into your uptime checks, as in the uptime monitoring post.
  • High availability. Two front boxes need a way to share an address (a cloud load balancer, keepalived with a virtual IP, or DNS). That part does not change with the proxy brand.

A decision guide

  • Is the box mainly serving many HTTPS sites with simple routing, and is your team small? Start with Caddy.
  • Is the box mainly balancing traffic across backends that fail or slow down, or proxying TCP? Start with HAProxy, on an LTS branch.
  • Does the box also serve files, cache responses and carry years of rewrites? Stay with NGINX, on a supported branch.
  • Do you need an nginx-style server with built-in ACME and a status API, and you accept a smaller vendor? Look at Angie. Do you want nginx development outside a large company? Look at freenginx.
  • Is your setup Kubernetes ingress or an API product with keys and quotas? This post is the wrong one; read the ingress and API gateway posts linked at the top.

Checklist

  • Configuration in Git, deployed by a script, identical on every front box.
  • Syntax check (nginx -t, caddy validate, haproxy -c) in the deploy script before every reload.
  • Version on a supported branch, with a named person watching security announcements.
  • Default server or site that rejects unknown hostnames.
  • Admin interfaces (Caddy admin API, HAProxy stats and runtime socket) bound to localhost or a Unix socket, never public.
  • Health checks on every upstream, with a tested drain procedure.
  • Logs include upstream address, upstream time and status.
  • Real client IP passed through, and trusted by the app only from the proxy.
  • Timeouts and buffering checked for WebSockets and long uploads.
  • Certificate renewal tested, and expiry monitored from outside.
  • HTTP/3 enabled only if you will patch it quickly.
  • Reload tested with a bad configuration and with a long-lived connection.

Common mistakes

  • Assuming the site address is the bind address. In our drill, a Caddy site written as http://127.0.0.1:18080 still listened on all interfaces until we set default_bind 127.0.0.1. The host in a site address is a matcher, not a bind. NGINX has the same trap with server_name versus listen.
  • Leaving an admin endpoint open. Several 2026 Caddy advisories were about the remote admin API. HAProxy’s stats admin page is documented as open to cross-site request forgery. Keep both private.
  • Copying regex rewrites from old answers. The worst NGINX bugs of 2026 were in rewrite and map with regex. Fewer, simpler regexes mean less exposure.
  • Forgetting old workers. NGINX workers serving WebSockets after a reload can keep running for hours. Set worker_shutdown_timeout if that is a problem.
  • Turning on HTTP/3 for fashion. It is the busiest area for fixes in all three tools. Turn it on when you can measure the benefit and patch quickly.
  • Two Caddy boxes, two ACME accounts. Without shared storage both boxes ask for the same certificates and may hit rate limits.
  • Trusting X-Forwarded-For from anyone. The app must accept it only from the proxy’s address.
  • Ignoring default changes. Caddy 2.11.6 changed header limits and timeouts. NGINX 1.29.7 turned on upstream keepalive by default. Read the release notes before upgrading.

The drill we ran

We ran a small drill on our shared test machine, as an ordinary user, in a scratch folder, listening only on 127.0.0.1. We did not install any package, start any system service or change any system file. The full notes are in our drill log; here is the summary.

  1. Download and verify. We downloaded the official Caddy 2.11.7 Linux amd64 release archive and its checksums file from GitHub. The SHA-512 we computed matched the published line. We did not verify the signature on the checksums file, so that step was not run.
  2. Two tiny backends. Two Python HTTP servers on 127.0.0.1 ports 18081 and 18082, each replying with its own name.
  3. A small Caddyfile. The admin API was on 127.0.0.1:20199. An HTTP site on 127.0.0.1:18080 proxied to both backends with round robin and an active health check every two seconds. An HTTPS site on 127.0.0.1:18443 used Caddy’s internal CA, with installing that CA into the system trust store turned off.
  4. The bind surprise. The first start listened on all interfaces for ports 18080 and 18443, even though the site addresses said 127.0.0.1. We stopped it at once, added default_bind 127.0.0.1, and after the restart every listener was on 127.0.0.1 only, including the UDP listener for HTTP/3.
  5. Balancing and headers. Six requests alternated between the two backends. The backend saw X-Forwarded-For, and the response carried a Via header from Caddy.
  6. HTTPS. curl negotiated HTTP/2 with status 200, and the response advertised HTTP/3 through Alt-Svc. We did not test an actual HTTP/3 request.
  7. Health check. After we stopped one backend, all requests went to the other within about five seconds. When it came back, traffic alternated again within about four seconds.
  8. Reload under load. A loop of sequential requests ran while we reloaded. Without reloads, 824 requests in six seconds all succeeded. With three reloads in eight seconds, 1,121 requests all succeeded, and each reload took about 0.04 seconds. In an earlier single-reload run, one request out of 999 failed and we did not capture why, so treat this as a smoke test, not proof.
  9. Bad configuration. A Caddyfile with an unknown directive, and a broken file, were both rejected by caddy reload, and the old configuration kept serving.
  10. Cleanup. We stopped Caddy and both backends and checked that no process or port was left.

Not run: we did not run NGINX or HAProxy, because doing so on the shared machine would have needed a package install. We did not test real ACME issuance against a public CA, an HTTP/3 client, or any benchmark. The request counts above are from a single loop on one machine and say nothing about capacity.

Unlearn, re-learn

  • Unlearn “the proxy is set-and-forget”. Re-learn that it is the most exposed process you run, and it needs a patch routine.
  • Unlearn “NGINX open source cannot do sticky sessions or upstream keepalive by default”. Re-learn the 2026 changelogs, because both changed.
  • Unlearn “HTTPS needs a cron job”. Re-learn that Caddy, HAProxy 3.2 and later, the nginx-acme module and Angie can renew certificates themselves, and that this state now needs backups.
  • Unlearn “a reload is risky”. Re-learn that all three reject a bad configuration and keep the old one, as long as you check before you reload.
  • Unlearn “the host in the configuration decides where it listens”. Re-learn to check with ss -ltnup after every change.
  • Unlearn “HTTP/3 is a free speed-up”. Re-learn that it is new code with new bugs, and turn it on deliberately.

Sources

comments powered by Disqus

Releted Posts

DNS servers in 2026: revisit CoreDNS, PowerDNS and BIND 9 before you add another DNS record by hand

In most companies, somebody still adds internal DNS records by hand. A new database gets an IP address, an engineer logs in to the DNS server, types one line into a zone file or a web console, and saves.

Read more

Container engines in 2026: revisit Docker Engine, Podman and containerd before you install another container daemon

On most teams, installing a container engine is the first thing done on a new machine and the last thing anybody thinks about.

Read more

Configuration management in 2026: revisit Ansible, Salt and Puppet before you write another server setup script

Almost every team has one: a setup.sh or bootstrap.sh written for the first production server and grown every year since. It installs packages, edits configuration files with sed, creates users and starts services.

Read more