Compiling nginx with LibreSSL for HTTP/2 support

Get nginx with HTTP/2 protocol support up and running today. You’re not afraid of compiling a bit of source-code on your own, are you?

nginx + LibreSSL = HTTP/2

TL;DR: Get the build script for compiling nginx with LibreSSL on github.com.

Why HTTP/2 can be useful?

The focus of the protocol is on performance; specifically, end-user perceived latency, network and server resource usage. One major goal is to allow the use of a single connection from browsers to a Web site.

At a high level, HTTP/2:

You can read more about the benefits on the HTTP/2 frequently asked questions wiki page.

Nick Shadrin, Technical Solutions Architect at nginx inc., gave a nice presentation about the current state of HTTP/2 support in nginx.

In short: it is worth the trouble.

Why choose LibreSSL?

In order to make HTTP/2 work with browsers, you need encryption. Although encryption is no requirement in the specification per se, all browser vendors decided it was necessary to enforce. So before you venture forth, make sure you have a working HTTPS setup in place. Thanks to the efforts of Lets Encrypt you can get certificates for free now.

Secondly, most current Linux distributions do not ship an OpenSSL version supporting the Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension which is neccesary for protocol negotiation. Unless a recent version is provided by your distribution of choice, compiling by hand is necessary anyway.

Lastly, OpenSSL updates have become quiet frequent, as more and more security vulnerabilites are discovered that are so severe, they are given names. The DROWN-Attack is the most current one, but I am certain more will follow.

In contrast, LibreSSL seems to have a slightly better track-record regarding security vulneribilities. It wasn’t even affected by some of the reported issues.

If you want to support HTTP/2 with nginx right now, you’ll most likely have to build it on your own. Why not choose a library that looks a bit more stable and secure.

Show me the code

The following shell-script is tailored to RHEL/CentOS 7. If you’re using another Linux distribution, you’ll have to spot the few moments where yum is used and replace those commands with what is appropiate.

#!/usr/bin/env bash

# Names of latest versions of each package
export VERSION_PCRE=pcre-8.39
export VERSION_ZLIB=zlib-1.2.8
export VERSION_LIBRESSL=libressl-2.4.4
export VERSION_NGINX=nginx-1.11.8

# URLs to the source directories
export SOURCE_LIBRESSL=http://ftp.openbsd.org/pub/OpenBSD/LibreSSL/
export SOURCE_PCRE=http://ftp.csx.cam.ac.uk/pub/software/programming/pcre/
export SOURCE_NGINX=http://nginx.org/download/
export SOURCE_ZLIB=http://zlib.net/

# Path to local build
export BUILD_DIR=/tmp/nginx-static-libressl/build
# Path for libressl
export STATICLIBSSL="${BUILD_DIR}/${VERSION_LIBRESSL}"

