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这样的命令时,具体流程是这样的:

  1. 客户端发送HTTP请求,这个请求是明文的。
  2. Nginx的ssl_preread功能检测到这是一个HTTP请求。
  3. Nginx根据map规则进行匹配,发现$ssl_preread_protocol为空,所以选择http_backend
  4. 请求被代理到127.0.0.1:8080,也就是HTTP服务器。
  5. HTTP服务器处理完请求后,返回HTTP响应。

(二)客户端访问HTTPS

当你执行curl -v https://zhgdqh.ezczb.com:443时:

  1. 客户端先发送TLS的ClientHello握手包。
  2. Nginx的ssl_preread读取这个TLS协议信息。
  3. Nginx根据map规则匹配,判断这是一个HTTPS请求,所以选择https_backend
  4. 请求被代理到127.0.0.1:8443,也就是HTTPS服务器。
  5. 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的共存。