在 nginx 中配置 HTTP/2 支持

目录
[隐藏]

HTTP/2 协议于 2015 年 5 月 14 日正式版发布。随着 nginx 等流行 webserver 以及各大浏览器对 HTTP/2 的支持,越来越多的网站开始部署 HTTP/2 了。

HTTP/2 协议

HTTP/2 源自 SPDY/2。SPDY 系列协议由谷歌开发,于 2009 年公开。它的设计目标是降低 50% 的页面加载时间。

HTTP/2 协议由以下两个 RFC 组成:

RFC 7540 - Hypertext Transfer Protocol Version 2 (HTTP/2)
RFC 7541 - HPACK: Header Compression for HTTP/2

HTTP/2 官网 可以找到更多有关 HTTP/2 协议的资料。

HTTP/2 特点

HTTP/2 在底层传输上带来了大量的改动与优化,简单来讲,就是可以使得页面更快更持久。

二进制格式传输数据

HTTP/2 采用二进制格式传输数据。二进制格式在协议的解析和优化扩展上带来更多的优势和可能。

消息头压缩传输

HTTP/2 对消息头采用 HPACK 进行压缩传输,能够节省消息头占用的网络的流量。而 HTTP/1.x 每次请求,都会携带大量冗余头信息,浪费了很多带宽资源。头压缩能够很好的解决该问题。

多路复用

利用多路复用可以实现延迟削减。

每个 Frame Header 都有一个 Stream ID 就是被用于实现该特性。每次请求/响应使用不同的 Stream ID。就像同一个 TCP 链接上的数据包通过 IP:PORT来区分出数据包去往哪里一样。通过 Stream ID 标识,所有的请求和响应都可以欢快的同时跑在一条 TCP 链接上了。

当流并发时,就会涉及到流的优先级和依赖。优先级高的流会被优先发送。图片请求的优先级要低于 CSS 和 SCRIPT,这个设计可以确保重要的东西可以被优先加载完。

直白的说就是所有的请求都是通过一个 TCP 连接并发完成。HTTP/1.x 虽然通过 pipeline 也能并发请求,但是多个请求之间的响应会被阻塞的,所以 pipeline 至今也没有被普及应用,而 HTTP/2 做到了真正的并发请求,同时,流还支持优先级和流量控制。

服务器推 (Server Push)

当服务端需要主动推送某个资源时,便会发送一个 Frame Type 为 PUSH_PROMISE 的 Frame,里面带了 PUSH 需要新建的 Stream ID。意思是告诉客户端:接下来我要用这个 ID 向你发送东西,客户端准备好接着。客户端解析 Frame 时,发现它是一个 PUSH_PROMISE 类型,便会准备接收服务端要推送的流。

这使得服务端能够更快的把资源推送给客户端。例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 再发送这些请求。当客户端需要的时候,它已经在客户端了。

nginx 启用 HTTP/2 支持

nginx 在 1.9.5 以后开始支持 HTTP/2,并且移除了 SPDY 模块。只需将http2 添加到所有 listen 指令当中即可。示例:

server {
    server_name lzw.me;
    root /site/xxx;
    listen 443 ssl http2 fastopen=3 default_server;

    # 证书
    ssl_certificate     /etc/ssl_cert/lzw.me.crt;
    ssl_certificate_key /etc/ssl_cert/lzw.me.key;

    # 配置赫尔曼密钥
    # openssl dhparam -out /etc/ssl/dhparam.pem 2048 //openssl 生成 2048 位的密钥
    ssl_dhparam /etc/ssl_cert/dhparam.pem;

    # 禁止已经不安全的加密算法
    ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4';

    # 缓解 BEAST 攻击
    ssl_prefer_server_ciphers on;

    # OCSP 缝合
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/nginx/cert/trustchain.crt;
    resolver 114.114.114 8.8.8.8 valid=300s;

    # 禁止不安全的 SSL 协议,使用安全协议
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    # 缓存连接凭据
    ssl_session_cache shared:SSL:20m;
    ssl_session_timeout 60m;

    # 启用 HSTS,要求浏览器总是通过 https 访问
    add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains; preload';
    # 禁止被外部网站 iframe
    add_header X-Frame-Options SAMEORIGIN;
    # 严格的 MIME 类型响应,禁止 MIME-sniffing 猜测资源类型
    add_header X-Content-Type-Options nosniff;

    # Public Key Pinning 对抗中间人攻击
    # 生成方法参考:https://lzw.me/a/public-key-pins-hpkp.html
    add_header Public-Key-Pins 'pin-sha256="oYQpJ7NWxATnj4TXRpTIKMECkarA0lPcAblBBaV23Io="; pin-sha256="JGBhwHhyQ8RjdAiTim2KGnNegkNyBfISSuCL2YiSbTM="; max-age=2592000; includeSubDomains';

}

