Recently, we took a look at the Caddy Web server. Today, we’re going to back things up a little bit and look at the A from the classic LAMP stack: the Apache Web server.
Apache has a bad reputation for being old, crusty, and low-performance—but this idea mostly stems from the persistence of ancient guides that still show users how to set it up in extremely antiquated ways. In this guide, we’re going to set up an Ubuntu 20.04 droplet at Digital Ocean with an Apache Web server set up properly and capable of handling serious levels of traffic.
After spinning up a new $5/mo VM (Digital Ocean calls them “droplets”), the first thing we’ll do is what anyone should do with any brand new Linux server. We check for and then install upgrades, and—since one of them was a new Linux kernel version—reboot the server.
root@apache:~# apt update root@apache:~# apt dist-upgrade root@apache:~# shutdown -r now
With that bit of minor housekeeping out of the way, it’s time to install Apache itself and the PHP language that most Web applications require.
root@apache:~# apt install apache2 php-fpm
Friends don’t let friends use mod_php inappropriately
I want to make incredibly clear what we have not installed—we did not, and will not, install the
mod_php Apache module.
root@apache:~# apt policy libapache2-mod-php libapache2-mod-php: Installed: (none) Candidate: 2:7.4+75 Version table: 2:7.4+75 500 500 http://mirrors.digitalocean.com/ubuntu focal/main amd64 Packages
mod_php module was, once upon a time, the favored way to integrate PHP support into your Web server. It by and large replaced the older CGI (Common Gateway Interface) method, which passed the files with specified extensions off to a different application to process—the most common in those days being Perl.
Mod_php does things differently—instead of a separate PHP executable handling PHP code, the PHP language is embedded directly into the Apache process itself. This is an extremely efficient way to process PHP code—but it absolutely sucks for a server expected to handle non-PHP content, because every single Apache process must bring an entire PHP execution environment with it, sharply limiting the number of total Apache processes available due to memory bloat.
mod_php also means requiring Apache to run with the elderly
prefork MPM (Multi Process Module), which doesn’t scale to as many available worker processes as the modern default MPM,
event. The reason
prefork—are still around at all is that they are very good for a pure application service, 100% PHP workload, with all CSS, static HTML, images, and so forth offloaded to a different server or servers.
Php-fpm is the right choice for a multi-purpose Web server
Instead, we installed the
php-fm, the PHP FastCGI Process Manager. In this model, Apache doesn’t bring PHP handling capabilities into the Apache processes themselves—instead, Apache hands its code execution needs off to a pool of dedicated PHP workers, which in turn pass results back to Apache.
Offloading PHP execution duties to a set of dedicated PHP worker threads enables Apache to use its more modern and better scaling
event MPM handler. It also means each individual Apache thread can be spun up without the bulk of a PHP execution environment, drastically reducing the necessary amount of RAM for each thread.
The front page of my personal blog entails 31 separate HTTPS requests. Ten of those are to other domains—fonts.googleapis.com, fonts.gstatic.com, and my own Matomo instance. Index.php itself is another, and the remaining twenty are static files delivered from the same server.
RAM is easily the most precious resource on that VM—and since we now know that I’m serving static files at about a 20:1 ratio to dynamic pages, I obviously shouldn’t be wasting RAM on a full PHP environment for every Apache worker process!
Enabling php-fpm and installing remaining support packages
Most real-world Web applications will want a bunch of additional PHP modules installed. If we wanted to install WordPress, for instance, we’d want the following laundry list of PHP extensions:
root@apache:~# apt install php-fpm php-common php-mbstring php-xmlrpc php-soap php-gd php-mysql php-xml php-intl php-mysql php-cli php-ldap php-zip php-curl
Whew. If you’re not familiar with the use of the backslash there, it’s a way of forcing a line break in the terminal without affecting code execution: so that’s really all just one big line, installing all of the additional PHP extensions WordPress will want.
With those installed, we need to enable
php-fpm itself, with the following commands:
root@apache:~# a2enmod proxy_fcgi root@apache:~# a2enconf php7.4-fpm.conf root@apache:~# systemctl restart apache2
That’s it—we’ve now created our full, WordPress-ready webserver environment. The next step is creating a MySQL database for WordPress, which looks like this:
root@apache:~# mysql -u debian-sys-maint -p mysql> create database wordpress; mysql> create user 'wordpress'@'localhost' identified by 'supersecretpassword'; mysql> grant all on wordpress.* to 'wordpress'@'localhost'; mysql> quit;
Now we’re ready to create a new
vhost—virtual host—to contain the new WordPress site. We could just use the default vhost config, but we’re not going to—we’re going to do this like professionals and be ready to manage a multi-site environment.
Apache site, module, and configuration configuration (that’s not a typo!)
The thing I enjoy the most about using Apache rather than competing Web servers is the highly segmented approach it uses for configuration management. In the olden days—which I remember none too fondly—a server would have a single, monolithic
httpd.conf file that could easily be thousands of lines long and contain global configs for the server as well as all individual configs for every site on the server. Yuck!
Happily, Apache eventually introduced the
Include directive, which allowed the main Apache config file to link in other config files—and, best of all, directories which could be expected to be full of config files. This allowed site admins to create an individual, short config file for each site, and—just by dumping it into the appropriate directory—have that site’s configurations automatically added to the existing server config after a
systemctl apache2 reload (or, on non-systemd machines,
The fine folks at Debian, in turn, took that concept and ran with it. When you install Apache on a modern Debian-derived system such as Ubuntu, you get the following directories automatically created:
/etc/apache2 /etc/apache2/sites-available /etc/apache2/sites-enabled /etc/apache2/mods-available /etc/apache2/mods-enabled /etc/apache2/conf-available /etc/apache2/conf-enabled
So let’s say you want to add a module—like
php-fpm itself—to Apache. You don’t need to monkey around with the global config file in
/etc/apache2/apache2.conf, because the
php-fpm package just drops its configuration and load files into
/etc/apache2/mods-available. They haven’t actually taken effect yet because they’re only in
mods-enabled—but remember when we ran the command
a2enmod proxy_fcgi in the last section?
root@apache:~# a2enmod proxy_fcgi Considering dependency proxy for proxy_fcgi: Enabling module proxy. Enabling module proxy_fcgi. To activate the new configuration, you need to run: systemctl restart apache2
What that command actually did was
symlink the config file
/etc/apache2/mods-enabled/proxy_fcgi.load. And when we next restart Apache as it’s asking us to, Apache will
Include all the files in
mods_enabled—including our new friend,
proxy_fcgi.load—and we’ll therefore have the FastCGI proxy available.
If you remember, we did another command immediately after that one:
root@apache:~# a2enconf php7.4-fpm Enabling conf php7.4-fpm. To activate the new configuration, you need to run: systemctl reload apache2
That command symlinked
/etc/apache2/conf-enabled/php7.4-fpm.conf, and similarly, Apache will
Include everything it finds in
conf-enabledat each startup, so we’ve now got the following necessary configuration directives enabled:
root@apache:/etc/apache2/conf-available# cat php7.4-fpm.conf # Redirect to local php-fpm if mod_php is not available # Enable http authorization headers SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1 <filesmatch ".+.ph(ar|p|tml)$"=""> SetHandler "proxy:unix:/run/php/php7.4-fpm.sock|fcgi://localhost" <filesmatch ".+.phps$"=""> # Deny access to raw php sources by default # To re-enable it's recommended to enable access to the files # only in specific virtual host or directory Require all denied # Deny access to files without filename (e.g. '.php') <filesmatch "^.ph(ar|p|ps|tml)$"=""> Require all denied
Now, if you don’t see the beauty in that… I don’t know what to tell you. If you’re confused about exactly how Apache is handling PHP files when it encounters them, you have a single file where you can look to see those config stanzas, and only those config stanzas. You can view it without being confused and annoyed by hundreds or thousands of other lines of configs, and you can edit it if necessary without fearing accidentally screwing up those other hundreds or thousands of lines of configuration that you aren’t touching, since you’re only working in this single self-contained file.
This isn’t only for system-provided configuration stanzas, either—nothing’s stopping you from writing your own config stanzas for a particular purpose, dropping them in
a2enconfing them as desired. Want to know all the modules that are enabled?
ls /etc/apache2/mods_enabled. Want to see if more are available?
mods_available. The same thing goes for configs in
conf_available, and site (
vhost) configurations in
That makes my sysadmin heart sing, it really does.