How to install and optimize Apache with mpm_event, php5-fpm and MariaDB

A note to the reader

This post is a legacy post. The legacy posts that are available on this website were written many years ago. These posts are made available here for archival purposes only.
They reflect the age I was, and the level of knowledge that I had when I wrote them, and they may contain outdated information, so please keep that in mind as you proceed to read this article.

Original Title: How to install a highly optimized LAMP stack with mpm_event and MariaDB

Apache is today more important as a web server than ever before. Most PHP scripts assume (or even require) apache to be installed in order to run. However, for most people running a VPS with less than a GB of RAM, having to run apache is a deal breaker. The same applies for the stock MySQL configuration. This tutorial will help you to install Apache with the event MPM + PHP [fastcgi] + MariaDB 5.5

Step 1 - Install Prerequisites

This tutorial requires root. If you’re not already root, run this command:

1
sudo su

and gain root priviledges. Alternatively, you may prefix the commands with sudo also. We will use the aptitude package manager as a replacement for apt-get. Since it is not available by default, go ahead and install it.

1
apt-get install -y aptitude

Step 2 - Install Apache

Apache is sometimes included by default by ubuntu, however aptitude will take care of removing the preform mpm when we install the apache fastcgi module, and the event mpm

1
aptitude install -y libapache2-mod-fastcgi apache2-mpm-event

Then, we disable the build in apache php modules as they consume too much precious memory

1
2
3
a2dismod php4 # PHP v4 is outdated. It should be criminal to even have it on a system
a2dismod php5 # mod_php is not usable with worker mpm, therefore we disable it
a2dismod fcgid # fcgid is not required. We will set up fastcgi later.

Then, we will enable the mod_fastcgi, mod_ssl, mod_actions and mod_rewrite

1
2
3
4
a2enmod fastcgi
a2enmod ssl
a2enmod actions
a2enmod rewrite

Step 3 - Install PHP

We will be running PHP as a FastCGI process. We will configure FastCGI to run as a separate server and have a 120 second timeout.

First, we configure the apache2 fastcgi module.

1
nano /etc/apache2/mods-available/fastcgi.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<IfModule mod_fastcgi.c>
FastCgiIpcDir /var/lib/apache2/fastcgi
# Set FPM to run externally
FastCGIExternalServer /srv/www/fcgi-bin.d/php5-fpm -pass-header Authorization -idle-timeout 120 -socket /var/run/php5-fpm-www-data.sock
Alias /php5-fcgi /srv/www/fcgi-bin.d
AddType application/x-httpd-php5 .php
# FPM will handle all PHP files
<FilesMatch "\.php$">
SetHandler php-fpm
</FilesMatch>
Action php-fpm /php5-fcgi/php5-fpm

# Prevent external access to FPM directory
<Location "/php5-fcgi/php5-fpm">
    Order deny,allow
    Deny from All
    Allow from env=REDIRECT_STATUS
</Location>

</IfModule>

Finish off by creating the FPM directory

1
mkdir -p /srv/www/fcgi-bin.d

Then, we install the php packages Base PHP packages. Essential to run PHP

1
aptitude install -y php5-fpm php5-common php-apc php5-mysqlnd php5-dev

Secondary, but useful PHP modules/packages. Feel free to omit

1
aptitude install -y php5-memcache php5-curl php5-mcrypt php5-xsl php5-gd php5-imagick php5-snmp php5-xmlrpc

And, we’re done installing PHP

Step 4 - Install MariaDB

We will be using MariaDB as our database server. It is an enhanced, drop-in replacement for MySQL that is developed by the original team and is more performant

Install MariaDB

1
aptitude -y install mariadb-server mariadb-client

Secure MariaDB

1
mysql_secure_installation

And we’re done!

Step 5 - Pre-Optimization

Before we optimize, we need to restart apache2 and php.

1
2
service php5-fpm restart
apache2ctl graceful

Step 6 - Optimize Apache

The only optimization apache really needs is the config optimization. Edit the config file:

