Apache & PHP

Introduction

I did a lot of reading and research into the best method of running Apache and PHP and together on a shared, limited resourced environment such as my Linode server.

The initial plan was to use the following combination:

  • Running the MPM-Worker version of Apache
    To understand more about the Multi-Processing Module (MPM) versions of Apache then take a look at the Apache HTTP Server Version 2.2 Documentation.
  • Running the CGI version of PHP

I then stumbled across a great article by Brandon Turner titled FastCGI with a PHP APC Opcode Cache. I recommend you read this as a background to what is about to follow on this page.

A quick summary of the benefits of my final setup:

  • Static files can be served by a lightweight multi-threaded web server process while PHP scripts are served by a single-threaded FastCGI process. What’s more, if PHP crashes, it doesn’t bring down the entire web server.
  • Control over the number of FastCGI processes spawned for each website.
  • Each websites PHP scripts are executed with the website user’s credentials. This leads to a more secure environment for both the host and the shared user.
  • APC opcode cache can dramatically speed up PHP execution.
  • Shared APC caches across the processes spawned for each website.
  • Memory assigned to APC is configurable for each website.

Install PHP 5

Issue the following command to install the CGI version of PHP5 and various 3rd party modules:

sudo aptitude install php5-cgi php5-curl php5-gd php5-mysql php5-xsl

Install and Configure the APC Opcode Cache

One of the easiest and most effective things you can do to speed up your PHP scripts is to enable an opcode cache such as APC, XCache or eAccelerator. An opcode cache caches the compiled state of PHP scripts in shared memory. Thus each time a PHP script is run, the server doesn’t have to waste time compiling the source code. Opcode caches can speed up execution of scripts by up to 5 times and decrease server load.

To install APC, simply run:

sudo aptitude install php-apc

Once installed, we need to configure APC by editing the apc.ini which can be located at /etc/php5/conf.d/apc.ini. The default settings will work fine with one exception. We need to comment out apc.shm_size="30" (line 5 below). Commenting this line will enable us to set it per user later.

The apc.ini file I use looks like this:

extension=apc.so
apc.enabled="1"
apc.shm_segments="1"
;commenting this out allows you to set it in each fastcgi process
;apc.shm_size="30"
apc.num_files_hint="1024"
apc.ttl="7200"
apc.user_ttl="7200"
apc.gc_ttl="3600"
apc.cache_by_default="1"
;apc.filters=""
apc.mmap_file_mask="/tmp/apcphp5.XXXXXX"
apc.slam_defense="0"
apc.file_update_protection="2"
apc.enable_cli="0"
apc.max_file_size="1M"
apc.stat="1"
apc.write_lock="1"
apc.report_autofilter="0"
apc.include_once_override="0"
apc.rfc1867="0"
apc.rfc1867_prefix="upload_"
apc.rfc1867_name="APC_UPLOAD_PROGRESS"
apc.rfc1867_freq="0"
apc.localcache="0"
apc.localcache.size="512"
apc.coredump_unmap="0"

Install Apache 2

So as mentioned above we'll be using the mpm-worker version of Apache 2.

sudo aptitude install apache2-mpm-worker

Install mod_fastcgi

mod_fastcgi is only available in the multiverse repositories which are not enabled by default. So we have to add these to our sources list first. Append the following lines to the bottom of the /etc/apt/sources.list file.

## multiuniverse repositories - uncomment to enable
deb http://us.archive.ubuntu.com/ubuntu/ lucid multiverse
deb-src http://us.archive.ubuntu.com/ubuntu/ lucid multiverse

Now we just need to refresh the repository list.

sudo aptitude update

We can now go ahead and install FastCGI module for Apache.

sudo aptitude install libapache2-mod-fastcgi

We may have to enable this module.

sudo a2enmod fastcgi

Enable mod_actions

I found this wasn't pre-enabled in the Apache 2 install that I had. It's needed so the handler actions can be set in the forthcoming FastCGI module configuration.

