Включаем поддержку ALPN на Nginx

Тема сборки Nginx для поддержки ALPN избита и изъезжена уже неоднократно, казалось бы что тут нового можно рассказать ?

Как правило во всех статьях для включения ALPN на Nginx предлагают сразу же его пересобрать с нужной версией OpenSSL, но возможно есть другой путь ?

Давайте разберемся как же лучше поступить, обновить ОС, пересобрать Nginx из исходников, найти готовый пакет или использовать Docker ?

Я не буду повторяться и рассказывать что такое HTTP/2, про это есть неплохая статья на Habrahabr от Selectel.

Что нужно знать ?

А то, что в мае 2016 года Google выпустил Chrome 51, исключив поддержку SPDY и NPN в пользу HTTP/2 и ALPN. Отключение поддержки NPN в Google Chrome привело к тому, что сайты которые не поддерживают ALPN стали открываться по HTTP/1 вместо HTTP/2. На тот момент можно было перейти на другой браузер, где поддержка NPN сохранилась и все было бы хорошо. Однако к середине 2017 года все самые популярные поставщики браузеров, кроме Safari (версия 10), отказались от поддержки NPN, начиная со следующих версий:

  • Chrome 51
  • Edge 12
  • Firefox 53
  • Internet Explorer 11
  • Opera 38
  • Теперь все зависит от администраторов Web-серверов, это они должны обновить ПО Web-серверов до той версий, которые поддерживают ALPN и HTTP/2. Nginx поддерживают протокол HTTP/2 с сентября 2015 года (начиная с версий Nginx 1.9.5 и Nginx Plus R7).

    Более значительная проблема заключается в том, что операционная система, на которой работает Web-сервер, должна содержать версию OpenSSL, которая поддерживает ALPN. Для OpenSSL это версия 1.0.2 или новее.

    Nginx собирается со статической версией OpenSSL и это дает ему независимость от версии OpenSSL в операционной системе.

    В таблице ниже приводиться информация о версиях OpenSSL в разных дистрибутивах ОС Linux, а так же поддержка этими версиями ALPN и NPN по состоянию на сентябрь 2017 года.

    Операционная системаВерсия OpenSSLПоддержка ALPN/NPN
    CentOS/Oracle Linux/RHEL 6.5+, 7.0–7.31.0.1eNPN
    CentOS/Oracle Linux/RHEL 7.4+1.0.2kALPN и NPN
    Debian 7.01.0.1eNPN
    Debian 8.91.0.1tNPN
    Debian 9.21.1.0fALPN и NPN
    Ubuntu 12.04 LTS1.0.1NPN
    Ubuntu 14.04 LTS1.0.1fNPN
    Ubuntu 16.04 LTS1.0.2gALPN и NPN

    Что Вы можете сделать ?

    Вы можете проверить, какая версия OpenSSL используется в Вашем Nginx, запустив nginx -V:

    # nginx -V
    nginx version: nginx/1.13.7
    built by gcc 4.9.2 (Debian 4.9.2-10)
    built with OpenSSL 1.0.1t  3 May 2016
    TLS SNI support enabled
    

    У Вас есть три варианта, если вы хотите, чтобы посетители вашего сайта обращались к сайту через HTTP/2:

    1. Обновите свою операционную систему.

    Как показано в таблице выше, некоторые дистрибутивы ОС поставляются с OpenSSL, которые поддерживают ALPN. Другие поставщики, скорее всего, будут поддерживать ALPN в своих будущих выпусках с долгосрочной поддержкой.

    2. Перекомпилируйте Nginx из исходников и используйте для сборки OpenSSL 1.0.2 или новее.

    Nginx поддерживает OpenSSL 1.0.2 и более поздние версии, поэтому Вы можете перекомпилировать Nginx с опцией —with-openssl.

    Однако разработчики Nginx не рекомендуют такой подход, т.к. в этом случае Вы берете на себя бремя мониторинга OpenSSL на предмет возможных уязвимостей в нем и такую сборку Nginx придется перекомпилировать каждый раз, когда выдается важное обновление для OpenSSL. Вы можете установить Nginx из репозитария Вашей ОС или из официального репозитария Nginx, но тут кроется несколько подводных камней, к примеру для Debian 8 (Jessie) в официальный Nginx собирается до сих пор с OpenSSL 1.0.1t, что не позволяет использовать ALPN. Для Debian 9 (Stretch) ситуация получше, там Nginx собирается с OpenSSL 1.1.0f, что позволяет нам использовать ALPN.

    3. Запускайте Nginx в контейнере Docker.

    В некоторых случаях Вы можете поместить свой Nginx или Nginx Plus в контейнер Docker, основанный на ОС, которая содержит OpenSSL 1.0.2 или новее. Это не потребует полного обновления ОС и является разумным ходом, только если Вы уже используете Docker в своем окружении. Если же Docker у Вас не используется, то такой вариант будет слишком сложным и повлечет дополнительных затрат времени на поддержание инфраструктуры Docker-контейнеров.

    Какой вариант следует выбрать и в каких случаях ?

    1. Вариант с обновлением ОС.
    К сожалению обновление ОС не всегда возможно по ряду причин, к примеру с Debian 8 до Debian 9 целесообразно проводить обновление если у Вас Nginx используется в качестве балансировщика нагрузки или прокси. Если на Debian 8 у Вас используется PHP-FPM 5.6, то Вы сталкиваетесь с проблемой того, что в официальных репозитариях Debian 9 уже нет PHP-FPM 5.6, а есть только PHP-FPM 7.0 и Вам придется использовать сторонние репозитарии для установки PHP-FPM 5.6 на Debian 9.
    Если же приград для обновления ОС на новую нет, то я рекомендую выполнить переход на новую версию ОС.

    2. Вариант с перекомпиляции Nginx из исходников с нужными опциями.

    Наверно самый популярный, т.к. это сделать довольно быстро и в случае с Nginx достаточно безболезненно.
    Но и тут есть подводные камни в виде пересборки Nginx каждый раз при выходе новой версии Nginx или новой версии OpenSSL.

    Решение 1: Использование репозитария Debian Backports для установки Nginx с поддержкой ALPN.

    К счастью для Debian 8 (Jessie) есть решение проще, в Debian Backports есть nginx 1.10.3 с нужной версией OpenSSL и поддержкой ALPN, давайте заменим Nginx без ALPN из официального репозитария Nginx Inc. на Nginx из Deban Backports.

    Исходные данные: Debian 8.9 Jessie (amd64) + nginx 1.10.3 (official Nginx Inc. repo)

    Найдем в каком файле конфигурации репозитариев у нас прописан официальный репозитарий Nginx Inc.:

    # find /etc/apt/ -type f -exec grep "nginx.org" {} ';' -print
    deb http://nginx.org/packages/debian/ jessie nginx
    deb-src http://nginx.org/packages/debian/ jessie nginx
    /etc/apt/sources.list.d/nginx.list
    

    Удалим файл /etc/apt/sources.list.d/nginx.list

    rm -f /etc/apt/sources.list.d/nginx.list

    Добавим репозитарий jessie-backports:

    echo "deb http://deb.debian.org/debian jessie-backports main contrib non-free" >/etc/apt/sources.list.d/backports.list
    echo "deb http://deb.debian.org/debian jessie-backports-sloppy main contrib non-free" >>/etc/apt/sources.list.d/backports.list
    

    Обновим список пакетов:

    apt-get update
    

    Сделаем резервную копию каталога /etc/nginx

    cp -r /etc/nginx/ ~
    

    Удалим Nginx который был установлен из официального репозитария Nginx Inc., а так же ВНИМАНИЕ! удалим все файлы настроек из /etc/nginx:

    apt-get remove nginx-* --purge
    

    Установим Nginx:

    apt-get -t jessie-backports install nginx-full
    

    Посмотрим версию Nginx:

    # nginx -V
    nginx version: nginx/1.10.3
    built with OpenSSL 1.0.2l  25 May 2017
    TLS SNI support enabled
    

    Отлично, Nginx собран с OpenSSL 1.0.2l, а это значит, что ALPN будет работать.

    Теперь скопируем наши сохраненные файлы конфигурации Nginx, в моем случае потребуется восстановить файлы:

    cp ~/nginx/nginx.conf /etc/nginx/
    cp ~/nginx/conf.d/default.conf /etc/nginx/conf.d/
    cp ~/nginx/conf.d/fallback.conf /etc/nginx/conf.d/
    cp ~/nginx/sites-available/mysite.ru.vhost /etc/nginx/sites-available/
    

    И создать симлинк:

    ln -s /etc/nginx/sites-available/mysite.ru.vhost /etc/nginx/sites-enabled/100-mysite.ru.vhost
    

    Проверим конфигурацию Nginx:

    # nginx -t
    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /etc/nginx/nginx.conf test is successful
    

    Все отлично, теперь перезагрузим конфигурацию:

    nginx -s reload
    

    Теперь мы можем воспользоваться сервисом тестирования SSL от SSLLabs или от от High-Tech Bridge и убедиться, что ALPN у нас работает.
    Дополнительно мы можем открыть инструменты разработчика в Google Chrome (горячая клавиша F12), на вкладке Network включить отображение столбца Protocol и открыть наш сайт и убедиться, что загрузка происходит по протоколу HTTP/2 (H2).

    К сожалению у решения с репозитарием Debian Backports есть пара недостатков:
    1. Nginx версии 1.10.3, то есть считается устаревшим, смотрим официальный сайт.
    2. Отсутствует модуль nginx-njs (NginScript), если он Вам будет нужен, то увы, в Debian Backports его нет.

    Поэтому для меня Решение 1 увы не подходит и мы переходим к Решению 2.

    Решение 2: Пересборка Nginx из исходников.

    Исходные данные: Debian 8.9 Jessie (amd64) + nginx 1.13.7 без ALPN (official Nginx Inc. repo)

    В предыдущей статье: Базовая установка и настройка Nginx на Debian 8 мы установили Nginx 1.13.7 из официального репозитария Nginx Inc., но как я писал выше, для Debian 8 он собран с OpenSSL 1.0.1t, а значит без поддержки ALPN.

    Процедура пересборки Nginx довольно простая:

    1. Создание временного каталога для сборки, скачивание нужной версии OpenSSL:

    mkdir -p /usr/src/nginx && cd /usr/src/nginx
    wget https://www.openssl.org/source/openssl-1.0.2m.tar.gz
    tar zxf openssl-1.0.2m.tar.gz
    

    2. Скачивание исходников Nginx из официального репозитария Nginx Inc. и подготовка системных зависимостей для сборки:

    apt-get source nginx
    apt-get build-dep nginx
    

    3. Изменение параметров сборки DEB-пакета (файл rules):

    vi /usr/src/nginx/nginx-1.13.7/debian/rules
    

    Находим строку начинающуюся с

    CFLAGS=""

    и после опции

    --with-stream_ssl_preread_module

    добавляем:

    --with-pcre-jit --with-openssl=/usr/src/nginx/openssl-1.0.2m --with-openssl-opt="threads -fPIC"
    

    Что это значит?
    а) Опция —with-pcre-jit — мы будем собирать Nginx cо сборкой библиотеки PCRE с поддержкой JIT-компиляции. Для чего? А для того, чтобы можно было использовать опцию pcre_jit on; которая существенно ускорит обработку регулярных выражений за счет использования PCRE JIT;
    б) Опция —with-openssl — указывает с какой версией библиотеки OpenSSL будет собран Nginx;
    в) Опции —with-openssl-opt — указывают с какими параметрами собирать OpenSSL;

    4. Сборка Nginx:

    cd /usr/src/nginx/nginx-1.13.7
    dpkg-buildpackage -us -uc
    

    После успешной сборки в каталоге /usr/src/nginx/ должен появится готовый DEB-пакет nginx_1.13.7-1~jessie_amd64.deb

    5. Установка собранного Nginx:

    cd /usr/src/nginx
    dpkg -i nginx_1.13.7-1~jessie_amd64.deb
    

    6. Проверка Nginx:

    # nginx -V
    nginx version: nginx/1.13.7
    built by gcc 4.9.2 (Debian 4.9.2-10)
    built with OpenSSL 1.0.2m  2 Nov 2017
    TLS SNI support enabled
    

    7. Тестирование ALPN:

    testsite="mysite.ru"; echo | /usr/src/nginx/openssl-1.0.2m/.openssl/bin/openssl s_client -alpn h2 -connect $testsite:443 -servername $testsite 2>&1 | grep -q "ALPN protocol: h2" && echo "ALPN supported" || echo "ALPN not supported"
    

    P.S. Не забудьте исправить «mysite.ru» на свой сайт.

    В результате должно выдать «ALPN supported».
    Если вдруг будет выдано «ALPN not supported», то следует перезапустить Nginx и провести тест еще раз.

    Для дополнительной проверки мы можем воспользоваться сервисом тестирования от SSLLabs или от от High-Tech Bridge и убедиться еще раз, что ALPN у нас работает. Так же мы можем открыть инструменты разработчика в Google Chrome (горячая клавиша F12), на вкладке Network включить отображение столбца Protocol и открыть наш сайт и убедиться, что загрузка происходит по протоколу HTTP/2 (H2).

    8. Блокировка обновления Nginx из официального репозитария.

    После того как мы установили наш Nginx нужно заблокировать возможность обновления Nginx из официального репозитария в случае использования команды apt-get upgrade.

    Смотрим список всех установленных компонентов Nginx:

    # dpkg --get-selections | grep nginx
    nginx                                           install
    nginx-module-image-filter                       install
    nginx-module-njs                                install
    

    Блокируем обновление пакетов:

    apt-mark hold nginx nginx-module-image-filter nginx-module-njs
    

    Какие Ваши действия при выходе новой версии Nginx в официальном репозитарии ?

    1. Обновить список пакетов:

    apt-get update
    

    2. Произвести пересборку новой версии Nginx в соответствии с нашим «Решение 2»

    Например вышел Nginx 1.13.8, то в случае успешной сборки у нас получиться DEB-пакет nginx_1.13.8-1~jessie_amd64.deb

    3. Разблокировать модули Nginx из официального репозитария и установить наш новый Nginx:

    systemctl stop nginx.service
    apt-mark unhold nginx-module-image-filter nginx-module-njs
    apt-get install nginx-module-image-filter nginx-module-njs
    dpkg -i nginx_1.13.8-1~jessie_amd64.deb
    systemctl start nginx.service
    

    Проверить запуск Nginx и его версию командами:

    nginx -V
    systemctl status nginx.service
    netstat -ltupn | grep nginx
    

    На этом все, до скорых встреч. Если у Вас возникли вопросы или Вы хотите чтобы я помог Вам, то Вы всегда можете связаться со мной разными доступными способами.