nginx 配置长跑(中):HTTPS、转发、跨域与泛域名的实战套路

关键词:nginx / HTTPS / 反向代理 / 跨域 / rewrite / 永久跳转 / 泛二级域名 / resolver / server_names_hash_bucket_size

引言:当域名、证书和转发规则「搅在一起」

有个同事接了个看似简单的需求:

  • http://www.test.com 转到内部服务 127.0.0.1:6666
  • 顺带上个 HTTPS,给前端 API 用;
  • 再加一下 CORS,方便本地调试。

结果一顿操作之后:

  • HTTP 可以访问,但被浏览器标红「不安全」;
  • HTTPS 能连上,但接口 502,日志里全是「no resolver defined」之类的提示;
  • 跨域头随缘返回,有时候 OPTIONS 直接 403。

域名、证书、转发、跨域,这些看起来零散的需求,最后都会聚在 nginx 这一层。

这一篇,我们沿着一个「入口流量」的视角,把 HTTPS、反向代理、CORS、永久跳转、泛二级域名这些高频场景串成一条线:

让你对「nginx 作为流量入口」有一个完整、可落地的实战套路。


一、HTTPS:让 TLS 仅仅是「多写几行配置」

HTTPS 本身的协议细节可以很复杂,但在 nginx 里,最基础的启用方式其实就几行:

nginx 复制代码
server {
    listen       443 ssl;
    server_name  example.com;

    ssl_certificate      cert.crt;
    ssl_certificate_key  cert.key;
    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;
    ssl_ciphers          HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    location / {
        root   html;
        index  index.html index.htm;
    }
}

几个要点:

  • listen 443 ssl:没有 ssl 关键字,nginx 不会以 TLS 模式监听;
  • ssl_certificate / ssl_certificate_key:公钥证书 + 私钥;
  • 其余如 session_cacheciphers 是典型的「按安全基线抄」配置。

工程实践里,HTTPS 真正的难点往往不是「写哪几行」,

而是证书申请、更新自动化,以及如何把 HTTP 流量低成本地引到 HTTPS 上。


二、反向代理:把前端流量稳稳送到后端

最常见的 nginx 用法,依旧是反向代理:

把「对外的域名」映射到「内部的服务」上。

1. 标准反向代理模板

例如,把 http://www.test.com 转发到本地 127.0.0.1:6666

nginx 复制代码
server {
    listen       80;
    server_name  www.test.com;

    location / {
        # 透传真实客户端 IP
        proxy_set_header X-Real-IP       $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host            $http_host;

        # 代理连接超时设置为 10 秒,默认 60 秒太长
        proxy_connect_timeout 10;

        proxy_pass http://127.0.0.1:6666;
    }
}

几个实践建议:

  • 不要用 localhost,对于某些配置(尤其结合变量)会踩到解析坑,IP 更稳妥;
  • 一定要设置 proxy_connect_timeout,否则后端服务半死不活时,前端会等到怀疑人生;
  • Host 透传给后端,很多应用会根据 Host 做路由或多租户判断。

2. 再加一层:入口 + BFF + 后端

在真实生产环境中,nginx 一般只是「最外面那层」:

  • nginx 把流量打到 BFF(Node.js / Nest / Hono);
  • BFF 再去调内部服务(Go / Java / 微服务集群)。

nginx 里这层的目标只有两个字:干净

  • 不做业务逻辑,只做协议层的事情:TLS、限流、基本鉴权、CORS、流量分流;
  • 其余复杂逻辑交给 BFF / 网关层处理。

三、CORS:把跨域放在入口统一处理

前端最常见的一句抱怨:

「明明后端已经在接口里加了 CORS,怎么浏览器还是说跨域?」

如果你的 nginx 在最外层挡着,而后端只给应用服务加了 CORS,很可能出现:

  • 预检(OPTIONS)被 nginx 拦下了,根本没到后端;
  • 或者 nginx 自己返回了 4xx/5xx,却没带任何 CORS 头。

最简单的解决方式,就是直接在 nginx 入口层加 CORS 头:

nginx 复制代码
server {
    listen       80;
    server_name  www.test.com;

    location / {
        add_header 'Access-Control-Allow-Origin' '*' always;
        add_header 'Access-Control-Allow-Headers' 'Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With' always;
        add_header 'Access-Control-Allow-Methods' 'POST, GET, OPTIONS' always;

        # 如果需要处理 OPTIONS 预检,可以单独拦一条
        if ($request_method = OPTIONS) {
            return 204;
        }

        proxy_pass http://127.0.0.1:6666;
    }
}

要点:

  • always 可以确保即便返回 4xx/5xx 也带上 CORS 头;
  • 尽可能在入口层统一处理 CORS,而不是每个后端服务都写一遍。

CORS 这种「协议层」的东西,

放在入口统一做,比撒在各个业务代码里要可靠得多。


四、永久跳转:把「历史域名」优雅收束起来