sudo a2enmod actions

Edit global Apache settings

There are two sets of settings you must configure in Apache: those that affect all users and those that affect a specific user. This section describes global settings that affect all users.

I keep my global settings in the /etc/apache2/mods-available/fastcgi.conf file, but these can go in any part of your http.conf file. Most of the time you do not want this in a VirtualHost section. My global mod_fastcgi settings look like this:

<IfModule mod_fastcgi.c>
FastCgiConfig -idle-timeout 20 -maxClassProcesses 1
FastCgiWrapper On

AddHandler php5-fcgi .php
Action php5-fcgi /cgi-bin/php-fastcgi

<Location "/cgi-bin/php-fastcgi">
   Order Deny,Allow
   Deny from All
   Allow from env=REDIRECT_STATUS
   Options ExecCGI
   SetHandler fastcgi-script
</Location>
</IfModule>

FastCgiConfig
The FastCgiConfig configuration directive sets parameters for all dynamic FastCGI processes. The idle-timeout causes FastCGI to abort a request if there is no activity for more than 20 seconds. The maxClassProcesses option is very important: it tells FastCGI to only spawn one php-cgi process regardless of how many requests are pending. Remember that our PHP process will spawn its own children, so FastCGI only needs to spawn one. Until this APC bug is fixed, this is necessary to allow sharing the APC cache among children.

FastCgiWrapper
The FastCgiWrapper configuration directive is needed to allow suEXEC to work.

AddHandler / Action
The AddHandler and Action configuration directives tell Apache to handle all files ending in .php with the php-fastcgi script in cgi-bin. In the next step, you’ll see how we alias this cgi-bin directory for each individual user.

Location
The Location directive tells Apache how to handle requests to /cgi-bin/php-fastcgi. The Allow from env=REDIRECT_STATUS on line 13 prevents users from executing this script directly. With this line, the only way to execute php-fastcgi is by requesting a file ending in .php.

Install/enable Apache suEXEC

By using a mechanism such as suEXEC we can run each FastCGI process as a different user. In the shared hosting context, each website user’s PHP scripts are executed with the website user’s credentials. This leads to a more secure environment for both the host and the shared user.

Install the suEXEC components:

sudo a2enmod suexec
sudo aptitude install apache2-suexec

Setting a Document Root for Websites

I chose to place all of my websites onto the data disk that I created and mounted as part of the Adding a Disk Image instructions.

I created this as:

sudo mkdir /mnt/data/www

In addition I wanted to have the Apache2 group (www-data) set as the group and all files created inside to inherit this as their group privilege.

sudo chgrp -R www-data /mnt/data/www
sudo chmod +s /mnt/data/www

For the chroot feature of SFTP users to work all folders in the path to our document root need to be non group writable.

sudo chmod g-w /mnt/data/www

You may find helpful to have a shortcut (/www) to this new folder from the root directory.

sudo ln -s /mnt/data/www /www

SuEXEC requires the CGI script to be under the server's DocumentRoot (not the VirtualHost DocumentRoot). It is permitted, however, for the VirtualHost DocumentRoot to be a symlink to a directory that appears under the real DocumentRoot.

By default suExec uses /var/www as the document root, so I simply redirected this to the new document root using a symlink

sudo ln -s /mnt/data/www /var/www

Tuning Apache

The following sections are purely optional but I found that the default Apache install is based on a conservative guess at the capabilities of your server. The tuning options help make the most of the resources available on my Linode.

Loaded Modules

Reduce memory footprint by loading only the required modules. Use a2enmod and a2dismod to quickly enable/disable Apache modules from the command line.

SymLinks

Make sure 'Options +FollowSymLinks -SymLinksIfOwnerMatch' is set for all directories. Otherwise, Apache will issue an extra system call per filename component to substantiate that the filename is NOT a symlink; and more system calls to match an owner.

<Directory />
  Options FollowSymLinks
