将nginx配置为支持HTTP/2的方法(同时在Chrome 51及更高版本中生效)

这次我在CentOS6.7上设置nginx的SSL时,也尝试了配置HTTP/2,但在Google Chrome中无法启用HTTP/2,非常麻烦。所以我将这次的步骤记录下来。

在使用较旧的nginx稳定版本(1.10以下)时,如果在iOS Safari上进行Post操作,可能会出现错误。请参考后续内容。

追记 2016/09/30,我成功地从SRPM构建了主线版本。(非常感谢您的评论。)

追加 2017/12/02 更新了nginx的文章,包括NPN/ALPN的兼容性表(引用)。
追加 2017/12/02 添加了各种浏览器不支持NPN的情况。

为什么Google Chrome无法启用HTTP/2的原因?

谷歌浏览器51版(2016年4月)对SPDY/3.1和NPN进行了删除,只支持ALPN。

移除对SPDY/3.1的支持
移除TLS Next Protocol Negotiation (NPN)

如果在CentOS6.7上使用yum安装nginx,只能支持NPN协议,不支持ALPN协议。当Chrome连接到不支持ALPN的服务器时,会放弃HTTP/2连接并回退到HTTP/1.1。(Firefox48.0.2支持NPN,所以可以使用HTTP/2连接。)

NPN和ALPN是什么?

这是决定浏览器和web服务器之间连接方式的步骤。通过ALPN,客户端会向服务器提供HTTP/2、SPDY、HTTP/1.1等连接方式的列表,并由服务器选择其中一种连接方式。

在TLS上进行的协议协商机制,NPN和ALPN – あすのかぜ。

(补充)各种浏览器的NPN不支持情况(2017年)

您所使用的浏览器不支持NPN协议。

然而,截至2017年中期,除了Safari浏览器(版本10)外,所有最受欢迎的浏览器供应商都已停止支持NPN,以下是各个浏览器版本的具体情况(请参考Qualys SSL Labs网站上各个浏览器页面的”协议详细信息”部分):
Chrome 51
Edge 12
Firefox 53
Internet Explorer 11
Opera 38

为Google Chrome用户提供对HTTP/2的支持。

如何在Nginx中支持ALPN

Nginx 在1.9.5版本开始支持HTTP/2。

ngx_http_v2_module模块(1.9.5版本)提供了对HTTP/2的支持,并替代了ngx_http_spdy_module模块。

然而,Nginx使用OpenSSL进行TLS连接,而OpenSSL在1.0.2或更高版本中支持ALPN。
虽然只需要使用OpenSSL 1.0.2或更高版本就可以了,但目前大部分Linux发行版仍然使用不支持ALPN的OpenSSL 1.0.1系列。

2016年的时候

以下是对ALPN和NPN协议在不同操作系统的支持情况的表格总结:

操作系统
OpenSSL版本
ALPN和NPN支持情况

CentOS/Oracle Linux/RHEL 5.10及以上
0.9.8e
无支持

CentOS/Oracle Linux/RHEL 6.5及以上,7.0及以上
1.0.1e
仅支持NPN

Ubuntu 12.04 LTS
1.0.1
仅支持NPN

Ubuntu 14.04 LTS
1.0.1f
仅支持NPN

Ubuntu 16.04 LTS
1.0.2g
同时支持ALPN和NPN

Debian 7.0
1.0.1e
仅支持NPN

Debian 8.0
1.0.1k
仅支持NPN

2017年九月份的时候

以下是截至2017年9月Linux操作系统对ALPN和NPN的支持情况总结。

操作系统
OpenSSL版本
ALPN/NPN支持

CentOS/Oracle Linux/RHEL 6.5+,7.0–7.3
1.0.1e
NPN

CentOS/Oracle Linux/RHEL 7.4+
1.0.2k
ALPN和NPN

Debian 7.0
1.0.1e
NPN

Debian 8.0
1.0.1k
NPN

Debian 9.0
1.1.0f
ALPN和NPN

Ubuntu 12.04 LTS
1.0.1
NPN

Ubuntu 14.04 LTS
1.0.1f
NPN

Ubuntu 16.04 LTS
1.0.2g
ALPN和NPN

你可以試試看輸入 nginx -V 來確認一下,你使用的是 OpenSSL 1.0.1 系列。

# nginx -V
nginx version: nginx/1.10.1
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-16) (GCC)
built with OpenSSL 1.0.1e-fips 11 Feb 2013

升级安装在操作系统上的OpenSSL版本可能会对使用OpenSSL的其他程序产生影响,造成困扰。
因此,我会从源代码编译nginx,并使用–with-openssl标志进行静态链接,以便使用OpenSSL 1.0.2版本。
请注意,如果OpenSSL或nginx有更新,需要再次进行编译。

NGINX源码支持OpenSSL 1.0.2(以及即将发布的1.1.0版本),所以另一个选择是从源代码重新编译NGINX,并通过包含–with-openssl参数链接到私有的OpenSSL 1.0.2构建版本。 支持Google Chrome用户的HTTP/2 | NGINX