# 80 端口跳转到 https
server {
    listen 80;
    add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload';
    return 301 https://lzw.me$request_uri;
}

支持 ALPN

chrome 51 之后,要求对 HTTP/2 只支持 ALPN,否则还是会使用 HTTP/1.1。openssl 在 1.0.2 之后的版本才支持 ALPN。如果你的 nginx 是使用动态链接方式支持 openssl,需要更新系统的 openssl 版本;如果 nginx 是使用静态编译方式,则需要重新编译。验证是否支持 ALPN:

openssl s_client -alpn h2 -servername lzw.me -connect lzw.me:443 < /dev/null | grep 'ALPN'

看到输出 ALPN protocol: h2 则说明是支持的。

如不支持,请参考下面的编译 nginx 方法。

编译 nginx 支持 ALPN

安装依赖库和编译要用到的工具(选择没有的进行安装):

sudo yum install build-essential libpcre3 libpcre3-dev zlib1g-dev unzip git

下载 openssl

unzip openssl.zip
mv openssl-OpenSSL_1_0_2h/ openssl

cd openssl
patch -p1 < ../sslconfig/patches/openssl__chacha20_poly1305_draft_and_rfc_ossl102g.patch 

cd ../

下载 nginx-ct,以启用 Certificate Transparency

wget -O nginx-ct.zip -c https://github.com/grahamedgecombe/nginx-ct/archive/v1.2.0.zip
unzip nginx-ct.zip
mv nginx-ct-1.2.0 nginx-ct

下载 nginx 并解压,执行编译安装

wget -c https://nginx.org/download/nginx-1.11.3.tar.gz
tar zxf nginx-1.11.3.tar.gz

cd nginx-1.11.3/

./configure --add-module=../nginx-ct-1.2.0 --with-openssl=../openssl --with-http_stub_status_module --with-http_v2_module --with-http_ssl_module --with-ipv6 --with-http_sub_module --with-http_gzip_static_module 
# 应根据实际情况添加 user 和 group 参数
# --prefix=/usr/local/nginx --user=www --group=www

make
sudo make install

验证网站对 HTTP/2 的支持

在 chrome 浏览器控制台中,可以通过查看 Protocol 列进行验证。示例:

更多参考:https://www.nginx.com/blog/nginx-1-9-5/

也可以通过 ssl 在线测试网站来查看网站 ssl 支持情况:

ssl 在线测试
HTTP Security Report

注意点

HSTS preload 与子域名相关问题

谨慎设置 HSTS 信息。它会导致所有基于 http 的访问都被自动提升为 https。如果设置了 includeSubDomains,子域名也会如此。

add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains; preload';

如果遇到因为这个问题导致 Chrome 无法访问站点的情况,注释去掉这个设置,并且到 Chrome 相关设置中删除域名缓存:

chrome://net-internals/#hsts

hsts-delete

https 后端 API 代理使用 http 问题

用户访问的域名使用了 https ,但后端不同服务可能会转发到不同的后端服务上,这些服务使用 http 作代理时,在获取使用的协议时就变成了 http。此时需要添加一个代理转发header:X-Forwarded-Proto

代理转发配置示例:

    location /api {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_pass   http://localhost:1337;
    }

相关参考

HTTP/2 gitbook
http://http2.github.io/
HTTP,HTTP2.0,SPDY,HTTPS你应该知道的一些事
https://segmentfault.com/a/1190000002765886
https://imququ.com/post/enable-alpn-asap.html
https://imququ.com/post/my-nginx-conf.html

0 个评论
写个评论