</Directory>

AllowOverride

Set a default 'AllowOverride None' for your filesystem. Otherwise, for a given URL to path translation, Apache will attempt to detect an .htaccess file under every directory level of the given path.

<Directory />
  AllowOverride None
</Directory>

ExtendedStatus

If mod_status is included, make sure that directive 'ExtendedStatus' is set to 'Off'. Otherwise, Apache will issue several extra time-related system calls on every request made.

ExtendedStatus Off

Timeout

Lower the amount of time the server will wait before failing a request.

Timeout 45

Managing multiple configuration tweaks

I chose to put all of my configuration tweaks in files inside the /etc/apache2/conf.d/ directory. This directory is parsed during the configuration stage of Apache and using this approach avoids having to edit any of the core Apache config files such as /etc/apache2/apache2.conf. I've separated my tweaks into distinct blocks based on the type of tweak.

Performance Tuning

sudo nano /etc/apache2/conf.d/performance-tuning

Now add the following lines to this new file (case sensitive):

<IfModule prefork.c>
  StartServers            2
  MinSpareServers         5
  MaxSpareServers        10
  ServerLimit            15
  MaxClients             15
  MaxRequestsPerChild  2000
</IfModule>

<IfModule worker.c>
  StartServers            2
  MaxClients            150
  MinSpareThreads        25
  MaxSpareThreads        75
  ThreadsPerChild        25
  MaxRequestsPerChild     0

  # KeepAlive
  KeepAlive On
  KeepAliveTimeout 15
  MaxKeepAliveRequests 80

</IfModule>

<IfModule mod_status.c>
  # If mod_status is included, make sure that directive 'ExtendedStatus'
  # is set to 'Off'. Otherwise, Apache will issue several extra time-related
  # system calls on every request made.
  ExtendedStatus Off
</IfModule>

# Lower the amount of time the server will wait before failing a request.
Timeout 45

Security

I've adjusted and added to the existing security conf file that comes pre-installed with Apache2.

sudo nano /etc/apache2/conf.d/security

Now add the following lines to this new file (case sensitive):

#
# Disable access to the entire file system except for the directories that
# are explicitly allowed later.
#
# This currently breaks the configurations that come with some web application
# Debian packages. It will be made the default for the release after lenny.
#
#<Directory />
#    AllowOverride None
#    Order Deny,Allow
#    Deny from all
#</Directory>

# Changing the following options will not really affect the security of the
# server, but might make attacks slightly more difficult in some cases.

#
# ServerTokens
# This directive configures what you return as the Server HTTP response
# Header. The default is 'Full' which sends information about the OS-Type
# and compiled in modules.
# Set to one of:  Full | OS | Minimal | Minor | Major | Prod
# where Full conveys the most information, and Prod the least.
#
#ServerTokens Minimal
ServerTokens Prod
#ServerTokens Full

#
# Optionally add a line containing the server version and virtual host
# name to server-generated pages (internal error documents, FTP directory
# listings, mod_status and mod_info output etc., but not CGI generated
# documents or custom error documents).
# Set to "EMail" to also include a mailto: link to the ServerAdmin.
# Set to one of:  On | Off | EMail
#
ServerSignature Off
#ServerSignature On

#
# Allow TRACE method
#
# Set to "extended" to also reflect the request body (only for testing and
# diagnostic purposes).
#
# Set to one of:  On | Off | extended
#
TraceEnable Off
#TraceEnable On

<IfModule mod_ssl.c>

#   SSL Protocol support:
# Restrict protocols down to SSLv3 and TLSv1, i.e. disable SSLv2.
SSLProtocol -ALL +SSLv3 +TLSv1

#   SSL Cipher Suite:
# Disable weaker ciphers
SSLCipherSuite ALL:!aNULL:!eNULL:!ADH:RC4+RSA:+HIGH:!MEDIUM:!LOW:!SSLv2:!EXP

</IfModule>

HTML5 Boilerplate

