支持SNI回源的NGINX反向代理配置

背景:

这事起源于需要一个反向代理,请求的后端是某开放云的CDN,CDN配置为基于HTTPS的虚拟主机(单IP多Host区分的虚拟主机)

注意:所以本文并不是你的nginx提供了https服务,而在于反向代理的后端是否为https服务。

正常nginx配置了SSL是可以通过HTTPS访问后端的,但是对SNI的支持有点麻烦。

代理服务器是nginx。以下为配置过程:

首先支持SNI协议的openssl版本最低为0.9.8f

Mozilla NSS 3.11.1 client-side only
OpenSSL
0.9.8f (released 11 Oct 2007) – not compiled in by default, can be compiled in with config option ‘–enable-tlsext’
0.9.8j (released 07 Jan 2009) through 1.0.0 (released 29 March 2010) – compiled in by default
GNU TLS
libcurl / cURL since 7.18.1 (released 30 Mar 2008) when compiled against an SSL/TLS toolkit with SNI support
Python 3.2 (ssl, urllib and httplib modules)
Qt 4.8
Oracle Java 7 JSSE

这里选用了最新的openssl 1.0.1t

openssl安装
1、下载openssl
curl -o openssl.tar.gz https://www.openssl.org/source/openssl-1.0.1t.tar.gz

2、解压缩
tar -xvf openssl-1.0.1t.tar.gz

openssl这里不用编译,有源代码就可以了

nginx安装
1、下载nginx
nginx从1.7之后开始支持SNI proxy指令,我这里选择的nginx 1.10

wget http://nginx.org/download/nginx-1.10.0.tar.gz

2、解压缩
tar -xvf nginx-1.10.0.tar.gz

3、进入nginx目录,配置
cd nginx-1.10.0

以下是我的配置命令,目录自行选择

./configure –prefix=/opt/nginx/ –with-http_ssl_module –with-openssl=~/openssl-1.0.1t

上面参数解释:

–prefix是nginx安装目录

–with-http_ssl_module是启用ssl支持

–with-openssl是刚刚下载的openssl代码目录

4、编译、安装
make && make install

编译之后,进入nginx的sbin/nginx -V 看一下是否支持

nginx version: nginx/1.10.0
built by gcc 3.4.5 20051201 (Red Hat 3.4.5-2)
built with OpenSSL 1.0.1t 3 May 2016
TLS SNI support enabled

如果有TLS SNI support enabled就表示支持SNI

配置nginx
修改nginx安装后的conf/nginx.conf文件

以下是我的server配置节

server {
listen 8443;

access_log logs/proxy_access.log main;
underscores_in_headers on;
ssl_protocols SSLv3 TLSv1.2 TLSv1.1 TLSv1 SSLv2;

error_log logs/error.log info;
location / {
proxy_ssl_server_name on;
proxy_pass https://$http_host$request_uri;
proxy_ssl_verify off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header cookie $http_cookie;
proxy_set_header Proxy-Connection “”;
proxy_http_version 1.1;
}
}
以上配置酌情修改,注意以下几点:

1、proxy_pass 后面是https://这说明请求以https协议发出

2、为了安全,proxy_ssl_verify 应该配置为on,这里便于调试配置了off,如果配置了on,请确保你的ca文件中有对方服务器的证书

3、ssl_protocols 表示支持的协议,印象中是sslv3和tls1.0以后才支持的SNI,所以都写上吧。

4、最关键的一句proxy_ssl_server_name on最关键的,也就是把主机名字传递给后端服务器,让对方服务器在TLS握手层面就可以收到host,便于打到具体的主机。(nginx 1.7开始支持)

重启nginx服务器(涉及socket监听的不要使用reload,因为无法reload信号无法让nginx重新监听)

验证
curl -v -x 127.0.0.1:8443 http://www.symantec.com/

应返回:
* Trying 127.0.0.1…
* Connected to 127.0.0.1 (127.0.0.1) port 8443 (#0)
> HEAD http://www.symantec.com/ HTTP/1.1
> Host: www.symantec.com
> User-Agent: curl/7.47.1
> Accept: */*
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 200 OK

特别说明一下:http://www.symantec.com/这里虽然写的是http,但实际上代理走的是https协议,这里不能直接写https

未配置SNI会出现的问题
目前发现,对于使用SNI做域名识别的HTTPS Web服务器,如果代理服务器不发送SNI,会返回502错误。即无法正常和后端通信。

参考资料
SNI(Server Name Indication)https://en.wikipedia.org/wiki/Server_Name_Indication

nginx关于proxy_ssl_server_name的配置:http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ssl_server_n