ALPN negotiation choosing http/1.1 over h2

I've created a DigitalOcean droplet with "LAMP 18.04" preset and did all updates. Configured a new virtual host and used certbot to configure HTTPS. So far everything was working fine. Then I tried to enable HTTP2:

  • a2enmod http2
  • Added Protocols h2 http/1.1 to the HTTPS virtual host

However, browsers still use HTTP/1.1 when connecting to the site (with caching disabled). When running curl --http2 -v https://example.com (domain obfuscated) the following is printed:

* Connected to example.com (1.1.1.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
...
* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
* ALPN, server accepted to use http/1.1

... And in headers:
< Upgrade: h2
< Connection: Upgrade

Furthermore, https://tools.keycdn.com/http2-test shows the following:

screenshot from HTTP2 testing tool

My question is - why is http/1.1 being chosen? Also, why does the tool think that HTTP2/ALPN is disabled? Here is the full VHost config:

<IfModule mod_ssl.c>
<VirtualHost *:443>
        ServerName example.com
        DocumentRoot /var/www/example/www
        Protocols h2 http/1.1

        ErrorLog ${APACHE_LOG_DIR}/example/error.log
        CustomLog ${APACHE_LOG_DIR}/example/access.log combined

        <IfModule mod_dir.c>
            DirectoryIndex index.php index.pl index.cgi index.html index.xhtml index.htm
        </IfModule>

        <FilesMatch "^index\.html$">
            Include no-cache.conf
        </FilesMatch>

Include /etc/letsencrypt/options-ssl-apache.conf
ServerAlias www.example.com
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
</VirtualHost>
</IfModule>

Most distributions still default to the prefork MPM. (Unless you really have to use mod_php with really crappy ancient code there is no reason to do so anymore in this day and age.)

HTTP/2 support in Apache in combination with the prefork MPM comes with so many limitations that in practice it will deliver none of the benefits of HTTP/2. That is probably why with testing the headers indicate HTTP/2 and ALPN, offering h2 support but further testing shows a failure.

Switch to a different MPM and you should get HTTP/2 to actually work.