function setup() {
    # create and clean build directory
    mkdir -p ${BUILD_DIR}
    rm -Rf ${BUILD_DIR}/*
    # install build environment tools
    yum -y groupinstall "Development Tools"
}

function download_sources() {
    # todo: verify checksum / integrity of downloads!
    echo "Download sources"

    pushd ${BUILD_DIR}

    curl -sSLO "${SOURCE_ZLIB}${VERSION_ZLIB}.tar.gz"
    curl -sSLO "${SOURCE_PCRE}${VERSION_PCRE}.tar.gz"
    curl -sSLO "${SOURCE_LIBRESSL}${VERSION_LIBRESSL}.tar.gz"
    curl -sSLO "${SOURCE_NGINX}${VERSION_NGINX}.tar.gz"

    popd
}

function extract_sources() {
    echo "Extracting sources"

    pushd ${BUILD_DIR}

    tar -xf "${VERSION_PCRE}.tar.gz"
    tar -xf "${VERSION_LIBRESSL}.tar.gz"
    tar -xf "${VERSION_NGINX}.tar.gz"
    tar -xf "${VERSION_ZLIB}.tar.gz"

    popd
}

function compile_nginx() {
    echo "Configure & Build nginx"

    pushd "${BUILD_DIR}/${VERSION_NGINX}"

    make clean

    ./configure \
        --prefix=/usr/share/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 \
        --http-client-body-temp-path=/var/lib/nginx/tmp/client_body \
        --http-proxy-temp-path=/var/lib/nginx/tmp/proxy \
        --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi \
        --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi \
        --http-scgi-temp-path=/var/lib/nginx/tmp/scgi \
        --pid-path=/run/nginx.pid \
        --lock-path=/run/lock/subsys/nginx \
        --user=nginx \
        --group=nginx \
        --with-threads \
        --with-file-aio \
        --with-ipv6 \
        --with-http_ssl_module \
        --with-http_v2_module \
        --with-http_realip_module \
        --with-http_gunzip_module \
        --with-http_gzip_static_module \
        --with-http_slice_module \
        --with-http_stub_status_module \
        --without-select_module \
        --without-poll_module \
        --without-mail_pop3_module \
        --without-mail_imap_module \
        --without-mail_smtp_module \
        --with-stream \
        --with-stream_ssl_module \
        --with-pcre="${BUILD_DIR}/${VERSION_PCRE}" \
        --with-pcre-jit \
        --with-openssl="${STATICLIBSSL}" \
        --with-zlib="${BUILD_DIR}/${VERSION_ZLIB}" \
        --with-cc-opt="-fPIC -pie -O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic" \
        --with-ld-opt="-Wl,-z,now -lrt"

    make -j1

    popd
}

echo "Building ${VERSION_NGINX} with static ${VERSION_LIBRESSL}, ${VERSION_PCRE}, and ${VERSION_ZLIB} ..."

setup && download_sources && extract_sources && compile_nginx

retval=$?
echo ""
if [ $retval -eq 0 ]; then
    echo "Your nginx binary is located at ${BUILD_DIR}/${VERSION_NGINX}/objs/nginx."
    echo "Listing dynamically linked libraries ..."
    ldd ${BUILD_DIR}/${VERSION_NGINX}/objs/nginx
    echo ""
    ${BUILD_DIR}/${VERSION_NGINX}/objs/nginx -V
else
    echo "Ooops, build failed. Check output!"
fi

Configuration paths, compiler, and linker settings are loosely based on the regular nginx RPM-package. Nginx modules that require additional libraries are excluded. Email-related modules are disabled on purpose, as well. Adapt the script to your liking, it is just a blueprint.

Update 2016-03-24: Since the LibreSSL 2.3.3 release, the developers have added the install_sw target to the build script. Patching nginx sources is no longer needed.

It is absolutely necessary to patch the nginx source-files in the patchNginxOpenSSLMakefile() function.

Yes, now [since v1.9.12] nginx uses install_sw target to install OpenSSL when --with-openssl=path is used, and this breaks things when you try to use LibreSSL instead of OpenSSL. […], it’s only expected to be able to compile OpenSSL.

Maxim Dounin

Alternatively, you might want to patch LibreSSL to provide a install_sw target.

In general, I would recommend running the build in a chroot, Docker container, or VM; all that matters is the nginx binary created at the end of the process.

Also, keep in mind that the build will contain all debug symbols (meaning the file is almost 20MB big), so you may want to skip the --debug flag in configure, and call strip on the executable afterwards for good measure.

Modern browsers benefit most

Virtually all modern, a.k.a. “ever-green”, browsers will benefit from having to open only one connection to a given website. At the very least, nginx can now handle even more concurrent requests than ever before.

On the other hand, “old-ish” clients must fallback to standard HTTP/1.1 with all its associated drawbacks. Running the ngx-spdy module in parallel doesn’t look like a good idea, either.

Reconsider, before you take leave of the domain-sharding pattern pattern, if most of the website visitors come from mobile or less than current clients.

Addendum: My sincere apologies to everyone who visited this page on March 7, 2016. I accidently had published the post in a very rough draft state, which was barely readable and missed many important points.