Nginx同一端口如何实现支持HTTP与HTTPS两种协议
nginx经常会碰到这样一个问题:能不能让Nginx在同一个端口下同时支持HTTP和HTTPS这两种协议呢?今天咱们就来看一下这个问题,教你如何巧妙配置Nginx实现这一功能。
一、Nginx的局限性与解决方案
Nginx本身是不支持在同一个端口同时提供HTTP和HTTPS服务的。这是为啥呢?因为HTTP和HTTPS这两种协议有着本质区别,HTTPS需要进行TLS握手来建立安全连接,而HTTP不需要。Nginx没办法自动判断一个来自同一端口的请求到底是HTTP的还是HTTPS的,所以直接这么配置肯定不行。
不过别担心,咱们有办法解决。可以借助SSL预读(SSL Preread)技术,利用Nginx的stream
模块来区分同一端口上的HTTP和HTTPS流量。下面就来看看具体的配置方法。
二、具体配置步骤
(一)使用stream模块监听443端口并区分流量
在Nginx的配置文件中,在http
之外的全局配置部分添加如下代码:
# 在 http 之外的全局配置中 stream { # 根据$ssl_preread_protocol变量的值来映射不同的后端服务器 map $ssl_preread_protocol $name { "" http_backend; # 如果$ssl_preread_protocol为空,说明没有TLS,是HTTP请求,映射到http_backend default https_backend; # 其他情况,也就是有TLS的,是HTTPS请求,映射到https_backend } # 定义HTTP后端服务器,这里配置为本地的8080端口 upstream http_backend { server 127.0.0.1:8080; } # 定义HTTPS后端服务器,这里配置为本地的8443端口 upstream https_backend { server 127.0.0.1:8443; } # 定义一个监听443端口的服务器 server { listen 443; # 根据$name变量的值,将请求转发到对应的后端服务器 proxy_pass $name; # 开启ssl_preread功能,用于读取TLS握手信息 ssl_preread on; } }
(二)配置http部分
在http
模块中,分别配置HTTP和HTTPS服务器:
http { # 配置监听8080端口的HTTP服务器 server { listen 8080; # 设置服务器名称,这里替换为你的域名 server_name yourdomain.com; location / { # 设置网站根目录 root /usr/share/nginx/html; # 设置默认首页 index index.html; } } # 配置监听8443端口的HTTPS服务器 server { listen 8443 ssl; # 设置服务器名称,这里替换为你的域名 server_name yourdomain.com; # 指定SSL证书路径 ssl_certificate /path/to/cert.pem; # 指定SSL证书私钥路径 ssl_certificate_key /path/to/key.pem; location / { # 设置网站根目录 root /usr/share/nginx/html; # 设置默认首页 index index.html; } } }
三、工作原理
(一)stream模块的作用
stream
模块主要用于处理TCP层(也就是四层)的代理。咱们都知道,Nginx默认是个HTTP服务器,只能解析HTTP请求。但有了stream
模块,它就能直接代理TCP流量了,这其中就包括HTTP和HTTPS的流量。
(二)ssl_preread on;的作用
开启ssl_preread
功能后,Nginx可以在不终止TLS连接的情况下,读取客户端发送的TLS握手信息。具体来说,它会读取客户端发送的ClientHello数据包。如果是HTTPS请求,这个数据包里会包含TLS版本、加密算法、SNI(服务器名称指示)等信息;要是HTTP请求,数据包里直接就是明文的GET/POST请求。通过这个方式,Nginx就能判断接收到的请求是HTTP还是HTTPS了。
(三)map变量映射
在stream
模块里的map
配置,是根据$ssl_preread_protocol
变量的值来决定请求该转发到哪个后端服务器。如果$ssl_preread_protocol
变量为空,那就说明是HTTP请求(因为HTTP没有TLS握手,也就不会有这个协议相关的值),会被代理到http_backend
,也就是8080端口;要是$ssl_preread_protocol
变量有值,那就是HTTPS请求,会被代理到https_backend
,也就是8443端口。
(四)stream服务器配置
stream
模块里的server
配置,监听443端口。它会自动区分HTTP和HTTPS请求,然后根据$proxy_backend
变量(也就是前面map
配置映射出来的值),把请求转发到8080端口(处理HTTP请求)或者8443端口(处理HTTPS请求)。
四、访问流程详解
(一)客户端访问HTTP
当你在命令行里执行curl -v http://zhgdqh.ezczb.com:443
这样的命令时,具体流程是这样的:
- 客户端发送HTTP请求,这个请求是明文的。
- Nginx的
ssl_preread
功能检测到这是一个HTTP请求。 - Nginx根据
map
规则进行匹配,发现$ssl_preread_protocol
为空,所以选择http_backend
。 - 请求被代理到
127.0.0.1:8080
,也就是HTTP服务器。 - HTTP服务器处理完请求后,返回HTTP响应。
(二)客户端访问HTTPS
当你执行curl -v https://zhgdqh.ezczb.com:443
时:
- 客户端先发送TLS的
ClientHello
握手包。 - Nginx的
ssl_preread
读取这个TLS协议信息。 - Nginx根据
map
规则匹配,判断这是一个HTTPS请求,所以选择https_backend
。 - 请求被代理到
127.0.0.1:8443
,也就是HTTPS服务器。 - HTTPS服务器继续完成握手过程,建立起HTTPS连接,然后处理请求并返回响应。
五、为什么要这么做以及方案优缺点
(一)为什么要用这种方案
默认情况下,Nginx确实不能在同一端口同时处理HTTP和HTTPS请求。按照传统的配置方法,一般是用listen 80
来处理HTTP请求,用listen 443 ssl
来处理HTTPS请求。但有些场景下,可能只能使用443端口,这时候就需要借助ssl_preread
技术来区分流量了。这种方案特别适用于负载均衡或者网关的场景,通过stream
模块代理TCP流量,可以在反向代理前端统一监听443端口,然后把请求转发到不同的服务(HTTP或HTTPS)。
(二)方案优缺点对比
优点 | 缺点 |
---|---|
允许HTTP和HTTPS共用443端口 | stream 只能代理TCP层,没办法解析HTTP请求路径 |
ssl_preread 无需解密TLS,效率高 | 不能同时实现HTTP到HTTPS的自动重定向 |
兼容性好,在负载均衡场景中表现出色 | 需要额外的http {} 服务器监听8080和8443端口 |
六、总结
通过stream
模块和ssl_preread
技术,咱们就能让Nginx在443端口同时支持HTTP和HTTPS,而且还不需要额外的端口。这种方案在很多场景下都非常实用,比如企业级的Nginx反向代理、云服务器的配置,还有Kubernetes Ingress网关等。虽然它存在一些小缺点,像没办法自动进行HTTP到HTTPS的跳转,但可以通过前端JS或者meta refresh
来解决。希望这篇文章能帮助大家更好地理解和配置Nginx,实现同一端口下HTTP和HTTPS的共存。