Nginx vs Apache vs Caddy: Choosing a Web Server

Nginx vs Apache vs Caddy: Choosing a Web Server

Three web servers cover nearly all Linux deployments. They differ in architecture, configuration philosophy, and what they automate.

The architectural difference

Apache historically used a process or thread per connection. With 1,000 concurrent connections you had 1,000 threads, each with its own stack, and memory use scaled linearly. This is where the reputation for heaviness came from.

Nginx uses an event loop. A small fixed number of worker processes, typically one per CPU core, each handling thousands of connections with non-blocking I/O. Memory use is roughly flat as connections rise.

Caddy is also event-driven, written in Go, using goroutines which are cheap enough that the per-connection cost stays low.

The important correction: Apache’s event MPM has been the default for years and is event-driven too. The performance gap in the benchmarks people still cite reflects prefork, which most deployments no longer use.

apachectl -V | grep MPM
# Server MPM: event

If that says prefork, you are running the slow configuration, usually because mod_php requires it. Switching to PHP-FPM lets you move to event and is generally the single biggest improvement available to an old Apache setup.

Configuration

This is where the day-to-day difference is largest.

Nginx

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    root /var/www/example.com;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    location /api/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Declarative and hierarchical. The proxy_set_header lines are the ones people forget, and omitting them means your application sees every request as coming from 127.0.0.1, which breaks rate limiting and logging.

Apache

<VirtualHost *:443>
    ServerName example.com
    DocumentRoot /var/www/example.com

    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

    <Directory /var/www/example.com>
        Require all granted
        AllowOverride None
    </Directory>

    ProxyPass        /api/ http://127.0.0.1:3000/
    ProxyPassReverse /api/ http://127.0.0.1:3000/
</VirtualHost>

Verbose, and its per-directory blocks are genuinely useful for granular control.

Caddy

example.com {
    root * /var/www/example.com
    file_server

    handle /api/* {
        reverse_proxy 127.0.0.1:3000
    }
}

That is the complete configuration, including TLS. Caddy obtains a certificate from Let’s Encrypt on first request, renews it automatically, redirects HTTP to HTTPS, and sets sane security headers. No certbot, no cron job, no renewal hook.

The proxy headers Nginx needs spelled out are set by default.

What each is best at

Nginx

The default reverse proxy. If you are putting something in front of an application, this is what most documentation, most Docker images, and most of your future colleagues assume.

Strong at static files, high concurrency, load balancing, and caching. Weak at anything requiring per-directory user configuration, because it has no .htaccess equivalent by design.

That absence is deliberate: .htaccess requires Apache to check for the file in every directory along the path on every request, which is a filesystem cost per request. Nginx trades the flexibility for the speed.

Apache

Still the right answer in several situations.

Shared hosting, because .htaccess lets users control their own rewrite rules and access restrictions without server access.

Complex rewriting. mod_rewrite is more capable than Nginx’s rewrite, particularly for conditional logic.

Module availability. The largest module ecosystem of the three, and some things only exist for Apache.

AllowOverride None

Set that wherever you do not need .htaccess. It removes the per-request filesystem checks and is a meaningful performance improvement.

Caddy

The best choice when you want a working HTTPS site with minimal effort, which describes most self-hosted setups.

Automatic certificates are not a small convenience. Certificate renewal failures are a common cause of outages, and Caddy removes that entire class of problem. Our Let’s Encrypt guide covers doing it manually, which is what you are avoiding.

The tradeoffs are real: a smaller ecosystem, less written about it, and when you hit an unusual problem there is less material to search. It is also a younger project, though it has been production-ready for years now.

Reverse proxying, which is what most people want

Our reverse proxy explainer covers the concept, and the reverse proxy builder generates config for nginx or Caddy.

WebSockets are the part people get wrong. Nginx needs explicit upgrade headers:

location /ws/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400;
}

Without proxy_http_version 1.1 and the two upgrade headers, WebSocket connections fail in ways that look like application bugs. The long proxy_read_timeout prevents idle connections being dropped at the default 60 seconds.

Caddy handles WebSockets automatically with no configuration.

Always test before reloading

sudo nginx -t
sudo apachectl configtest
caddy validate --config /etc/caddy/Caddyfile

sudo systemctl reload nginx

Reload rather than restart. A reload starts new workers with the new config and lets existing connections finish; a restart drops them.

A reload with a broken config can leave the service stopped, which is why the test comes first. This is exactly what Ansible’s validate parameter exists for if you are managing config with Ansible.

Picking

SituationChoose
Reverse proxy for an appNginx, or Caddy
Self-hosted, want TLS handledCaddy
Shared hosting with user configApache
Static site at high trafficNginx
Team already knows one wellThe one they know
Complex rewrite logicApache

That last row is not a joke. Operational familiarity beats a marginal architectural advantage, and the difference between these three matters far less than whether the person on call at 3am understands the configuration.

For a first self-hosted setup, Caddy gets you to a working HTTPS site fastest. For anything you expect to grow into, Nginx is the safer long-term investment because of how much of the ecosystem assumes it.

Frequently Asked Questions

Is Nginx actually faster than Apache?

For serving static files and proxying at high concurrency, yes, because its event-driven model uses far less memory per connection. For dynamic content the difference largely disappears, since the application is the bottleneck. Apache with the event MPM closed most of the gap years ago, so the old benchmarks people cite are misleading.

What makes Caddy different from Nginx and Apache?

Caddy obtains and renews TLS certificates automatically with no configuration, using Let’s Encrypt or ZeroSSL. It also has a much simpler configuration format and ships as a single static binary with no dependencies. The tradeoff is a smaller ecosystem and less material available when you hit an unusual problem.

Can I still use .htaccess files with Nginx?

No, and this is deliberate. Nginx has no equivalent because checking for per-directory config files on every request costs performance. Rules that would live in .htaccess go in the main server configuration instead, which requires a reload to apply but is faster and easier to audit.

Which web server should I use for a reverse proxy?

Nginx is the conventional choice and the one most documentation assumes. Caddy is a strong alternative if you want automatic TLS and a simpler config. Both handle reverse proxying well, and for a self-hosted setup the automatic certificates often make Caddy the faster path to a working setup.

Does Apache still make sense in 2026?

Yes, particularly where you need per-directory configuration that users control, as in shared hosting, or where you depend on a module that only exists for Apache. Its module ecosystem remains the largest, and mod_rewrite is more capable than the Nginx equivalent for complex rules.

How do I test a web server config before reloading?

Run nginx -t, apachectl configtest, or caddy validate depending on the server. All three check syntax and report the file and line of any error. Always test before reloading, because a reload with a broken config can leave the service stopped.