Here’s a comparison of the web servers I’ve used the most.

What’s Wrong with “Dev” Web Servers?

A lot of web frameworks, like Django, come with built-in web servers. Usually, the docs will warn very strongly that these servers are only for development purposes and shouldn’t be used in production. But what’s actually wrong with using them in production? Of course, they don’t have anything like the featureset of solid production servers, but there’s one reason in particular that they’re not good: Slowloris attacks. This is where a single client DoSes a server just by making connections and being really slow about it. Imagine you’re waiting in a queue at a shop and some git at the front is paying for $100 of stuff by counting out small change, one coin at a time, checking each one carefully with a magnifying glass. That’s a Slowloris attack. Non-production web servers are like the cashier who just stands there waiting for the git to finish counting. Production-quality web servers will serve the other clients waiting in the queue, and will eventually tell the git to get lost.


I used to use Apache a lot, like most people did at the time, but I’ve hardly used it at all in the past decade. Newer servers have cleaner config syntax and better performance. The advantage that Apache used to have was that it could do things like serve PHP using just an internal module (mod_php). Other servers, like Nginx, require running an external process and reverse proxying to it (more on that below). Nowadays reverse proxying is more popular, so this is less of an advantage.

For a long time Apache was vulnerable to Slowloris, but now mod_reqtimeout can be used as a mitigation, although Apache’s performance still degrades more than other servers with a large number of concurrent connections. Some people daisy chain Nginx to Apache to whatever application they’re serving. This is usually to combat Slowloris, while keeping most of the web configuration in Apache. Since Nginx can pretty much serve any site Apache can, I don’t recommend this added complexity unless it’s needed for supporting a legacy application.

The one Apache feature I still sometimes miss is .htaccess files, which can be used to embed access control into the site data itself. It’s a convenient feature, but other servers (like Nginx) don’t support it by design for performance reasons.


Nginx (“Engine X”) is the server I end up using most these days. While Apache is built as an all-batteries-included web server, Nginx is built as a reverse proxy that can also serve web pages and do other things. “Reverse proxying” just means requests come in and get delegated to various web applications running behind Nginx (or to files on the filesystem). It’s a simple model that works well — compared to Apache’s module system, it integrates better with more web frameworks, it’s more flexible (e.g., can work with containerised apps) and has good scalability and performance pros.

Overall, Nginx is a great all-rounder that’s “good enough” for the majority of sites on the web: There are better caching servers (like Varnish) but Nginx is often “good enough”. There are better reverse proxies and load balancers (like HAProxy below) but Nginx is usually “good enough”. Nginx also has excellent performance stats and is popular even for very high traffic sites. Even if better caching (for example) is needed as a site grows, a more advanced cache server can be added to an existing Nginx-based stack, so it’s hard to go wrong using Nginx by default.

The config syntax for Nginx is nicer than Apache’s, but one downside is that the rules for exactly which config lines affect which requests can be a little confusing. I recommend reading “How nginx processes a request” from the official docs.


Officially, the name is pronounced “Lighty”.

Lighttpd is very similar to Nginx but less popular. There’s one important difference that earns it a place in my toolbox, though: it’s very easy to run as a non-root user to serve from an arbitrary directory. In theory Nginx can do that, too, but go try it for yourself. I’ve seen dev teams come up with all kinds of hacks for running Nginx in front of microservices in integration tests, but Lighttpd just works.


As the name suggests, HAProxy is not a web server, it’s a “do one thing and do it well” highly available proxy. This means it can’t serve web pages by itself, but it is an excellent, full-featured server for chanelling requests to multiple backends.

As I said above, Nginx is a “good enough” reverse proxy for most sites, but if the real problem you’re trying to solve is a proxying one, not a page serving one, I recommend trying HAProxy instead. It has better, more flexible support for load balancing, health checking, draining and access control. I once installed it for the Australian Digital Transformation Agency, replacing an existing Nginx proxy as a frontend for multiple Government websites (some public, some not). Not only did HAProxy have more features, the config and architecture were much simpler just because the tool was a better fit for the job. (One of the configuration files went from ~250 lines to ~40 lines.)

My one complaint about HAProxy is confusing and inconsistent naming in the configuration syntax — things like “cook” being used as a pointless abbreviation for “cookie” in some places but not others.


Caddy is a relatively new server with its main selling point being “just works” integration with the ACME protocol for getting SSL certificates from Let’s Encrypt. In short, if you have a domain name, your site can be on HTTPS with no extra configuration. In fact, the whole server design is basically to be a “just works” secure web server for people who’d rather not be configuring web servers. It’s great for dashboards and tooling used within a team, and possibly good for simple production sites, too, though I haven’t tried using it for any larger sites, yet.

Python SimpleHTTPServer

This is not a production web server, but it’s the easiest and most reliable way I know to get a web server running on an arbitrary *nix system — useful, say, if I want to test a new proxy or firewall config or whatever I’ve just installed. Most *nix distros have Python installed, and Python has a web server in its standard library.

$ cd /usr/share/doc
$ python -m SimpleHTTPServer 8000
Serving HTTP on port 8000 ...

The contents of /usr/share/doc are now being served on port 8000.