用原始代码构建Nginx

这是从nginx源代码构建的步骤。

安装构建工具和依赖库。(正在使用 root 权限进行操作。)

# yum groupinstall  "Development Tools"
# yum install  pcre pcre-devel zlib zlib-devel

下载并解压OpenSSL-1.0.2。

# curl -L -O https://www.openssl.org/source/openssl-1.0.2h.tar.gz
# tar xvfz openssl-1.0.2h.tar.gz

下载并解压nginx-1.10.1。

# curl -L -O https://nginx.org/download/nginx-1.10.1.tar.gz
# tar xvfz nginx-1.10.1.tar.gz

使用”./configure”命令。
在末尾加上”–with-openssl=../openssl-1.0.2h/”指定OpenSSL的完整文件夹路径。

# cd nginx-1.10.1
# ./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 \
  --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 \
  --with-openssl=../openssl-1.0.2h/

进行安装。

# make 
# make install

通过查看 nginx -V,可以确认 OpenSSL 版本为1.0.2系。

# nginx -V
nginx version: nginx/1.10.1
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-17) (GCC)
built with OpenSSL 1.0.2h  3 May 2016 
TLS SNI support enabled

参考来源:
– 配置选项参考了nginx.org上的内容。

nginx: Linux包的配置参数

    起動スクリプトは以下にあります。(動作未確認)

红帽NGINX启动脚本| NGINX

    logrotate等は別途設定が必要です。

使用nginx构建SRPM

在设置服务器时,使用rpm软件包仍然更方便。由于nginx还提供了源代码的RPM包,因此可以重新创建rpm软件包。
(最终我在虚拟PC上的CentOS操作系统中创建了rpm软件包,并将该软件包应用于服务器。)

以下是创建nginx的rpm软件包的步骤。

安装构建工具和依赖库。

由于GeoIP-devel是通过epel分发的,因此需要添加epel存储库。

# rpm -ivh http://ftp.riken.jp/Linux/fedora/epel/6/i386/epel-release-6-8.noarch.rpm
# yum install rpm-build  
# yum groupinstall "Development Tools"
# yum install zlib-devel pcre-devel perl-devel perl-ExtUtils-Embed GeoIP-devel libxslt-devel gd-devel 

据说在root权限下进行rpm构建是危险的,因此我们将创建一个名为builder的用户。

useradd builder  

切换至建筑者用户。

su builder -

创建.rpmmacros文件(rpmbuild的配置文件)。

cd 
echo '%_topdir %(echo $HOME)/rpmbuild' > ~/.rpmmacros 

获取并解压nginx的SRPM。

curl -L -O http://nginx.org/packages/centos/6/SRPMS/nginx-1.10.1-1.el6.ngx.src.rpm
rpm -ivh nginx-1.10.1-1.el6.ngx.src.rpm

如果使用nginx1.11系(主线版本),可以从 http://nginx.org/packages/mainline/centos/6/SRPMS/ 下载SRPMS。

curl -L -O http://nginx.org/packages/mainline/centos/6/SRPMS/nginx-1.11.4-1.el6.ngx.src.rpm
rpm -ivh nginx-1.11.4-1.el6.ngx.src.rpm

如果要应用nginx源代码并更改版本,请先下载相关的源代码。例如,如果要使用nginx-1.11.3的源代码。

cd rpmbuild/SOURCES
curl -L -O https://nginx.org/download/nginx-1.11.3.tar.gz

我将编辑 nginx.spec 文件。(我将编辑★处的部分。)

%if 0%{?rhel}  == 6
%define _group System Environment/Daemons
%define with_http2 1
Requires(pre): shadow-utils
Requires: initscripts >= 8.36
Requires(post): chkconfig

 opensslをコメントに
# Requires: openssl >= 1.0.1
# BuildRequires: openssl-devel >= 1.0.1

BuildRequires: perl-devel
BuildRequires: perl-ExtUtils-Embed
BuildRequires: GeoIP-devel
%endif

 中略  

