将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
仅支持NPNUbuntu 12.04 LTS
1.0.1
仅支持NPNUbuntu 14.04 LTS
1.0.1f
仅支持NPNUbuntu 16.04 LTS
1.0.2g
同时支持ALPN和NPNDebian 7.0
1.0.1e
仅支持NPNDebian 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
NPNCentOS/Oracle Linux/RHEL 7.4+
1.0.2k
ALPN和NPNDebian 7.0
1.0.1e
NPNDebian 8.0
1.0.1k
NPNDebian 9.0
1.1.0f
ALPN和NPNUbuntu 12.04 LTS
1.0.1
NPNUbuntu 14.04 LTS
1.0.1f
NPNUbuntu 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’。
同时,您还可以在Chrome扩展程序HTTP/2和SPDY指示器中进行确认。
追记) 注意事项:适用于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)的退出状态不正确