Loading... # Nginx HTTPS 端口接收 HTTP 请求异常(The plain HTTP request was sent to HTTPS port)处理方案 ## 一、问题背景 在使用 Nginx 做反向代理并开启 HTTPS 后,客户端如果误将 HTTP 请求发送到 HTTPS 端口(通常是自定义端口,如 `8880`),Nginx 会报错并返回一条提示: `The plain HTTP request was sent to HTTPS port`  这时,用户无法正常访问,业务流程被中断。常见场景包括: * 系统代码写死了 `http://ip:8880`,无法修改; * 内网环境中不能使用 80/443 端口,必须使用自定义端口; * 需要自动将 HTTP 请求重定向到 HTTPS 同端口地址。 ## 二、原理分析 1. **HTTP vs. HTTPS 协议差异** * HTTP 是明文协议,客户端直接发送请求头。 * HTTPS 在传输前要进行 TLS 握手(ClientHello),并加密后续数据。 2. **Nginx 的错误状态码 497** * 当 Nginx 在一个开启了 `ssl` 的 `listen` 端口上收到明文 HTTP 请求时,会立即返回内部错误码 `497`(`HTTP Request Sent to HTTPS Port`)。 * 这个状态码可用于 `error_page` 指令捕获并做自定义处理。 ## 三、错误的尝试方式:使用 `$scheme` 判断无效 也尝试使用如下方式: ```nginx if ($scheme = http) { return 301 https://$host:8880$request_uri; } ``` **这在本场景下是无效的**,原因如下: * `$scheme` 是 Nginx 成功解析 HTTP 请求后生成的变量。 * 当 HTTP 请求被误发到 `listen ... ssl;` 的端口时,Nginx 连 TLS 握手都没有成功完成,也就**不会进入 HTTP 配置语法树**,`$scheme` 根本不存在,配置块直接跳过,进入错误流程。 * 因此 `if ($scheme = http)` 是永远不会被执行到的。 **正确的处理方式是使用** **`error_page 497`** **捕获错误。** ## 四、解决思路 利用 Nginx 的 `error_page` 指令捕获 `497` 错误,并将请求重定向到 HTTPS 同端口地址。核心配置只需一个 `server` 块,示例: ```nginx server { listen 8880 ssl; server_name _; ssl_certificate /path/to/your/cert.pem; ssl_certificate_key /path/to/your/key.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # 捕获 HTTP 请求到 HTTPS 端口的错误状态码 497,并重定向 error_page 497 =307 https://$host:8880$request_uri; location / { proxy_pass http://your_upstream; # … 其他 proxy_set_header、超时等配置 } } ``` 这样,访问 `http://ip:8880/foo` 时: 1. Nginx 检测到明文 HTTP 请求发到了 SSL 端口,返回内部状态码 497; 2. `error_page 497 =307 ...` 将响应替换为 307 重定向; 3. 浏览器自动访问 `https://ip:8880/foo`,进入正常的 HTTPS 服务。 ## 五、常见实现方式对比 ### 方法一:直接使用 `error_page 497 =<状态码> 重定向` ```nginx error_page 497 =301 https://$host:8880$request_uri; error_page 497 =302 https://$host:8880$request_uri; error_page 497 =307 https://$host:8880$request_uri; ``` * **301 Moved Permanently**:永久重定向,浏览器会缓存重定向结果。 * **302 Found**:临时重定向,浏览器不会缓存,可灵活变更。 * **307 Temporary Redirect**:HTTP/1.1 标准,保持原请求方法(GET/POST)不变。 **适用场景**: * GET 请求可用 301 或 302; * 涉及 POST/PUT 等需要保留请求方法时,推荐使用 307; ### 方法二:使用命名 `location` 进行重定向 ```nginx error_page 497 @redirect_to_https; location @redirect_to_https { return 303 https://$host:8880$request_uri; } ``` * **303 See Other**:对于 POST 等非 GET 请求,客户端会使用 GET 方法去访问重定向地址。 **适用场景**: * 需要将 POST 转为 GET(比如表单提交后跳转展示页面); * 想要更精细地控制重定向逻辑。 ## 六、状态码区别与选型 |状态码|描述|浏览器缓存|请求方法保持|典型场景| | --------| ---------------------------------| ------------| --------------| --------------------------------------| |301|永久重定向(Moved Permanently)|会|保持|静态资源或永久变更的 URL| |302|临时重定向(Found)|不会|保持|临时维护或测试环境跳转| |303|See Other|不会|转为 GET|表单提交后跳转,避免重复提交| |307|Temporary Redirect(HTTP/1.1)|不会|保持|需要保留 POST/PUT 等方法的安全重定向| |308|Permanent Redirect(HTTP/1.1)|会|保持|永久重定向且保留方法的场景| ## 七、完整示例 ```nginx server { listen 8880 ssl; server_name example.com; ssl_certificate /etc/ssl/certs/example.pem; ssl_certificate_key /etc/ssl/private/example.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # 捕获 HTTP 请求到 HTTPS 端口,使用 307 保持请求方法 error_page 497 =307 https://$host:8880$request_uri; # 如果需要更复杂逻辑,可以用命名 location # error_page 497 @redirect_to_https; # location @redirect_to_https { # return 303 https://$host:8880$request_uri; # } location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ``` ## 八、小结 * **问题**:明文 HTTP 请求被发送到 HTTPS 端口,Nginx 返回 497 错误。 * **错误方式**:`if ($scheme = http)` 不会生效,因为请求无法被正确解析。 * **核心解决方案**:利用 `error_page 497` 指令捕获该错误,并做重定向。 * **灵活性**:可选用不同状态码(301/302/303/307/308)满足各种场景。 * **优点**:配置简洁,无需额外 TCP 分流或多实例部署。 更多细节请参考 Nginx 官方文档: * [ngx_http_core_module#error_page](http://nginx.org/en/docs/http/ngx_http_core_module.html#error_page) * [ngx_stream_ssl_preread_module](http://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html) 最后修改:2025 年 04 月 10 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果文章有帮助到你,请随意赞赏