[Today’s random Sourcerer profile: https://sourcerer.io/selfrefactor]

It’s true.

Apache is still the number one web server on the internet, and will likely be for quite some time.

I know Nginx is all the rage, but Apache is more extensible, easier to configure in a fair number of use cases, and, as of version 2.4 and later, is roughly comparable in performance.

Before I start, I want to state that I am not against Nginx. In fact, I use it on some of my sites and find it to be quite excellent. That said, I still think Apache is better in some cases. In this article I intend to explain my position.

A Patchy History

The Apache HTTP server was publicly released in 1995 by a group of eight core contributors. It was based on the original HTTP daemon (httpd) written by Rob McCool at the University of Illinois and improved with a collection of patches coordinated by the core team.

It is widely thought that these patches created the name “Apache” (a patchy web server), but Brian Behlendorf explained in a 2000 interview in Linux Magazine that the pun came after the original inspiration for the web server. It was respectfully titled after the Native American tribe for its resilient strength and take no prisoners attitude.

Since Apache was the most popular web server when the web was flourishing commercially, many languages and middleware titles were compatible. At first, CGI (common gateway interface) scripts (usually written in Perl) allowed a web page to do things like send an email, register a user, or protect content behind a login, instead of just deliver static HTML files.

Later, the PHP language and the MySQL database server became so ubiquitous in the hosting industry that it formed the LAMP stack, titled after Linux, the primary operating system of deployments, Apache HTTP Server, MySQL, and PHP.

Apache was so dominate in the web hosting landscape that it was a reasonable contender for production use on non-Linux systems like Windows, where it earned the moniker WAMP (Windows, Apache, MySQL, and PHP) and FAMP (FreeBSD).

Showing It’s Age

In our society we tend to associate aged anything (software, cars, and sadly, even people) as slow, not as interesting, or perhaps even less reliable. This is a grave mistake, as maturity demands experience. This only comes through age, and few software titles continue until they reach the “mature” stage.

Apache has earned this mature title. This isn’t a bad thing, but in some newer server management and DevOps methodologies the Apache HTTP server is seen as “legacy”. To be fair, age isn’t the only reason.

Apache has a slow release and development cycle. It’s only on version 2.4.34 as of this release. That’s over seventy six releases in twenty three years, averaging a point release every four months or so. And the time between major releases is substantial, providing stability for long term Linux distributions to release a current and stable version.

It’s a Major Event

This development cycle is surely a sign of maturity, but Apache’s slow embrace of threaded and event-based design lead to its sometimes-deserved reputation of being slow and bloated.

The original HTTP server would spawn a fork, or essentially a copy, of itself for each request. You could limit the number of new child processes in the configuration, but if you weren’t careful, you could consume all of your memory on handling requests — even those that are in a networking state where clients (that is, browsers) were the hold up, not the server.

This “prefork” configuration, as it was called, also made it easier to DoS (Denial of Service) attack an Apache server as if you sent it tons of nonsensical requests you could, if the server were improperly configured, fill up available RAM and cause system crashes or instability.

Interestingly enough, one of Apache’s strengths, its easy extensibility, would save the day. Through the module, or plug-in architecture of Apache, the MPM, or multi-processing module, could be replaced, allowing for different connection handling schemes.

Using this technique, threaded and event-based models could be used. The threaded module assigns requests to threads, allowing systems with multiple CPU cores to better scale on larger systems with more traffic.

The event-based model, most recently developed, takes advantage of Linux’s epoll and similar event IO API calls to help manage connections between workers and listener threads in a more optimized manner. While waiting for a connection (or a response), the same thread can perform other work. Using this technique, better performance and higher concurrency can be achieved.

Apache’s Unfortunate Timing

Apache didn’t add event-based handling until December of 2005 with release 2.2. This is only a year later than Nginx, but given that the popular Red Hat Enterprise Linux (and its companion, CentOS) version 4 shipped with 2.0.x and version 5 shipped with version 2.1.x, users of this distribution couldn’t officially install Apache 2.2 with event handling until version 6, released in December of 2013.

Red Hat users had to wait nine years (that’s over sixty in dog years, and over a billion years in computing) for a pre-installed Apache version that supported event handling. Of course it was possible to compile and install any version you pleased, but Red Hat’s well-built and officially supported packages are very stable and expertly integrated into the overall system, providing a significant advantage.

The situation with Debian wasn’t much better. Server admins running this popular distribution couldn’t install native Apache 2.2 or higher until Debian 4 (etch), released in 2010.

There are, of course, countless other distributions, and as discussed, custom installs would have fixed this problem, but the simple fact remains that Apache was stuck in the dark ages by mainstream distributions for years beyond this challenge from Nginx.

It’s Not All Timing

I don’t mean to blame downstream distribution packagers for all of Apache’s woes. No — to do a proper analysis we must take the good with the bad. Apache’s performance, at least in terms of out-of-the-box configuration, sucked. And as of this writing, it still does.

Red Hat and Debian release Apache preconfigured with the prefork MPM, negating the huge performance and concurrency increases that the event-based model provides.

We’ve got to do better!

Nginx is Awesome

Nginx, released in October of 2004, used event-driven, asynchronous request handling from the start. One of its primary design goals was to be faster than Apache and to solve the C10K problem, a challenge to handle large numbers of concurrent requests.

It meets its goals quite well. It’s fast, remarkably stable, and easy to configure. Because of this, it has earned the many accolades and swooning testimonials it has received. As I stated before, I use it, and I like it. But it has some significant drawbacks.

Nginx is Less Patchy, More Enginy

I don’t know if those are valid adjectives, but let’s go with it. Apache’s modular nature has proven to be a massive strength. Because of this, you can load new functionality into Apache quite easily.

Nginx has modules as well, but until very recently (this year), you could not load them dynamically. Even still, they must be built at the same time as Nginx. If you’re using a binary distribution, this won’t be an issue, but if your’e compiling this will need to be considered.

.htaccess Denied

A trivial concern for some, a monumental hurdle for others. If you are hosting one website and are comfortable configuring the web server directly (and restarting it each time you need to make a change to its configuration), this isn’t such a downside. But for your average server that hosts multiple sites and multiple web applications, this presents a problem.

The .htaccess file allows you to dynamically reconfigure many of the elements of Apache and its modules via a simple text file placed in either the website’s home directory or in a specific downstream path from that root. Using this technique, you can host multiple web applications with multiple settings that can be changed by non-system users (if desired) on the fly without restarting the server.

Of course there is a small performance penalty to pay for this convenience, but for development and testing this is enormously valuable. Having the ability to quickly change a configuration value (i.e. block a malicious referrer, change a rewrite rule, etc.) without restarting can be invaluable.

No Multi-user Support

Through modules, Apache can start web servers for separate virtual hosts as different users. This is extremely helpful on multi-user systems.

Nginx runs entirely as the www-data (on Debian, or nobody on Red Hat), or whatever user you configure it with, but uses that user for all of its processes.

Admittedly, with PHP, this matters less because a well-configured set of multiple PHP-FPM pools can run as individual users. But static setups may face special configuration and permission issues with Nginx and multiple UNIX users hosting content.

PHP-FPM (Fast CGI Process Manager)

Apache has a native PHP module, but it isn’t thread-safe by default. Because of this, PHP pages can be handled inside the web server process but only with the prefork module. Threaded and event models are not compatible with the native PHP module. If you want to use Apache’s event module, you’ll have to use PHP-FPM.

Nginx has no native module, so you must also use PHP-FPM with it.

But this isn’t a downside. PHP-FPM is faster than Apache’s native PHP module and provide more precise performance configuration. Even if, for some reason, you want to use the prefork MPM with Apache, I would still use PHP-FPM.

Debian 9 includes easily-installed support for PHP-FPM, but an Apache install on any modern distribution should activate the event MPM module and PHP-FPM should be the default configuration for PHP. In 2018, there is little reason to use any other configuration.

Nginx’s Specific Specialties

While I maintain that Apache is a better general-purpose web server, Nginx can really shine in specific areas.

Static content delivery (i.e. direct files like images, CSS files, ZIPs, etc., no PHP or other processed content) is faster and uses less resources with Nginx. Even Apache’s event module can’t keep up in this regard.

If you need to proxy a Ruby on Rails, Python, or Java application, Nginx is probably still the best choice. Though Passenger has an excellent module for Rails, it’s fair to say that Python and Java are best served with Nginx.

Best of Both Worlds

Some system administrators deploy both Apache and Nginx.

I was shocked to see that Plesk for Linux includes this configuration by default. It uses Nginx as a proxy / static content delivery server and uses Apache to provide a compatible backend for .htaccess and PHP.

Of course Nginx can run PHP via FPM, but php.ini values can be changed in some configurations far more easily with Apache.

Using this configuration can give you the best of both worlds, especially in situations where multiple unique sites with different requirements must be hosted on the same machine.

Quick Benchmarks

I ran some completely unscientific benchmarks. I used the same virtual machine for both: 1GB of RAM, 1 CPU Core running Debian 9. I installed Apache 2.4 and ran:

a2dismod mpm_prefork

a2enmod mpm_event

To switch to the event model. I installed Nginx with no modifications and ran them at different times using the Apache Benchmark (ab) tool. At first, I did a test solely with static content:

apache2: ab -n 1000 -c 10 http://localhost/ Concurrency Level: 10

Time taken for tests: 0.286 seconds

Requests per second: 3500.75 [#/sec] (mean)

Time per request: 2.857 [ms] (mean)

Time per request: 0.286 [ms] (mean, across all concurrent requests) apache2: ab -n 1000 -c 250 http://localhost/ Concurrency Level: 250

Time taken for tests: 0.550 seconds

Requests per second: 1817.08 [#/sec] (mean)

Time per request: 137.583 [ms] (mean)

Time per request: 0.550 [ms] (mean, across all concurrent requests) nginx: ab -n 1000 -c 10 http://localhost/ Concurrency Level: 10

Time taken for tests: 0.138 seconds

Requests per second: 7248.79 [#/sec] (mean)

Time per request: 1.380 [ms] (mean)

Time per request: 0.138 [ms] (mean, across all concurrent requests) nginx: ab -n 1000 -c 250 http://localhost/ Concurrency Level: 250

Time taken for tests: 0.159 seconds

Requests per second: 6292.91 [#/sec] (mean)

Time per request: 39.727 [ms] (mean)

Time per request: 0.159 [ms] (mean, across all concurrent requests)

It’s clear to see, and no surprise, that with static content, Nginx is faster.

Dynamic Content Benchmarking

I created a sample PHP file with nothing more than a call to phpinfo. This would give me the overhead of handling a PHP request without any specific PHP code that could skew the benchmark. Here are the results:

apache2+php-fpm: ab -n 1000 -c 50 http://localhost/phpinfo.php Concurrency Level: 50

Time taken for tests: 1.698 seconds

Requests per second: 589.03 [#/sec] (mean)

Time per request: 84.886 [ms] (mean)

Time per request: 1.698 [ms] (mean, across all concurrent requests) nginx+php-fpm: ab -n 1000 -c 50 http://localhost/phpinfo.php Concurrency Level: 50

Time taken for tests: 1.230 seconds

Requests per second: 813.27 [#/sec] (mean)

Time per request: 61.480 [ms] (mean)

Time per request: 1.230 [ms] (mean, across all concurrent requests)

Nginx appears to be faster, and this surprised me because it different with both my expectations. So, I installed a blank copy of WordPress and re-ran the tests:

apache2+php-fpm: ab -n 100 -c 50 http://localhost/ Concurrency Level: 50

Time taken for tests: 3.686 seconds

Requests per second: 27.13 [#/sec] (mean)

Time per request: 1842.993 [ms] (mean)

Time per request: 36.860 [ms] (mean, across all concurrent requests) nginx+php-fpm: ab -n 100 -c 50 http://localhost/ Concurrency Level: 50

Time taken for tests: 3.668 seconds

Requests per second: 27.26 [#/sec] (mean)

Time per request: 1834.046 [ms] (mean)

Time per request: 36.681 [ms] (mean, across all concurrent requests)

Adding an actual application changed things dramatically. Now Apache and Nginx showed equal performance — both serving up 27 requests per second.

SpeedMy did an excellent benchmark comparison of Apache and Nginx with dynamic (i.e. PHP) content. There results matched mine — both servers are roughly equivalent in performance when it comes to serving PHP and other dynamic content.

The main bottleneck in this case is the source of the dynamic content. PHP-FPM is lightning fast, but slapping Nginx in front of it isn’t going to increase it’s speed.

Nginx is no magic bullet. Code bottlenecks are more damaging to performance than your choice of web server.

Tale of Two Servers

A poorly configured Apache can be slow as molasses compared to the default configuration of Nginx. By the same token, a well-configured Apache can be just as fast as Nginx in dynamic content cases.

Why, then, would I still say Apache is still the best general purpose web server when Apache’s best case scenario gives it Nginx-like performance?

Performance is rarely everything. In some cases, it is, and in those scenarios I use Nginx. But oftentimes I need to support a wide range of applications (with mixed languages) on the same web server. In that case, the flexibility Apache provides wins me over — especially if I’m using PHP.

A Better Web

Regardless of your preference for these two fine web servers, it cannot be argued that Apache’s problems helped motivate Nginx’s developments, and Nginx provided an extra incentive to the Apache team to improve performance. They have both benefited each other, and thus the web at large.

If you left Apache long ago for the land of faster default performance with Nginx, I hope I have been able to persuade you to give Apache another try.