Nginx HTTP 强制跳转 HTTPS 全攻略:跳转、代理与排错实战


Nginx HTTP 强制跳转 HTTPS 全攻略:跳转、代理与排错实战

HTTPS 早已是网站标配,但"部署了证书"和"所有流量都走 HTTPS"是两回事。用户习惯性输入域名不带协议,搜索引擎收录的旧链接还是 http:// 前缀,如果没做好强制跳转,就会出现一部分流量裸奔在 80 端口上。本文介绍 Nginx 强制 HTTPS 的三种主流方案、常见坑点与排错方法。

一、方案一:return 301 全站跳转(最推荐)

最简单也最稳妥的写法,为 80 端口单独建一个 server 块,统一返回 301:

server {
    listen 80;
    server_name www.example.com example.com;
    # 全站强制跳转 HTTPS
    return 301 https://$host$request_uri;
}

注意变量选择:

$host:取请求的 Host 头,保留用户访问的域名,多域名共用一台服务器时不会跳错域。

$request_uri:保留原始路径与查询参数,跳转后 URL 不丢参数。

这样配置后,http://example.com/article?id=1 会 301 到 https://example.com/article?id=1,SEO 权重无损迁移。

二、方案二:if + rewrite 写法与坑

很多旧教程推荐 if ($scheme != https) { rewrite ... },注意两点:

server {
    listen 80;
    server_name example.com;

    # 不推荐但常见的写法,注意 if 指令的上下文限制
    if ($scheme != "https") {
        return 301 https://$host$request_uri;
    }
}

坑点一:if 是 Nginx 中的"邪恶指令",在 location 内使用 if 有几率出现不可预期行为。上面的写法本质上等价于方案一,既然都要单独开 80 端口 server 块,直接 return 更清晰。

坑点二:如果生产环境反代了多个站点共用 80 端口,且没有按域名区分,写成 return 301 会把所有域名的请求都跳到一个地址。务必保证每个域名有自己的 server 块。

三、方案三:仅对特定路径强制 HTTPS

有些站点部分资源想继续走 HTTP(如图片、静态下载),可以只对敏感路径做跳转:

server {
    listen 80;
    server_name example.com;

    # 登录与支付相关路径强制 HTTPS
    location ~ ^/(login|admin|pay|api/) {
        return 301 https://$host$request_uri;
    }

    # 其余路径正常处理
    location / {
        root /var/www/example;
    }
}

四、HSTS:让浏览器"记住"HTTPS

301 跳转只能管住本次访问,HSTS(HTTP Strict Transport Security)让浏览器在指定时间内强制走 HTTPS,直接从源头杜绝裸奔请求。在 443 的 server 块中加响应头:

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate     /etc/nginx/ssl/example.com.pem;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}

参数含义:max-age=31536000 表示一年内浏览器默认使用 HTTPS;includeSubDomains 覆盖所有子域名。首次启用建议先不加 includeSubDomains,观察一周确认子域名证书都正常后再加上,否则子域名证书缺失会导致用户无法访问。

五、HTTPS 站点常见坑点与排错

1. 证书链不完整,浏览器报"连接不安全"

nginx -t 能通过但浏览器仍报错,多半是证书链缺中间证书。用在线工具或 curl 检查:

# 检查证书链是否完整
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | grep -E "s:|i:"

# nginx 配置语法检查
nginx -t
# 重载配置
systemctl reload nginx

2. 部分接口仍是 http 请求

前端代码或第三方回调中写死了 http:// 地址。跳转后若出现"Mixed Content"(混合内容)报错,说明页面在 HTTPS 下加载了 HTTP 资源。可用 CSP 兜底:

add_header Content-Security-Policy "upgrade-insecure-requests" always;

这会让浏览器把页面内的 http 资源自动升级为 https 请求。

3. 反代场景下 HTTPS 穿透问题

Nginx 做反向代理时,后端收到的是来自本机的 HTTP 请求,若后端需要感知客户端协议,必须透传 X-Forwarded-Proto:

location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

后端(如某些框架)拿到 X-Forwarded-Proto: https 才会正确生成 https 链接,否则生成一堆 http 内链导致"跳了又跳"。

4. 301 与 302 的选择

强制跳转必须用 301(永久),用 302 会导致搜索引擎反复抓取 http 地址,权重难以合并。检查当前状态码:

curl -I http://example.com/ | head -5
# HTTP/1.1 301 Moved Permanently
# Location: https://example.com/

5. 测试环境证书信任

本地测试常因自签证书报错,可在 curl 时临时跳过校验,仅限测试:

curl -k -I https://127.0.0.1:443/ -H "Host: example.com"

六、完整配置模板

一套"一劳永逸"的强制 HTTPS 配置模板如下:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # HTTP 一律 301 到 HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

    ssl_certificate     /etc/nginx/ssl/example.com.pem;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_session_cache shared:SSL:10m;

    # HSTS
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # 应用反代
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

保存后依次执行:

sudo nginx -t
sudo systemctl reload nginx
curl -I http://example.com/ | head -5   # 应返回 301 指向 https

七、总结

强制 HTTPS 的正确姿势是:80 端口独立 server 块 return 301(保留域名与参数)→ 443 开启 HSTS 让浏览器长期记忆 → 反代透传 X-Forwarded-Proto → 用 curl -I 验证状态码与 Location。做到这四步,你的站点流量就彻底告别裸奔了。


手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

MySQL 主从复制实战:binlog、GTID 与故障切换排错全指南

MySQL 数据库备份实战:从 mysqldump 到异地同步的完整方案

评 论
更换验证码