历史域名、带不带 www、从 HTTP 到 HTTPS,这些需求本质上都是「跳转」问题。

1. 从带 www 跳到不带:www → 非 www

例如,将 http://www.wangmiaozero.cn 永久跳转到 http://wangmiaozero.cn,并丢弃路径

nginx 复制代码
server {
    listen       80;
    server_name  www.wangmiaozero.cn;

    location / {
        rewrite ^(.*) http://wangmiaozero.cn permanent;
    }
}

这里的 permanent = 301,浏览器会长期记住。

2. 从 HTTP + www 跳到 HTTPS + 非 www

如果希望:

  • http://www.wangmiaozero.cn/foo
    → 永久跳转到
    https://wangmiaozero.cn/foo (保留路径):
nginx 复制代码
server {
    listen       80;
    server_name  www.wangmiaozero.cn;

    location / {
        rewrite ^(.*) https://wangmiaozero.cn$document_uri permanent;
    }
}

注意:

  • $document_uri 是「不含查询参数」的路径;
  • 如果需要连 query 一起保留,可以用 $request_uri

推荐做法是:
所有 HTTP 流量统一跳到 HTTPS + 归一后的主域名

用户访问路径尽量保持不变。


五、泛二级域名:一套配置搞定「N 个子站点」

当你有一堆类似:

  • tenant-a.example.com
  • tenant-b.example.com
  • foo.internal.example.com

的需求时,就轮到「泛二级域名」登场了。

1. 基于转发的泛二级域名:不同子域名 → 不同后端路径

目标:
http://*.wangmiaozero.cn/ 统一转发到 http://127.0.0.1:8080/*/

示例配置:

nginx 复制代码
http {
    include       mime.types;
    default_type  application/octet-stream;

    # 重要:当 proxy_pass 里用变量构造地址时,必须配置 resolver
    resolver 8.8.8.8;

    sendfile        on;
    keepalive_timeout  65;

    # 某些特殊域名单独写在前面
    server {
        listen       80;
        server_name  www.wangmiaozero.cn www.test.com test.com;

        location / {
            rewrite ^(.*) http://wangmiaozero.cn permanent;
        }
    }

    server {
        listen       80;
        server_name  demo.wangmiaozero.cn;

        location / {
            root   D:/Workspace/github/demo;
            index  index.html index.jsp;
        }
    }

    # 泛二级域名:匹配 *.wangmiaozero.cn
    server {
        listen       80;
        server_name  ~^(.+)?\.wangmiaozero\.cn$;

        location / {
            # 注意这里要用 127.0.0.1,不要用 localhost
            proxy_pass http://127.0.0.1:8080/$1$request_uri;
        }
    }
}

说明:

  • server_name ~^(.+)?\.wangmiaozero\.cn$ 用正则把子域名拿出来;
  • $1 就是子域名,可以直接拼在转发路径里;
  • 前面可以加若干「特殊子域名」的 server,nginx 会先命中那些更具体的配置。

2. 基于目录的泛二级域名:不同子域名 → 不同静态目录

如果你想把 a.wangmiaozero.cn 映射到 D:/Workspace/github/test/a
b.wangmiaozero.cn 映射到 .../test/b,可以这样写:

nginx 复制代码
server {
    listen       80;
    server_name  ~^(.+)?\.wangmiaozero\.cn;

    location / {
        root   D:/Workspace/github/test/$1;
        index  index.html index.jsp;
    }
}

这套配置对「多租户静态站点」特别友好:

  • 每个子域名一个目录;
  • 上线只需要把对应目录挂上去。

3. no resolver defined to resolve localhost:别再踩一次的坑

当你在 proxy_pass 里用变量拼接地址时,例如:

nginx 复制代码
proxy_pass http://$http_host$request_uri;

如果没有配置 resolver,nginx 会报:

text 复制代码
no resolver defined to resolve localhost

这就是前面配置里那句:

nginx 复制代码
resolver 8.8.8.8;

存在的意义:当 nginx 需要在运行时解析一个域名(通过变量构造时),必须有 DNS 可以用。


六、server_names_hash_bucket_size:域名多了,表要加大

当你在一个 http {} 里写了大量 server_name 时,

有可能在 reload 时看到这样的错误:

text 复制代码
could not build the server_names_hash, you should increase server_names_hash_bucket_size: 64

大意就是:
「存域名的 hash 桶不够大了,请你调大点」。

解决办法也很简单,在 http {} 里加一行:

nginx 复制代码
http {
    include       mime.types;
    default_type  application/octet-stream;

    # 域名过多时需要配置这个参数,一般是 2 的指数,比如 512
    server_names_hash_bucket_size 512;
}

工程视角下,这类「hash bucket」报错的本质,

就是 nginx 在帮你暗戳戳管理一张「域名路由表」,

当这张表太挤时,你需要多给它一点空间。


