将 HTTP 流量重定向到 HTTPS 是网站安全的基本要求。本文聚焦 Nginx 中的最佳实践配置。
标准配置(推荐)#
使用两个 server 块分离 HTTP 和 HTTPS 流量:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# HTTP server - 重定向到 HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
# HTTPS server
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# HSTS - 告诉浏览器始终使用 HTTPS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
root /var/www/html;
index index.html;
}
|
return 301 vs rewrite#
两种常见写法:
1
2
3
4
5
|
# 推荐:return 301(更高效)
return 301 https://$host$request_uri;
# 不推荐:rewrite(效率较低)
rewrite ^(.*)$ https://$host$1 permanent;
|
推荐 return 301 的原因:
- 性能更好:直接返回,不经过正则匹配
- 语义更清晰:明确表示永久重定向
- 维护更简单:无需理解正则表达式
HSTS 响应头#
HSTS(HTTP Strict Transport Security)告诉浏览器始终使用 HTTPS:
1
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
参数说明:
| 参数 |
说明 |
max-age=31536000 |
有效期 1 年 |
includeSubDomains |
包含所有子域名 |
preload |
允许加入浏览器预加载列表 |
always |
所有响应都添加此头 |
验证 HSTS#
1
|
curl -sI https://secdoc.jazor.net | grep -i strict-transport
|
输出:
1
|
strict-transport-security: max-age=31536000
|
特殊场景#
保留路径和查询参数#
1
2
3
4
5
|
# 正确:保留完整 URI
return 301 https://$host$request_uri;
# 错误:丢失查询参数
return 301 https://$host;
|
跳过 Let’s Encrypt 验证#
ACME 证书验证需要 HTTP 访问 .well-known 目录:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
server {
listen 80;
server_name example.com;
# Let's Encrypt 验证路径
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# 其他请求重定向到 HTTPS
location / {
return 301 https://$host$request_uri;
}
}
|
非标准端口的重定向#
如果 HTTPS 使用非标准端口(如 8443):
1
2
3
4
5
|
server {
listen 80;
server_name example.com;
return 301 https://$host:8443$request_uri;
}
|
常见错误#
错误 1:忘记 always 参数#
1
2
3
4
5
|
# 错误:只有 200 响应才添加 HSTS
add_header Strict-Transport-Security "max-age=31536000";
# 正确:所有响应都添加
add_header Strict-Transport-Security "max-age=31536000" always;
|
错误 2:重复的 server_name#
1
2
3
4
5
6
7
8
9
10
11
|
# 错误:两个 server 块监听同一个域名和端口
server {
listen 80;
server_name example.com;
# ...
}
server {
listen 80;
server_name example.com; # 冲突!
# ...
}
|
错误 3:SSL 配置在 HTTP 块中#
1
2
3
4
5
6
|
# 错误:HTTP 块不需要 SSL 配置
server {
listen 80;
ssl_certificate /path/to/cert.pem; # 多余
return 301 https://$host$request_uri;
}
|
验证重定向#
测试 HTTP 重定向:
1
|
curl -I http://example.com 2>&1 | head -5
|
期望输出:
1
2
3
|
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://example.com/
|
参考资料#