1
nano /etc/apache2/apache2.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
...
LockFile ${APACHE_LOCK_DIR}/accept.lock
...
PidFile ${APACHE_PID_FILE}
...
Timeout 300
...
KeepAlive On
...
MaxKeepAliveRequests 100
...
KeepAliveTimeout 5
...
<IfModule mpm_worker_module>
    StartServers          2
    MinSpareThreads      25
    MaxSpareThreads      75 
    ThreadLimit          64
    ThreadsPerChild      25
    MaxClients          150
    MaxRequestsPerChild   0
</IfModule>
...
<IfModule mpm_event_module>
    StartServers          1
    MinSpareThreads       2
    MaxSpareThreads       5 
    ThreadLimit           20
    ThreadsPerChild       20
    MaxClients            60
    MaxRequestsPerChild   5000
</IfModule>
...
# These need to be set in /etc/apache2/envvars
User ${APACHE_RUN_USER}
Group ${APACHE_RUN_GROUP}

...

AccessFileName .htaccess

...
#
# The following lines prevent .htaccess and .htpasswd files from being 
# viewed by Web clients. 
#
<Files ~ "^\.ht">
    Order allow,deny
    Deny from all
    Satisfy all
</Files>

...
<Directory />
    Options -Indexes -FollowSymLinks
    AllowOverride All
    Order allow,deny
    allow from all
</Directory>
...
DefaultType None
...
HostnameLookups Off

...
ErrorLog ${APACHE_LOG_DIR}/error.log
...
LogLevel warn
...
# Include module configuration:
Include mods-enabled/*.load
Include mods-enabled/*.conf
...
# Include list of ports to listen on and which to use for name based vhosts
Include ports.conf
...
#
# The following directives define some format nicknames for use with
# a CustomLog directive (see below).
# If you are behind a reverse proxy, you might want to change %h into %{X-Forwarded-For}i
#
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
...
# Include generic snippets of statements
Include conf.d/
...
# Include the virtual host configurations:
Include sites-enabled/

Step 7 - Optimize FPM

Note: Most of these steps are done with automated linux tools like sed which make it easy to edit the configuration files. If you want to know what’s going on behind the scenes, refer to the comments above each command block.

Before we optimize FPM, we need to stop the service

1
service php5-fpm stop

The FPM configuration file is lengthy and complex. To quickly do the configuration automatically, run:

1
2
3
4
5
6
7
php_fpm_conf="/etc/php5/fpm/pool.d/www.conf"
# Configure these values to your liking. Optimized for 512MB RAM VPS
FPM_MAX_CHILDREN=5
FPM_START_SERVERS=1
FPM_MIN_SPARE_SERVERS=1
FPM_MAX_SPARE_SERVERS=2
FPM_MAX_REQUESTS=5000

pm.max_children is the configuration value that controls the maximum number of server processes that will be started by FastCGI. 5 is usually enough, but it might be a good idea to set it to 10 or 15 for a high traffic site on a high-end vps

1
sed -i 's/^pm.max_children.*/pm.max_children = '${FPM_MAX_CHILDREN}'/' $php_fpm_conf

pm.start_servers is the number of servers to be initially started by FastCGI. 1 is nearly always enough. Try 2 or 3 for higher traffic sites. Make sure that is it less that pm.max_children.

1
sed -i 's/^pm.start_servers.*/pm.start_servers = '${FPM_START_SERVERS}'/' $php_fpm_conf

pm.min_spare_servers is the minimum number of server processes FastCGI will have ready to handle additional load. Each server process is respawned when it hits pm.max_requests. It is here that this value becomes useful. It ensures that the site will not go offline for even a small amount of time, and will remain performant even under load. 1 is enough for small sites (< 5000 views / minute). For larger sites, try 5 or 6.

1
sed -i 's/^pm.min_spare_servers.*/pm.min_spare_servers = '${FPM_MIN_SPARE_SERVERS}'/' $php_fpm_conf

pm.max_spare_servers exists for only one reason: to prevent FastCGI from consuming too many resources in the event of high load. This is the number of maximum server processes that will be kept ready by FastCGI. Make sure it is at least half of pm.max_children and greater than pm.min_spare_servers. 2 is OK for a small site. Try 9 or 10 for a large site

1
sed -i 's/^pm.max_spare_servers.*/pm.max_spare_servers = '${FPM_MAX_SPARE_SERVERS}'/' $php_fpm_conf