七、路径追加规则:root / proxy_pass 到底怎么拼 URL?

路径拼接规则,是另一个高频「凭感觉写」然后翻车的地方。

看一段典型配置:

nginx 复制代码
server {
    listen       80;
    server_name  www.test.com;

    location / {
        root  E:/Workspace/test/htdocs/;
        index index.html;
    }

    location /test_root {
        # 访问 www.test.com/test_root/aaa/index.html
        # 实际访问的是 htdocs/test_root/aaa/index.html
        root  E:/Workspace/test/htdocs/;
        index index.html;
    }

    location /test_proxy1 {
        # 访问 www.test.com/test_proxy1/aaa/index.html
        # 实际访问 www.proxy.com/test_proxy1/aaa/index.html
        proxy_pass http://www.proxy.com;
        proxy_buffering off;
    }

    location /test_proxy2 {
        # 访问 www.test.com/test_proxy2/aaa/index.html
        # 实际访问 www.proxy.com//aaa/index.html
        proxy_pass http://www.proxy.com/;
        proxy_buffering off;
    }

    location /test_proxy3 {
        # 访问 www.test.com/test_proxy3/aaa/index.html
        # 实际访问 www.proxy.com/bbb/aaa/index.html
        proxy_pass http://www.proxy.com/bbb;
        proxy_buffering off;
    }
}

可以总结出几个实用结论:

  • root + location

    • location /foo + root /var/www → 实际路径是 /var/www/foo/...
    • 不管 foo 后面写不写 /location 里的那段都会被拼到真实路径上。
  • proxy_pass

    • 以不带 / 结尾的形式(如 http://www.proxy.com):会保留原始路径前缀;
    • / 或子路径结尾(如 http://www.proxy.com/http://www.proxy.com/bbb):会替换掉 location 的那一段前缀。

简单记忆:
root 是「前缀 + 原路径」,proxy_pass 则取决于你最后写没写 /


八、nginx 做代理服务器:能用,但别太依赖

理论上,你甚至可以用 nginx 搭一个通用 HTTP 代理:

nginx 复制代码
# 代理服务器
server {
    listen   6587;
    resolver 8.8.8.8;

    location / {
        proxy_pass http://$http_host$request_uri;
        #allow 127.0.0.1;
        #deny all;
    }
}

然后把浏览器的代理设置成 192.168.1.111:6587(假设 nginx 在这台机器)。

这玩意儿能跑,但问题也很多:

  • 不支持 HTTPS 透明转发;
  • 对某些协议 / 头部支持不完善;
  • 稳定性和观测能力都比专业代理差太多。

大部分场景下,更推荐用:

  • 专门的 HTTP/HTTPS 代理(mitmproxy、Squid、Charles 等);
  • 或者在网关 / sidecar 层做统一代理逻辑。

把 nginx 当万能代理工具,只是「能用」,

把它当成清晰的流量入口,才是「好用」的正确打开方式。


九、总结:让 nginx 真正成为「流量入口的工程设施」

  • HTTPS 层面,nginx 的目标是:让证书接入和 HTTP→HTTPS 跳转变成标准套路
  • 反向代理层面,它是最外面那层「干净的流量路由」,只做协议相关的事情;
  • 跨域层面,在入口统一加 CORS,比在每个后端服务里散着写要可靠;
  • 域名与泛域名层面,通过正则 server_name + resolver + hash bucket,可以优雅地管理一堆历史域名与子域名;
  • 路径与转发层面,搞清楚 rootproxy_pass 的拼接规则,就能少掉一大半「怎么转发到奇怪路径」的 Bug。

上篇我们解决了「请求到底会命中哪条 location」的问题,

这一篇把目光拉到流量入口,讲清了域名、证书与转发背后的工程套路。

下一篇(下篇),我们会从「nginx 的全局变量与调试思路」出发,

讨论如何在复杂系统里,把 nginx 从「黑盒」变成真正可观测、可诊断的一部分。

相关推荐
辭七七38 分钟前
在openEuler上用Docker部署RabbitMQ消息队列实战
后端
用户05248324917642 分钟前
在 openEuler 上快速体验 PyTorch 深度学习
后端
稚辉君.MCA_P8_Java42 分钟前
Gemini永久会员 VB返回最长有效子串长度
数据结构·后端·算法
卡皮巴拉_43 分钟前
周末实战:我用 Trae Solo 写了一个日志解析 CLI 工具
后端
星释43 分钟前
Rust 练习册 106:太空年龄计算器与宏的魔法
开发语言·后端·rust
用户05248324917643 分钟前
在 openEuler 上体验 JAX 高性能计算框架
后端
用户67361323685544 分钟前
openEuler 高效 AI 数据管道
后端
热爱跑步的恒川44 分钟前
OpenEuler上Docker Compose部署MySQL数据库
后端
侠客在xingkeit家top1 小时前
全技术栈企业级性能调优万花筒
后端