Chrome 51 disabled support for NPN, or Next Protocol Negotiation, the mechanism that millions of nginx servers needed to establish HTTP/2 connections with Chrome users. For anyone running nginx compiled against OpenSSL 1.0.1, Chrome 51 users are still connecting over SSL, but only via the legacy HTTP/1.1 specification, which lacks the performance benefits HTTP/2 imparts.
Both the nginx project, and Mattias Geniar, provide lengthier explanations of what changed in Chrome 51:
- https://www.nginx.com/blog/supporting-http2-google-chrome-users/
- https://ma.ttias.be/day-google-chrome-disables-http2-nearly-everyone-may-31st-2016/
For those wondering how to restore HTTP/2 support for Chrome 51 users, there is but one answer: switch nginx to OpenSSL 1.0.2. While OpenSSL 1.0.1 is only receiving security updates (and will stop receiving any updates after December 31, 2016), OpenSSL 1.0.2 is actively maintained and receiving new features, including the successor to NPN, which nginx supports: ALPN, or Application-layer Protocol Negotiation.
Switching to OpenSSL 1.0.2 isn’t as simple as running your distribution’s updater, though. As the aforementioned articles explain, distributions only apply security updates to the OpenSSL versions they bundle, meaning the only major distribution that currently ships with OpenSSL 1.0.2 is Ubuntu’s 16.04 LTS. Thankfully for everyone else, the nginx project makes it very easy to compile the binary with a specific version of OpenSSL, providing a simple solution to restore HTTP/2 support for all capable browsers.
OpenSSL source
To start, we’ll need a local copy of the OpenSSL 1.0.2 source. By building from sources, rather than requiring the server to run OpenSSL 1.0.2, nginx allows me to restore HTTP/2 without interfering with the myriad applications on my VPS that still expect OpenSSL 1.0.1.
The OpenSSL project uses GitHub and properly tags releases, so we’ll pull the latest release of OpenSSL 1.0.2 from there. If, somehow, you don’t have git installed, check out http://ethitter.com/2016/02/building-git-2-x-debian/. Then, visit https://github.com/openssl/openssl/releases and copy the commit hash for the latest version of OpenSSL 1.0.2—h
was the latest release when I wrote this, having the hash 5dd94f1847c744929a3bd24819f1c99644bb18c7
.
cd /usr/local/src git clone https://github.com/openssl/openssl.git cd openssl git checkout 5dd94f1847c744929a3bd24819f1c99644bb18c7
After completing the above, you’ll have the source for OpenSSL 1.0.2h
in /usr/local/src/openssl
. OpenSSL requires no further attention for the purposes of this post; building nginx will handle the rest.
Compiling nginx
Prior to Chrome 51’s removal of NPN support, I used the official build that nginx provides for my distribution, Debian Wheezy. To prepare for a custom build, I first needed to satisfy some of the build process’s dependencies. Depending on how many things you’ve compiled from sources, you may have additional dependencies, such as build tools or development libraries. I only needed the libraries nginx requires to support PCRE.
apt-get install libpcre3 libpcre3-dev
Next, we need a copy of the nginx source we’ll build from. nginx uses Mercurial, which I’ve no experience with, so I downloaded the archive from http://nginx.org/en/download.html instead1:
cd /usr/local/src curl -L --progress http://nginx.org/download/nginx-1.11.1.tar.gz | tar xz
Now, alongside the OpenSSL source from the last section, we have the nginx source needed to build our version that supports HTTP/2 for all users.
As mentioned, I previously used the pre-built package that the nginx project provides for Debian Wheezy: http://nginx.org/en/linux_packages.html. The project is helpful enough to supply the configuration arguments for its build, if you don’t have nginx installed currently.
--prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-http_auth_request_module --with-threads --with-stream --with-stream_ssl_module --with-http_slice_module --with-mail --with-mail_ssl_module --with-file-aio --with-http_v2_module --with-ipv6
If you’re running nginx already, use the command nginx -V
to display your build’s configuration arguments. This ensures that your replacement includes all of the existing functionality your nginx configurations expect.
Before starting the build process, we add one additional flag to our configuration arguments; this tells nginx to use the OpenSSL 1.0.2 source we downloaded in the last section, rather than compiling against the OpenSSL library provided by the distribution:
--with-openssl=/usr/local/src/openssl
For my purposes, I’m using the following configuration:
./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --with-openssl=/usr/local/src/openssl --user=www-data --group=www-data --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-http_auth_request_module --with-threads --with-stream --with-stream_ssl_module --with-http_slice_module --with-mail --with-mail_ssl_module --with-file-aio --with-http_v2_module --with-ipv6
After running the above in /usr/local/src/nginx-1.11.1
, we’re ready to build the binary and replace our existing nginx with it:
make apt-get remove nginx make install mkdir /var/cache/nginx && chown nginx:root /var/cache/nginx
If each of the above commands completes successfully, running nginx -V
a second time will reflect the settings from your custom build, including its use of OpenSSL 1.0.2h. An example from one of my VPS:
nginx version: nginx/1.11.1
built by gcc 4.7.2 (Debian 4.7.2-5)
built with OpenSSL 1.0.2h 3 May 2016
TLS SNI support enabled
configure arguments: …
Your previous nginx configurations will persist even though the pre-built package was uninstalled (unless you added the --purge
flag to the apt command), so you should now be able to start nginx using the new binary.
Testing HTTP/2 support
To confirm that the new binary was compiled correctly and supports ALPN, connect to your site in Chrome version 51 or later. Then, visit chrome://net-internals/#http2 and check that the “Protocol Negotiated” column shows h2 next to your domain.
For a browser-agnostic approach, the 2.7 release of testssl.sh will show if ALPN is supported–when testssl.sh is used with OpenSSL 1.0.2. Support for NPN is also reported.
- I didn’t realize until later that nginx provides a read-only mirror on GitHub, though since WordPress does the same, I should have. I may update this section after the next nginx release, which will give me a chance to build from the git mirror for the first time. ↩
Thanks, Erick! That was very useful for upgrading my nginx. I’d like to re-emphasize using `nginx -V`, that made it quite easy to 1:1 replace my current version. The only minor annoyance was that in most cases (for me in Debian wheezy) I had to get updated modules from Github.
There are also a bunch of testing websites that will tell you if HTTP2 works on your webserver.
Hey Erick,
Thanks a lot for this easy-to-follow tutorial. I really like texts which speak out to the general public!
Much appreciated.
I published a new post that describes how to add support for Google’s Brotli compression to nginx: http://ethitter.com/2016/12/adding-brotli-support-to-nginx/.
Thanks, exactly what I was looking for – Google Brotli included.
One (little) change I made was replacing
$ mkdir /var/cache/nginx && chmod nginx:root /var/cache/nginx
by
$ mkdir -p /var/cache/nginx && chown -cR nginx:root /var/cache/nginx
Good job Erick …
I’m starting with a new and want to know how to make a dynamic module openssl.
Excellent question. I’ll need to confirm when I next upgrade nginx or OpenSSL, but I believe that after checking out the correct OpenSSL release to
/usr/local/src/openssl
, I ran./config
in that directory. I don’t believe that I ranmake
; I think that the nginx make process handles that.I confirmed today, while upgrading to OpenSSL 1.1.0f, that it’s only necessary to run
/.config
against the latest OpenSSL checkout. The nginx build process handles the rest.Hi,
Thanks for your helpful article “Compiling nginx with OpenSSL 1.0.2 to maintain HTTP/2 support”
I believe you meant “chown” instead of “chmod” at the last step.
Indeed I did. Thanks for catching that!