Enable HTTP/2 with Apache+PHP on Ubuntu

I recently made the necessary adjustments to my Apache+PHP server to support the HTTP/2 protocol. This newer protocol comes with performance advantages and support is widespread by now. Depending on existing Apache server configuration, it is not always trivial to enable, due to specific requirements for HTTP/2 with Apache. This especially applies if you’re using PHP with the classic mod_php Apache module.

Here is a summary of the setup I used before, when my web server only supported HTTP/1.X:

  1. Ubuntu 20.04 LTS
  2. Apache 2.4 web server with the classic prefork multi processing module.
  3. mod_php for server side PHP script execution.
  4. Fully https-enabled site with certificate from Let’s Encrypt.
  5. WordPress for publishing.

Points 2 and 3 will require changes.

The Apache multi processing module (often referred to simply as an «MPM») must be switched to the generally recommended mpm_event. Since mod_php is not compatible with the multi threaded mpm_event, an alternative must be used for PHP script execution. The recommended approach is to setup PHP as a FastCGI service and have Apache proxy requests to this service for actual script execution. So the big difference here is that PHP code will execute outside of the Apache server processes, thereby decoupling Apache’s execution environment and request handling from PHP.

The steps in the following sections all apply to Ubuntu 20.04 and Debian in general (package versions may vary). There are some handy command line tools to configure the modular Apache server, which we will be using. Assuming nothing goes wrong, no significant downtime will occur when following these steps.

1. Setting up the PHP FastCGI service

Disable and remove the classic mod_php package, since it is no longer needed:

# Disable Apache mod_php:
a2dismod php7.4

# Optionally remove packages, no longer be needed:
apt autoremove libapache2-mod-php\*

Install the PHP FastCGI process manager package:

apt install php-fpm

The service should be automatically started. The package provides the following information after a successful installation:

NOTICE: Not enabling PHP 7.4 FPM by default.
NOTICE: To enable PHP 7.4 FPM in Apache2 do:
NOTICE: a2enmod proxy_fcgi setenvif
NOTICE: a2enconf php7.4-fpm
NOTICE: You are seeing this message because you have apache2 package installed.

So let’s just do what is recommended:

a2enmod proxy_fcgi setenvif
a2enconf php7.4-fpm

The proxy_fcgi module is required so that Apache can proxy requests to FastCGI services. The second command enables proxy configuration specific to the service setup by the php7.4-fpm package.

If you have customizations to php.ini, you will have to copy those from the old mod_php configuration file to the separate php-fpm configuration. These are the relevant files:

# old mod_php config file no longer in use:

# new php-fpm config file:

Finally, you can test that PHP still works on your web site after the switch to php-fpm, before moving on:

apache2ctl configtest
systemctl restart apache2

2. Switch to event multi processing module in Apache

Disable mpm_prefork, then enable mpm_event:

a2dismod mpm_prefork
a2enmod mpm_event

If those commands succeed, then restart Apache and test that your site still works:

apache2ctl configtest
systemctl restart apache2

3. Enable the HTTP/2 protocol

a2enmod http2
systemctl restart apache2

Now you can open your browser developer tools and do a page load of your site. If using https, then your browser should negotiate HTTP/2 with the server, and you will see that logged as the protocol. All done.

If not using https however, then you cannot really take advantage of HTTP/2. In theory it is supported, but most browsers will only use it over TLS connections. The protocol is then referred to as “h2”, meaning HTTP/2 over secure transport.

Tuning mpm_event and php-fpm

You can tune the Apache event MPM by editing the following configuration file:


The PHP FastCGI process manager service can be tuned by editing:


Summary of setup after enabling HTTP/2

  1. Ubuntu 20.04 LTS
  2. Apache 2.4 web server with the event multi processing, http2 and proxy_fcgi modules enabled.
  3. PHP-fpm for server side PHP script execution.
  4. Fully https-enabled site with certificate from Let’s Encrypt.
  5. WordPress for publishing.



Securing web site with https and Let’s Encrypt

A padlock image

I have added support for secure communication using https/TLS to my personal web site, which runs on Linux home servers using Apache. After Let’s Encrypt appeared and started offering free security certificates, there was really no reason not to give it a spin. And there are many good reasons to support secure http communication, even for sites not dealing with sensitive or secret information:

  • Data integrity. Using https is an effective way to prevent tampering with the data from the server to the client. It is not unheard of for ISPs or other third parties to modify http traffic, for instance to inject ads in pages, and https is an effective counter measure.
  • Initiatives like Mozilla’s “deprecation of non-secure HTTP”, which aims to move the web to secure communication by default.
  • Giving visitors the choice to keep their web browsing private across the internet.
  • Allow secure site administration.

Skip directly to a section in this post:

  1. Acquiring a Let’s Encrypt certificate
  2. Configuring Apache on Ubuntu 14.04 to use Let’s Encrypt certificate
  3. WordPress and https
  4. Validating https protocol support
  5. Automatic certificate renewal
  6. Donations to Let’s Encrypt and the way forward

Acquiring a Let’s Encrypt certificate

Using this recipe as a guide, I decided to use the certbot tool, which automates the process of verifying ownership of domain and downloading a new certificate from Let’s Encrypt. It also provides automation of the certificate renewal process, which is necessary with Let’s Encrypt due to rather short expiry periods.

