Nginx同一端口HTTP跳转HTTPS的实现方法
背景
小目标:在只监听一个端口的情况下,将 HTTP 访问自动跳转为 HTTPS。
在常规情况下,HTTP 协议使用 80 端口,HTTPS 协议使用 443 端口。要实现 HTTP 强制跳转到 HTTPS 是一件非常简单的事情,随便都可以找到很多方案。然而,当使用非默认端口时,这就变得有点麻烦了。
场景描述:
- 将 HTTPS 的网站设置在非标准端口上,访问时需要带上端口号。
- 常常因为忘记加上
https://前缀导致访问失败。 - 希望在没有加上
https://前缀时,也能正常访问。
曾经有人通过在同一端口上同时监听 HTTP 和 HTTPS,实现 HTTP 和 HTTPS 在一个端口上工作。这种方式非常强大,但如果仅仅是为了让 HTTP 跳转到 HTTPS,有点杀鸡用牛刀的感觉。
解决方案
在研究 Nginx 的过程中,发现 Nginx 使用了一个特殊的状态码 497。根据 Nginx 的官方文档:
497:Nginx 内部使用的状态码,用于标识发送到 HTTPS 端口的纯 HTTP 请求,以区别于其他 4XX 错误,并用于错误页面重定向。
因此,可以利用 error_page 指令来拦截这个错误,实现同一端口的 HTTP 跳转到 HTTPS。
方法一:在 server 块中配置 error_page(局部配置)
实现步骤
-
修改 Nginx 配置
在对应的
server块中,添加以下配置:server { listen 443 ssl; listen [::]:443 ssl; server_name your_domain.com; ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/privkey.pem; # 使用 error_page 实现同一端口的 HTTP 跳转到 HTTPS(局部配置) error_page 497 =301 https://$host:$server_port$request_uri; # 其他配置... } -
变量解释
- $host:请求中的主机名,可能包含端口号(如果非标准端口)。
- $server_port:服务器监听的端口号。
- $request_uri:完整的请求 URI,包括路径和查询字符串。
-
注意事项
- 仅对当前
server块生效:此配置只会影响定义了error_page的server块,不会影响其他虚拟主机。 - 适用于单个域名或站点:如果您只有一个站点需要此功能,可以使用此方法。
- 仅对当前
方法二:在 http 块中配置 error_page(全局配置)
实现步骤
-
修改 Nginx 配置
在
http块中,添加以下配置:http { # 使用 error_page 实现同一端口的 HTTP 跳转到 HTTPS(全局配置) error_page 497 =301 https://$host:$server_port$request_uri; # 其他全局配置... server { listen 443 ssl; listen [::]:443 ssl; server_name your_domain.com; ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/privkey.pem; # 其他配置... } # 其他 server 块... } -
变量解释
同上。
-
注意事项
- 作用于所有
server块:此配置将影响所有虚拟主机,无论是否定义了error_page指令。 - 适用于多个域名或站点:如果您有多个站点需要此功能,使用全局配置可以避免重复配置。
- 作用于所有
方法比较
| 方法一:局部配置 | 方法二:全局配置 | |
|---|---|---|
| 作用范围 | 仅限于当前的 server 块 |
影响所有的 server 块 |
| 灵活性 | 可以针对特定的虚拟主机进行定制 | 对所有虚拟主机统一处理 |
| 配置简洁性 | 需要在每个 server 块中添加配置 |
只需在 http 块中配置一次 |
| 适用场景 | 仅有少数站点需要该功能,或需要不同的处理方式 | 所有站点都需要相同的处理方式 |
推荐使用全局配置
理由:
- 简洁性:只需配置一次,避免在多个
server块中重复配置。 - 一致性:确保所有站点在遇到同样的错误时,处理方式一致。
- 维护方便:如果需要修改,只需更改一处配置。
最终配置示例
http {
# 使用 error_page 实现同一端口的 HTTP 跳转到 HTTPS(全局配置)
error_page 497 =301 https://$host:$server_port$request_uri;
# 其他全局配置...
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name your_domain.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
# 其他配置...
}
# 其他 server 块...
}
变量解释
- $host:请求中的主机名,可能包含端口号(如果非标准端口)。
- $server_port:服务器监听的端口号。
- $request_uri:完整的请求 URI,包括路径和查询字符串。
官方文档参考
注意事项
- 状态码选择:根据需求选择合适的重定向状态码,
301表示永久重定向,302和307表示临时重定向。其中,307不会改变请求方法(如 POST 请求仍为 POST)。 - 端口号处理:如果使用了非标准端口(非 443),在重定向时需要显式地加上端口号
$server_port,以确保客户端能够正确访问。 - SSL 配置:确保在
server块中正确配置了 SSL 证书和密钥。
解决效果
配置完成并重启 Nginx 后,当用户以 HTTP 协议访问 HTTPS 端口时(例如 http://your_domain.com:443),服务器会自动将请求重定向到 https://your_domain.com:443,从而实现同一端口的 HTTP 到 HTTPS 跳转。
总结
通过使用 Nginx 的内部状态码 497,结合 error_page 指令,可以在同一端口上实现 HTTP 跳转到 HTTPS。两种配置方法各有优劣:
- 局部配置适用于特定站点需要定制化处理的情况。
- 全局配置适用于多个站点需要统一处理的情况,推荐使用全局配置。
最终推荐:使用全局配置,在 http 块中配置 error_page,这样可以确保所有站点都受益于同一端口 HTTP 到 HTTPS 的自动跳转,且配置管理更加方便。
示例完整配置:
http {
# 使用 error_page 实现同一端口的 HTTP 跳转到 HTTPS(全局配置)
error_page 497 =301 https://$host:$server_port$request_uri;
# 其他全局配置...
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name your_domain.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
# 其他配置...
}
# 其他 server 块...
}
参考资料:
注:请将示例配置中的 your_domain.com 和证书路径替换为您自己的域名和证书路径。
PS:error_page 可以作为 server 节点的局部配置,也可以放在 http 节点作为全局配置。根据需求选择合适的配置方式,推荐使用全局配置以简化管理。