Apache is a highly configurable software. It has a ton of features, but each one comes at a hefty price. Tuning Apache is, in part, about allocating resources in an appropriate way. It also involves simplifying the configuration to include only what is necessary.
Configure MPM
Apache is modular because features can be easily added and removed. At the core of Apache, the Multi-Processing Module (MPM) provides this modular functionality—managing network connections and scheduling requests. MPM enables you to use threads and even migrate Apache to another operating system.
Only one MPM can be active at a time and must be compiled statically using --with-mpm=(worker|prefork|event).
The traditional model of using one process per request is called prefork. A newer threading model, called workers, uses multiple processes, each with multiple threads, to achieve better performance with lower overhead. The latest event MPM is an experimental model that uses separate thread pools for different tasks. To determine which MPM is being used, execute httpd -l.
Choosing which MPM to use depends on many factors. Until event MPM leaves experimental status, this model should not be considered, but rather a choice between using threads and not using them. On the face of it, if all underlying modules (including all libraries used by PHP) are thread-safe, threading is better than forking. Prefork is a safer choice; if you choose a worker, you should test it carefully. Performance gains also depend on the libraries and hardware included with your distribution.
Regardless of which MPM is chosen, it must be configured appropriately. In general, configuring MPM involves telling Apache how to control how many workers are running and whether they are threads or processes. Important configuration options for prefork MPM are shown in Listing 1.
Listing 1. Configuration of prefork MPM
StartServers 50
MinSpareServers 15
MaxSpareServers 30
MaxClients 225
MaxRequestsPerChild 4000
Compile your own software
When I first started working with UNIX®, I insisted on compiling software for everything that went into the system. Eventually, maintaining updates gave me trouble, so I learned how to build packages to simplify this task. Then I realized that most of the time I was doing the same thing as the release version. Now, for the most part, I try to stick with everything provided by my chosen distribution as much as possible, and only use my own packages when necessary.
Similarly, you may find that using a vendor-provided package is better in terms of maintainability than using the latest and greatest code. Sometimes the goals of performance tuning and system administration conflict. If you use a commercial version of Linux or rely on third-party support, you may have to consider vendor support.
If you insist on going your own way, learn how to build packages that work with your distribution, and learn how to integrate them into the patching system. This will ensure that the software, and any changes you make, are built consistently and work across multiple systems. You should also subscribe to appropriate mailing lists and RSS feeds to receive timely software updates.
The prefork model creates a new process for each request. Redundant processes remain idle to handle incoming requests, which reduces startup latency. The pre-configured configuration starts 50 processes as soon as the web server comes up and tries to keep 10 to 20 idle servers running. The hard limit on the number of processes is specified by MaxClients. Although a process can handle many consecutive requests, Apache cancels processes after the number of connections exceeds 4,000, which reduces the risk of memory leaks.
Configuring threaded MPM is similar, except you must decide how many threads and processes to use. The Apache documentation explains all necessary parameters and calculations.
It takes a few trials and errors to choose the value to use. The most important value is MaxClients. The goal is to allow enough worker processes or threads to run without causing the server to do excessive swapping. If incoming requests exceed processing capabilities, at least those that satisfy this value are served and other requests are blocked.
If MaxClients is too high, all clients will experience poor service as the web server attempts to swap out one process so that another can run. Setting it too low means that service may be denied unnecessarily. It is helpful to set this value by looking at the number of processes running under heavy load and the memory footprint caused by all Apache processes. If the value of MaxClients exceeds 256, ServerLimit must also be set to the same value. Please read the MPM documentation carefully for related information.
Tune the number of servers to start and keep idle based on the server's role. If the server is running only Apache, you can use a moderate value, as shown in Listing 1, because it will make the best use of the machine. If there are other databases or servers in the system, you should limit the number of running idle servers.
Use options and overrides effectively
Every request handled by Apache fulfills a complex set of rules that specify constraints or special instructions that the web server must follow. Access to a folder may be restricted to a specific folder by IP address, or a username and password may be configured. These options also include processing of specific files, for example, if a directory listing is provided, how the files should be processed, or whether the output should be compressed.
These configurations appear as containers in httpd.conf, such as
Listing 2. A Directory container applied to the root directory
AllowOverride None
Options FollowSymLinks
In Listing 2, the configuration between the pair of Directory and /Directory tags applies to the given directory and everything under that directory—in this case, the given directory is the root directory. Here, the AllowOverride tag indicates that the user is not allowed to override any options (more on this later). The FollowSymLinks option is enabled, which allows Apache to look at previous symbolic links to serve requests even if the file is located outside the directory containing the web file. This means that if a file in the web directory is a symbolic link to /etc/passwd, the web server will successfully serve the file when requested. If -FollowSymLinks is used, this feature will be disabled and the same request will cause an error to be returned to the client.
This last scene is what causes two concerns. The first aspect is related to performance. If FollowSymLinks is disabled, Apache must check all components (directories and files themselves) using that filename to make sure they are not symbolic links. This incurs additional overhead (disk operations). Another option called FollowSymLinksIfOwnerMatch will use symbolic links when the file owner is the same as the connection owner. For best performance, use the options in Listing 2.
At this point, security-conscious readers should feel wary. Security is always a trade-off between functionality and risk. In our case, the functionality is speed, and the risk is allowing unauthorized access to files on the system. One of the risk mitigation measures is that LAMP application servers are typically focused on one specific functionality and users cannot create dangerous symbolic links. If it is necessary to enable symbolic links, you can constrain them to a specific area of the file system, as shown in Listing 3.
Listing 3. Constraining FollowSymLinks to a user's directory
Options FollowSymLinks
Options -FollowSymLinks
As you can see, through the master server configuration, options can be configured individually for each directory. Users can override this server configuration themselves (if the administrator allows this via the AllowOverrides statement) by simply placing an .htaccess file in the directory. This file contains additional server directives that will be loaded and applied every time the directory containing the .htaccess file is requested. Although the problem of systems without users has been discussed before, many LAMP applications take advantage of this functionality to control access and implement URL rewriting, so it is necessary to understand how it works.
Even if the AllowOverrides statement prevents users from doing something you don't want them to do, Apache must check the .htaccess file to see if there is any work to be done. The parent directory can specify directives that are handled by requests from subdirectories, which means that Apache must search all components of the directory tree for the requested file. As you can imagine, this results in a lot of disk operations for each request.
The simplest solution is to disallow rewriting, which eliminates the need for Apache to check .htaccess. Any special configuration after that will be placed directly in httpd.conf. Listing 4 shows code added to httpd.conf to do a password check on a user's project directory, rather than putting it into a .htaccess file and relying on AllowOverrides.
AuthUserFile /home/user/.htpasswd
AuthName "uber secret project"
AuthType basic
Require valid-user
If the configuration is moved to httpd.conf and AllowOverrides is disabled, disk usage can be reduced. One user's project may not attract many clicks, but imagine how powerful this technology could be when applied to a busy site.
Listing 5. Scoping .htaccess checks
AllowOverrides None
AllowOverrides AuthConfig
After implementing Listing 5, Apache looks for the .htaccess file in the parent directory, but stops at the public_html directory because this feature is disabled for the rest of the file system. For example, if the request is for a file mapped to /home/user/public_html/project/notes.html, then only the public_html and project directories are searched.
One last tip about configuring each directory individually: Do it in order. Any article on Apache tuning will tell you that DNS lookups should be disabled via the HostnameLookups off directive, as it is a waste of resources to try to reverse resolve all IP addresses connected to your server. However, any hostname-based constraint forces the web server to perform a reverse lookup of the client's IP address and a forward lookup of the result to verify the authenticity of the name. Therefore, it is wise to avoid using client hostname-based access control and to limit its scope when it must be used.
Persistent connection
When a client connects to a Web server, the client is allowed to make multiple requests over the same TCP connection, which reduces the latency associated with multiple connections. This is useful when a Web page references multiple images: the client can request the page and then all the images over a single connection. The disadvantage is that the worker process on the server must wait for the client to close the session before moving on to the next request.
Apache enables you to configure how persistent connections (called keepalives) are handled. httpd.conf Global-level KeepAlive 5 allows the server to handle 5 requests on a connection before the connection is force-closed. Setting this value to 0 disables persistent connections. KeepAliveTimeout, also at the global level, determines how long Apache will wait for another connection before closing the session.
The handling of persistent connections is not a "one size fits all" configuration. For some Web sites, disabling keepalives is more appropriate (KeepAlive 0); for other sites, enabling it will bring significant benefits. The only solution is to try both configurations and see for yourself which one is more suitable. But if keepalives are enabled, it is wise to use a smaller timeout, such as 2, which is KeepAliveTimeout 2. This ensures that a client that wishes to make another request has sufficient time, and also ensures that the worker process is not left idle waiting for the next request that may never come.
Compression
The web server is able to compress the output before sending it back to the client. This will make pages sent over the Internet smaller, at the expense of CPU cycles on the web server. For servers that can afford the CPU overhead, this is a great way to speed up page downloads - it's not uncommon for pages to be compressed to a third of their original size.
Images are usually already compressed, so compression should be limited to text output. Apache provides compression through mod_deflate. Although mod_deflate can be easily enabled, it involves too many complexities that are explained in many manuals. This article does not cover the configuration of compression, but provides links to the appropriate documentation (see Resources section).
Tuning PHP
PHP is the engine that runs application code. You should install only those modules that you plan to use, and configure your web server to use PHP only for script files (usually those ending in .php) and not for all static files.
Opcode cache
When a PHP script is requested, PHP reads the script and compiles it into a Zend opcode, which is a binary representation of the code to be executed. This opcode is then executed and discarded by PHP. The opcode cache will save this compiled opcode and reuse it the next time the page is called. This will save a lot of time. There are many caches available, the one I use more commonly is eAccelerator.
To install eAccelerator, you need the PHP development library on your computer. Because different Linux distributions store files in different locations, it's best to obtain installation instructions directly from eAccelerator's Web site (see the Resources section for a link). It's also possible that your distribution already includes an opcode cache, which you just need to install.
No matter how you install eAccelerator on your system, there are some configuration options to be aware of. The configuration file is usually /etc/php.d/eaccelerator.ini. eaccelerator.shm_size defines the size of the shared cache, where compiled scripts are stored. The value is in megabytes (MB). Determine the appropriate size based on your application. eAccelerator provides a script to display the status of the cache, including memory usage, 64MB is a good choice (eaccelerator.shm_size="64"). If the value you select is not accepted, the kernel's maximum shared memory size must be modified. Add kernel.shmmax=67108864 to /etc/sysctl.conf and run sysctl -p to make the setting take effect. The unit of kernel.shmmax value is bytes.
If the allocation of shared memory exceeds the limit, eAccelerator must clear old scripts from memory. By default, this is disabled; eaccelerator.shm_ttl = "60" specifies that when eAccelerator runs out of shared memory, all scripts that have not been accessed within 60 seconds will be cleared.
Another popular eAccelerator alternative is Alternative PHP Cache (APC). The vendors of Zend also provide a commercial opcode cache, including an optimizer that further improves efficiency.
php.ini
PHP configuration is done in php.ini. Four important settings control how much system resources PHP can use, listed in Table 1.
Table 1. Resource-related settings in php.ini
Setting Description Recommended Value
max_execution_time How many CPU seconds a script can use 30
max_input_time How long a script waits for input data (seconds) 60
memory_limit How much memory (bytes) a script can use before being canceled 32M
output_buffering How much data (bytes) needs to be cached before sending the data to the client 4096
The exact number depends largely on your application. If you are receiving large files from the user, then max_input_time may have to be increased, either by modifying it in php.ini or by overriding it in code. Similarly, programs that use more CPU or memory may require larger settings. The goal is to mitigate the effects of excessive programs, so disabling these settings globally is not recommended. One more thing to note about max_execution_time: it represents the CPU time of the process, not the absolute time. So a program that does a lot of I/O and a little computation may run far longer than max_execution_time. This is why max_input_time can be greater than max_execution_time.
The number of logging records that PHP can perform is configurable. In a production environment, disabling all but the most important logging can reduce disk writes. If you need to use logs to troubleshoot problems, you can enable logging on demand. error_reporting = E_COMPILE_ERROR|E_ERROR|E_CORE_ERROR will enable enough logging to allow you to detect problems while eliminating a lot of useless content from the script.