As preparation, basic SSL support in Apache should be enabled. This is accomplished on a standard Debian/Ubuntu Apache by doing as super user:

cd /etc/apache2/mods-enabled/
ln -s ../mods-available/ssl.* ../mods-available/socache_shmcb.load .

/usr/sbin/apachectl graceful

This links up the required modules and default configuration, then reloads Apache config. Since certbot requires that your Apache server is reachable from the internet on port 443, make sure any required port forwardings and/or firewall rules are adjusted in your router.

Note that there is a default virtualhost configuration for SSL in the file /etc/apache2/sites-available/default-ssl.conf. However, there is no need to enable this file, as all the required SSL options are added to the name based virtualhost for the site. If you decide to link default-ssl.conf into /etc/apache2/sites-enabled/, you should edit it to include /etc/letsencrypt/options-ssl-apache.conf, to disable vulnerable SSLv2/v3 protocols.

On to the certbot process, download and run as super user:

curl -sS -o /usr/local/bin/certbot-auto
chmod +x /usr/local/bin/certbot-auto

/usr/local/bin/certbot-auto --apache certonly

I opted for the conservative approach of not allowing certbot to do any permanent Apache configuration changes and only acquire a certificate, by using the certonly option. This will do the verification process (using your running Apache server) and save certificates and account details under /etc/letsencrypt/. Note that certbot may ask to install additional packages on the system the first time you run it.

Certbot successfully detected the name based virtual host definitions in the Apache configuration (including aliases) and allowed me to choose for which domain names to acquire a certifcate. After selecting the domains, the validation process starts and, if successful, a certificate for the domains will be downloaded and made available on the system. This was an impressively simple process.

Configuring Apache on Ubuntu 14.04 to use Let’s Encrypt certificate

Assuming basic SSL support is enabled in Apache, the server runs with default options which come from /etc/apache2/mods-available/ssl.conf and [optionally] /etc/apache2/sites-available/default-ssl.conf. To SSL-enable an existing name based virtual host defined for port 80, I simply copied the defintion to a separate virtual host for port 443. Then I added the following configuration to the new virtual host definition:

<VirtualHost *:443>

  # Letsencrypt SSL options
  Include /etc/letsencrypt/options-ssl-apache.conf
  # virtual host cert
  SSLCertificateFile      /etc/letsencrypt/live/
  SSLCertificateChainFile /etc/letsencrypt/live/
  SSLCertificateKeyFile   /etc/letsencrypt/live/


Note to include the Let’s Encrypt recommended SSL options (have a look at the contents first), then specify the certificate files. This applies to Apache version < 2.4.8. If you are running Apache from -backports on Trusty, or on a newer Ubuntu, then you’ll have version  >= 2.4.10, which needs a slightly different certificate setup:

# Letsencrypt SSL options
Include /etc/letsencrypt/options-ssl-apache.conf
# virtual host cert
SSLCertificateFile      /etc/letsencrypt/live/
SSLCertificateKeyFile   /etc/letsencrypt/live/

(The SSLCertificateChainFile directive is deprecated starting with Apache 2.4.8.)

This was the only setup required to get functional SSL for my site.

WordPress and https

My site runs WordPress where the primary site URL uses plain http. This is how I wanted SSL to work:

  1. Both https and http should work across the site.
  2. If site is accessed on https, then all [internal] links and included resources should use https as well. (Enter on https, stay on https.)
  3. Site administration is only available on https.

Number 1 was easy – this works out of the box if Apache is configured correctly. But I got mixed content warnings for various resources inluded in pages when accessing over https. When evaluating how to fix a certain annoyance in WordPress, I tend to look for the simplest solutions which do not require extra plugins first. So I fixed images and other things included in content to use relative URLs. But there were still theme specific resources included with plain http URLs, likely due to the site URL being plain http. Not wanting to spend very much time diagnosing such specific cases, I ended up installing the SSL Insecure Content Fixer plugin. I run this plugin in “simple” mode, which I assume does not have to post-process all content being served (which I really wanted to avoid). That was enough to get consistent https-links when accessing content over https, and so number 2 is considered solved.

Since WordPress has a built in config option to force admin over SSL, adding the following to wp-config.php resolved number 3 on my list of requirements:

/** Admin over SSL */
define('FORCE_SSL_ADMIN', true);

Validating https protocol support

Qualys SSL labs has what looks like a rather thorough online validator for SSL protocol support. I ran my site through this validator and got an A-rating, with no glaring deficiencies. To improve the rating even further, I could have used a stronger RSA key than the default of 2048 bits, but it is certainly good enough for now.

Automatic certificate renewal

Obviously I like to keep site maintenance cost as low as possible, and automating the renewal process of certificates is a must. Certbot makes this very easy as well. I simply created a script in /etc/cron.daily/certbot-renew with the following content:

exec /usr/local/bin/certbot-auto renew --no-self-upgrade --quiet

Make sure it is executable, and then automatic renewal should be all set.

Donations to Let’s Encrypt and the way forward

My first experience with Let’s Encrypt has been great. I’ve spent much longer writing this blog post than the time it took to get the site up and running properly with https support. Good things deserve appreciation, especially when they are given away for free. I made a small donation to Let’s Encrypt for their excellent service.

I will switch my site to use SSL by default given some time. The cost of providing secure http by default has been reduced to something so neglible, I have few reasons not to.