%define COMMON_CONFIGURE_ARGS $(echo "\
        --prefix=%{_sysconfdir}/nginx \
        --sbin-path=%{_sbindir}/nginx \
        --modules-path=%{_libdir}/nginx/modules \
        --conf-path=%{_sysconfdir}/nginx/nginx.conf \
        --error-log-path=%{_localstatedir}/log/nginx/error.log \
        --http-log-path=%{_localstatedir}/log/nginx/access.log \
        --pid-path=%{_localstatedir}/run/nginx.pid \
        --lock-path=%{_localstatedir}/run/nginx.lock \
        --http-client-body-temp-path=%{_localstatedir}/cache/nginx/client_temp \
        --http-proxy-temp-path=%{_localstatedir}/cache/nginx/proxy_temp \
        --http-fastcgi-temp-path=%{_localstatedir}/cache/nginx/fastcgi_temp \
        --http-uwsgi-temp-path=%{_localstatedir}/cache/nginx/uwsgi_temp \
        --http-scgi-temp-path=%{_localstatedir}/cache/nginx/scgi_temp \
        --user=%{nginx_user} \
        --group=%{nginx_group} \
        --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-http_xslt_module=dynamic \
        --with-http_image_filter_module=dynamic \
        --with-http_geoip_module=dynamic \
        --with-http_perl_module=dynamic \
        --add-dynamic-module=njs-%{module_njs_shaid}/nginx \
        --with-threads \
        --with-stream \
        --with-stream_ssl_module \
        --with-http_slice_module \
        --with-mail \
        --with-mail_ssl_module \
        --with-file-aio \
        --with-ipv6 \
        --with-openssl=/home/builder/openssl-1.0.2h/ \  ★追加
        %{?with_http2:--with-http_v2_module}")


 中略  

# end of distribution specific definitions

 main_version を変更します。(nginxのソースコードを変更してバージョンを変える場合のみ
#%define main_version                 1.10.1
%define main_version                 1.11.3
%define main_release                 1%{?dist}.ngx
%define module_xslt_version          %{main_version}
%define module_xslt_release          1%{?dist}.ngx
%define module_geoip_version         %{main_version}
%define module_geoip_release         1%{?dist}.ngx


进行构建。(有关选项请参阅注释。1 2)

rpmbuild -bb rpmbuild/SPECS/nginx.spec -D 'debug_package %{nil}'

成功后将创建一个rpm文件。

rpmbuild/RPMS/x86_64/nginx-1.10.1-1.el6.ngx.x86_64.rpm

将您创建的rpm文件在目标服务器上进行安装。

rpm -ihv nginx-1.10.1-1.el6.ngx.x86_64.rpm

我将编辑nginx的default.conf文件。(包括SSL证书的配置和HTTP/2的配置)

server {
    server_name  XXXX.com;
    listen       80;
    listen      443  ssl http2;
    ssl_certificate      /etc/pki/tls/certs/XXXX.crt;
    ssl_certificate_key  /etc/pki/tls/private/XXXX.private.key;

     ・
     ・
     ・

我要启动nginx。

/etc/init.d/nginx start

HTTP/2连接验证

通过SSL连接,并检查是否启用了HTTP/2。当在开发者工具中显示协议列时,它将显示为’h2’。

http2enable.png

同时,您还可以在Chrome扩展程序HTTP/2和SPDY指示器中进行确认。

chrome_http.png

追记) 注意事项:适用于Mac OS X(Windows可能也存在类似情况)。

有些防病毒软件使用可能无法支持HTTP/2协议。在Mac OS X上可能无法启用HTTP/2。

在使用iOS Safari时,进行POST操作时发生了错误。

在iOS的Safari中使用AjaxPost时,如果使用nginx + HTTP/2,可能会在浏览器中导致如下错误。
(该错误不会记录在nginx日志中。)

Failed to load resource: request body stream exhausted
Failed to load resource: Could not connect to the server.

看起来与以下nginx问题有关。

    • #959 (Permit post before acking settings) – nginx

 

    HTTP/2: implemented preread buffer for request body

似乎对 MS IE/Edge、Safari 和 iOS 应用程序产生了影响。

如果流被拒绝,一些客户端(特别是MS IE/Edge,Safari,iOS应用程序)会显示错误甚至崩溃。

这是一个可能被考虑作为应对措施的方法。

    • nginxのソースにパッチを当てる(未確認です)

 

    nginx1.11系(mainline)を使う (やってみました。)

使用nginx1.11系(主线版)

截止目前(2016.09.06),似乎已经在nginx-1.11.0中加入了这个修正。

HTTP/2:为请求体实现了预读缓冲区(关闭#959)。…

HTTP/2协议中包含了后续的修正(包括解决了segfault问题)。
我尝试了nginx-1.11.3版本,从iOS Safari发送POST请求时没有出现错误。
有关如何制作nginx-1.11.3的rpm包的方法已经添加到了“nginx的SRPM构建”中。

参考网站及感想

我原打算在nginx的配置文件中只写上http2,所以费了很大功夫。或许在Apache2.4系列中也有类似的情况。

非常感谢您提供的网站,给我提供了宝贵的信息。

    • いまさら聞けないrpmbuildことはじめ – hack in 3 minutes

 

    nginxでsrpmからのconfigure変更

注释

-bb选项在进行%prep、%build和%install阶段后会构建一个二进制软件包。rpmbuild

-D ‘debug_package %{nil}’选项用于不创建调试软件包。如果未指定,会导致debugedit在中途出错。从/home/builder/rpmbuild/BUILDROOT/nginx-1.10.1-1.el6.ngx.x86_64/usr/lib64/perl5/vendor_perl/auto/nginx/nginx.so提取调试信息 /usr/lib/rpm/debugedit:规范化结果比预期少一个字符 错误:/var/tmp/rpm-tmp.Ibuv0o (%install)的退出状态不正确

广告
将在 10 秒后关闭
bannerAds