pm.max_requests controls the maximum number of hits a FastCGI process is allowed to serve before it is killed and respawned. This value is there in place to deal with the fact that over time, these processes leak memory. Periodically killing and respawning them allows us to keep the site snappy. 5000 is a pretty good value to begin with, give or take a couple hundreds. If pm.min_spare_servers is < 5, and this value is < 5000, the site will become more sluggish. Never set it to a 3 digit or less number. Always ensure that it is >= 5000

1
sed -i 's/\;pm.max_requests.*/pm.max_requests = '${FPM_MAX_REQUESTS}'/' $php_fpm_conf

Change to socket connection for better performance

1
sed -i 's/^listen =.*/listen = \/var\/run\/php5-fpm-www-data.sock/' $php_fpm_co

Now, configure the php.ini file.

1
2
3
4
5
6
7
php_ini_dir="/etc/php5/fpm/php.ini"
# Configure these values to your liking. Optimized for 512MB RAM VPS
PHP_MEMORY_LIMIT=192M
PHP_MAX_EXECUTION_TIME=120
PHP_MAX_INPUT_TIME=300
PHP_POST_MAX_SIZE=50M
PHP_UPLOAD_MAX_FILESIZE=50

max_execution_time is the maximum amount of time (in seconds) that a script is allowed to execute for, before it is killed. 120 is a big enough number to do most heavy calculations, but this number may be increased if many computations are required to be performed or heavy downloading needs to be done.

1
sed -i 's/^max_execution_time.*/max_execution_time = '${PHP_MAX_EXECUTION_TIME}'/' $php_ini_dir

memory_limit is the maximum amount of memory (in MB) that a PHP script is allowed to take up. 192 is more than enough for most jobs, but you may consider raising it if you want to do heavy computations.

1
sed -i 's/^memory_limit.*/memory_limit = '${PHP_MEMORY_LIMIT}'/' $php_ini_dir

This is the maximum amount of time (in seconds) for which a client can upload data to the server through a keep-alive connection. 300 seconds (or 5 minutes) is usually enough. You may want to set it to something like 6300 (2 hours) if you plan on uploading huge files.

1
sed -i 's/^max_input_time.*/max_input_time = '${PHP_MAX_INPUT_TIME}'/' $php_ini_dir

post_max_size is the maximum size (in MB) of the data that can be sent to the server through a POST request. 50 MB is usually more than enough for most needs, but feel free to change to will.

1
sed -i 's/^post_max_size.*/post_max_size = '${PHP_POST_MAX_SIZE}'/' $php_ini_dir

upload_max_filesize is the maximum size of an individual file (in MB) that can be uploaded. Change at will.

1
sed -i 's/^upload_max_filesize.*/upload_max_filesize = '${PHP_UPLOAD_MAX_FILESIZE}'/' $php_ini_dir

Don’t expose PHP. More of a security fix than memory optimization

1
sed -i 's/^expose_php.*/expose_php = Off/' $php_ini_dir

Disable potentially dangerous PHP functions that may allow attackers to execute arbitary code on your machine.

1
sed -i 's/^disable_functions.*/disable_functions = exec,system,passthru,shell_exec,escapeshellarg,escapeshellcmd,proc_close,proc_open,dl,popen,show_source/' $php_ini_dir

Now we restart all the services

1
2
3
service php5-fpm start
service php5-fpm restart
apache2ctl graceful

Step 8 - Optimize MariaDB

MariaDB configuration is quite easy to optimize. Just dropping InnoDB can cause a huge performance gain.

1
nano /etc/mysql/my.cnf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
[mysqld]
default-storage-engine = myisam
key_buffer = 1M
query_cache_size = 1M
query_cache_limit = 128k
max_connections=25
thread_cache=1
skip-innodb
query_cache_min_res_unit=0
tmp_table_size = 1M
max_heap_table_size = 1M
table_cache=256
concurrent_insert=2 
max_allowed_packet = 1M
sort_buffer_size = 64K
read_buffer_size = 256K
read_rnd_buffer_size = 256K
net_buffer_length = 2K
thread_stack = 64K
END

Now we restart the service

1
service mysql restart

Step 9 - Reboot

Reboot the server to get better performance

1
shutdown -r now

Step 11 - Set up a non-root sudo-enabled user

We will use this user’s home directory to contain our vhosts. Add the User

