Compiling nginx with OpenSSL 1.0.2 to maintain HTTP/2 support

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:

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.


  1. 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.

9 thoughts on “Compiling nginx with OpenSSL 1.0.2 to maintain HTTP/2 support”

  1. 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.

  2. 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

    1. 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 ran make; I think that the nginx make process handles that.

      1. 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.

  3. 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.

Comments are closed.