The tweaks here are based strongly on the work of the guys at http://html5boilerplate.com/. I've incorporated most of their suggestions with the exception of the caching configuration. I've left this out purely as I need some time to evaluate my needs in this area.

sudo nano /etc/apache2/conf.d/html5-boilerplate

Now add the following lines to this new file (case sensitive):

# Apache configuration file
# httpd.apache.org/docs/2.2/mod/quickreference.html

# Techniques in here adapted from all over, including:
#   Kroc Camen: camendesign.com/.htaccess
#   perishablepress.com/press/2006/01/10/stupid-htaccess-tricks/

# Force the latest IE version, in various cases when it may fall back to IE7 mode
# github.com/rails/rails/commit/123eb25#commitcomment-118920
# Use ChromeFrame if it's installed for a better experience for the poor IE folk
<IfModule mod_setenvif.c>
  <IfModule mod_headers.c>
      BrowserMatch MSIE ie
      Header set X-UA-Compatible "IE=Edge,chrome=1" env=ie
  </IfModule>
</IfModule>

<IfModule mod_headers.c>
#
# Because X-UA-Compatible isn't sent to non-IE (to save header bytes),
# We need to inform proxies that content changes based on UA
#
  Header append Vary User-Agent
# Cache control is set only if mod_headers is enabled, so that's unncessary to declare
</IfModule>

# hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/
# Disabled. Uncomment to serve cross-domain ajax requests
#<IfModule mod_headers.c>
#  Header set Access-Control-Allow-Origin "*"
#</IfModule>

#
# allow access from all domains for webfonts
# alternatively you could only whitelist
# your subdomains like "sub.domain.com"
#
<FilesMatch "\.(ttf|otf|eot|woff|font.css)$">
    <IfModule mod_headers.c>
        Header set Access-Control-Allow-Origin "*"
    </IfModule>
</FilesMatch>

# 
# Video
AddType video/ogg  ogg ogv
AddType video/mp4  mp4
AddType video/webm webm

# Proper svg serving. Required for svg webfonts on iPad
# twitter.com/FontSquirrel/status/14855840545
AddType image/svg+xml                 svg svgz
AddEncoding gzip                       svgz 

# Webfonts
AddType application/vnd.ms-fontobject eot
AddType font/truetype               ttf
AddType font/opentype               otf
AddType font/woff                   woff

# Assorted types
AddType image/vnd.microsoft.icon       ico
AddType image/webp                     webp
AddType text/cache-manifest            manifest
AddType text/x-component               htc
AddType application/x-chrome-extension crx

#
# allow concatenation from within specific js and css files
#
# e.g. Inside of script.combined.js you could have
#
#   <!--#include file="jquery-1.4.2.js" -->
#   <!--#include file="jquery.idletimer.js" -->
#
# and they would be included into this single file
#
#
#
# this is not in use in the boilerplate as it stands. you may
# choose to name your files in this way for this advantage
# or concatenate and minify them manually.
#
# Disabled by default.

# <FilesMatch "\.combined\.(js|css)$">
#         Options +Includes
#         SetOutputFilter INCLUDES
# </FilesMatch>

AddType text/cache-manifest           manifest

# gzip compression.
<IfModule mod_deflate.c>

# html, txt, css, js, json, xml, htc:
  AddOutputFilterByType DEFLATE text/html text/plain text/css application/json
  AddOutputFilterByType DEFLATE text/javascript application/javascript application/x-javascript
  AddOutputFilterByType DEFLATE text/xml application/xml text/x-component

#  webfonts and svg:
  <FilesMatch "\.(ttf|otf|eot|svg)$" >
      SetOutputFilter DEFLATE
  </FilesMatch>
</IfModule>

# use utf-8 encoding for anything served text/plain or text/html
AddDefaultCharset utf-8
# force utf-8 for a number of file formats
AddCharset utf-8 .html .css .js .xml .json .rss

References

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License