1
2
adduser demo # Replace demo with your name
# Like `adduser ishan`

Grant him priviledges

1
gpasswd -a demo sudo

Step 12 - Add a virtualhost

Replace “example.com” with your domain Replace “demo” with your user Create an FPM user

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
DOMAIN_OWNER=demo
cp /etc/php5/fpm/pool.d/{www.conf,$DOMAIN_OWNER.conf}
# Quickly edit the FPM file
# Create a new FPM pool with same name as user
sed -i 's/^\[www\]$/\['${DOMAIN_OWNER}'\]/' /etc/php5/fpm/pool.d/$DOMAIN_OWNER.conf
# set it to listen on a unix socket
sed -i 's/^listen =.*/listen = \/var\/run\/php5-fpm-'${DOMAIN_OWNER}'.sock/' /etc/php5/fpm/pool.d/$DOMAIN_OWNER.conf
# set permissions
sed -i 's/^user = www-data$/user = '${DOMAIN_OWNER}'/' /etc/php5/fpm/pool.d/$DOMAIN_OWNER.conf
sed -i 's/^group = www-data$/group = '${DOMAIN_OWNER}'/' /etc/php5/fpm/pool.d/$DOMAIN_OWNER.conf
sed -i 's/^;listen.mode =.*/listen.mode = 0660/' /etc/php5/fpm/pool.d/$DOMAIN_OWNER.conf
sed -i 's/^;listen.owner =.*/listen.owner = www-data/' /etc/php5/fpm/pool.d/$DOMAIN_OWNER.conf
sed -i 's/^;listen.group =.*/listen.group = www-data/' /etc/php5/fpm/pool.d/$DOMAIN_OWNER.conf

Restart FPM

1
service php5-fpm restart

Create a public_html for your domain

1
mkdir -p /home/demo/example_com/public_html

Create the log files:

1
touch /home/demo/test_digitalocean_tk/logs/{access.log,error.log}

Set permissions

1
2
3
4
5
chown demo:demo /home/demo/
chown -R demo:demo /home/demo/example_com
# Allow execute permissions to group and other so that the webserver can serve files
chmod 711 /home/demo/
chmod 711 /home/demo/example_com

Create the VirtualHost file

1
nano /etc/apache2/sites-available/example_com
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<VirtualHost *:80>

    ServerName example.com
    ServerAlias www.example.com
    ServerAdmin admin@example.com
    DocumentRoot /home/demo/example_com/public_html/
    ErrorLog /home/demo/example_com/logs/error.log
    CustomLog /home/demo/example_com/logs/access.log combined

    FastCGIExternalServer /home/demo/example_com/php5-fpm -pass-header Authorization -idle-timeout 120 -socket /var/run/php5-fpm-demo.sock
    Alias /php5-fcgi /home/demo/example_com

</VirtualHost>

Enable the virtualhost

1
ln -s /etc/apache2/sites-available/example_com /etc/apache2/sites-available/example_com

Test the virtualhost

1
nano /home/demo/example_com/public_html/info.php
1
2
3
<?php
phpinfo();
?>

navigate to your domain in the browser and see that it’s working correctly.

Conclusion

Now that you know how to install and optimize a LAMP stack, get cracking and create a great site that will be big enough to crash this setup!

Read Next

I’m running an experiment for better content recommendations. These are the 3 posts that are most likely to be interesting for you:

  • Install Postgres into XAMPP on Windows
    After mastering LAMP stack optimizations for Linux, you might be intrigued by the versatility of integrating Postgres with XAMPP on Windows, expanding your development environment capabilities.

  • 3 ways to render inline lists in PHP
    After mastering the art of optimizing your LAMP stack for peak performance, you’ll appreciate the finesse of using PHP to seamlessly render inline lists, a skill that complements your server optimization efforts by enhancing your web applications’ efficiency and user experience.

  • Accidentally deleted your .bashrc?
    After mastering the art of optimizing your LAMP stack, learning how to recover and customize your bash environment can be your next step to becoming a Linux server wizard.

All content is licensed under CC BY-NC-SA 4.0. Copying is an act of love - please copy!
More cool websites: Prev | Hotline Webring | Next
Built with Hugo
Theme Stack designed by Jimmy