Skip to main content
apache_web_server_logo

How to make Apache faster for Drupal

If you are reading this blog post you must be striving to improve the performance of your site. When we speak about performance two things should be taken into account essentially.

  1. Number of requests the site is able to serve (The more, The better)
  2. Response time per request (The least, The better)

I guess it is not much needed to stress as how essential the response time plays in deciding the success of a website. Apache the prominent Web server plays a key role in this connection.
When configuring Apache for Drupal most of the tutorials recommend changing AllowOverride directive to All, including the official handbook page in drupal.org http://drupal.org/node/36628 , while the default is None.

 

This is needed to make .htaccess file in Drupal core to override the Apache config as needed. Especially to get clean url working. Besides it let's to set page expiry time, and setting necessary headers for css/js gzip compression if supported.

But this is a performance killer change!

Reason when AllowOverride is set to All. It prompts Apache to look for the existence of .htaccess file in recursive sub-folders as below.

/.htaccess
/www/.htaccess
/www/htdocs/.htaccess
/www/htdocs/example/.htaccess

and goes on...

Eventually the number of I/O calls needed to serve the page request spikes up. Beware, I/O calls are expensive. And it goes further worst depending on the number of modules you have. Especially if you have hosted the site in VPS environments where disk operations are sub-optimal and can't be fully virtualized. The workaround for this problem is to tell Apache explicitly the location as where to look for overriding .htaccess file.

The following config helped us to fix the issue.

# Site example.com
<VirtualHost *:80>
  ServerAdmin user@example.com
  ServerName example.com
  ServerAlias www.example.com
  DocumentRoot /var/www/example.com
  <Directory /var/www/example.com>
    Options -Indexes -FollowSymLinks MultiViews
    AllowOverride None
    Order allow,deny
    allow from all
    Include /var/www/example.com/.htaccess    
  </Directory>
  <Directory /var/www/example.com/sites/default/files>
    Include /var/www/example.com/sites/default/files/.htaccess
  </Directory>
</VirtualHost>
In favor of performance, AllowOverride directive has been changed to None. And with Include directive we have explicitly mentioned the paths as where to look for .htacess files. This way, we could get the job done without compromising the performance & Drupal's features like clean url, expiry headers, etc. Some anonymous benchmark reports posted in groups.drupal.org says that Apache is certainly faster in serving dynamic pages over nginx when this specific directive is altered as mentioned above. But in Drupal besides root folder, .htaccess can be found in Temporary File system's files directory. Even some libraries like ckeditor and contrib modules shipped with their own .htaccess. This could happen when modules like Boost are installed. In case of Boost module additional .htaccess files are created in cache folder. To workaround this we could use find command or similar, we will have to search for the existence of .htaccess manually and mention their path in virtual host config.

Additionally apache's module mod_expires and mod_headers could be installed as Drupal's core .htaccess tries to leverage it if they exist.

Comments

JvE (not verified)

Tue, 04/02/2013 - 01:51

This looks very wrong.

If you include /var/www/example.com/sites/default/files/.htaccess inside <Directory /var/www/example.com> won't it apply the settings from that .htaccess file to that directory rather than the one you want? The reason different settings are in different .htaccess files in different locations is that those locations needs specific settings. If you go this way please use the proper <Directory> directives for separate
locations.

Rajesh Kumar R.K (not verified)

Tue, 04/02/2013 - 03:21

Good Info...

Did you find any statitical report on Improvement before and after tweaking up.

Dirk (not verified)

Tue, 04/02/2013 - 08:33

Your initial proposal seemed like a good plan to speed up Drupal. But a quick
"real world" test showed some shortcomings:

* There are a couple of directory based .htacces files. These
cannot simply included as stated above. Instead each of them has
to be embedded into a <directory /...> statement, because
they contain "Deny from all" statements valid only for
specific directories:

<Directory /var/www/files/public/>
Include /var/www/files/public/.htaccess
</Directory>

* Running the Apache benchmark against apache 2.2.15
with fcgi mode and running a drupal 7 and fetching
page with a length of 55kb brought no significant speed
improvements (I have the benchmark results available)

ab -c 20 -n 1000 http://example.net/a_55k_page

* Instead it happend often that the server crashed

Send request timed out!
apr_socket_recv: Operation timed out (60)
Total of 117 requests completed

This never happened with the "classic" setup (AllowOverride All).

Best regards
Dirk

@JvE and @Dirk, the second .htacess include must be in separate <directory>. Thanks! I have updated the post accordingly.

@Rajesh Kumar R.K and @Dirk, I had no benchmark results when I published this blog. But theoretically it should work fine also I noticed speed difference visually after the change (could be by imagination)

Now I have benchmark results to share with you,

With AllowOverride None

$ab -c 10 -n 500 http://drupal7.localhost/

This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking drupal7.localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Finished 500 requests

Server Software: Apache/2.2.22
Server Hostname: drupal7.localhost
Server Port: 80

Document Path: /
Document Length: 10550 bytes

Concurrency Level: 10
Time taken for tests: 74.825 seconds
Complete requests: 500
Failed requests: 0
Write errors: 0
Total transferred: 5499500 bytes
HTML transferred: 5275000 bytes
Requests per second: 6.68 [#/sec] (mean)
Time per request: 1496.495 [ms] (mean)
Time per request: 149.650 [ms] (mean, across all concurrent requests)
Transfer rate: 71.78 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.9 0 7
Processing: 851 1493 62.7 1488 1803
Waiting: 829 1333 55.6 1324 1631
Total: 851 1494 62.7 1488 1803

Percentage of the requests served within a certain time (ms)
50% 1488
66% 1497
75% 1506
80% 1513
90% 1544
95% 1574
98% 1625
99% 1788
100% 1803 (longest request)

With AllowOverride All

$ ab -c 10 -n 500 http://localhost/drupal7/

This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Finished 500 requests

Server Software: Apache/2.2.22
Server Hostname: localhost
Server Port: 80

Document Path: /drupal7/
Document Length: 10727 bytes

Concurrency Level: 10
Time taken for tests: 74.408 seconds
Complete requests: 500
Failed requests: 0
Write errors: 0
Total transferred: 5588000 bytes
HTML transferred: 5363500 bytes
Requests per second: 6.72 [#/sec] (mean)
Time per request: 1488.166 [ms] (mean)
Time per request: 148.817 [ms] (mean, across all concurrent requests)
Transfer rate: 73.34 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 1.4 0 10
Processing: 999 1486 41.8 1482 1695
Waiting: 984 1324 34.0 1321 1490
Total: 999 1486 41.7 1482 1695

Percentage of the requests served within a certain time (ms)
50% 1482
66% 1493
75% 1499
80% 1504
90% 1527
95% 1541
98% 1557
99% 1633
100% 1695 (longest request)

Inference -- Time taken for tests and Requests per second doesn't seem to have huge difference. But the total number of requests severed and Connection Times value are in favor of better performance. So it is certain that AllowOverride None is better (at least it is not making things worst).

To be honest, we have been using a variant of above mentioned configuration for this site!! No issues noticed this far.

It is to be noted when a load test is performed the load average of system will be high. We might need to wait until the load average comes back to normal state before running the next test.

Also if you are using any modules that depends on external service / network like AmazonS3 or Feed Aggregator, the result might be inconsistent.