将 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/

参考资料