0%

我在实际项目中是怎么用 Nginx 的

这几年做项目时,Nginx 基本一直都在。很多时候它不只是一个“转发请求的入口”,而是前端、后端、内外网边界之间那个最容易下手、也最容易快速见效的一层。

虽然这两年越来越多的场景开始用云原生方案,比如 Envoy Proxy 之类的组件来替代部分 Nginx 的工作,但回头看下来,很多问题最早还真就是靠 Nginx 先顶上、先跑通的。

所以想趁着还有印象,整理一下这些年在项目里实际用过的几种 Nginx 用法。它们不一定都算“标准答案”,但在真实环境里,确实都解决过问题。

1. 代理 SMTP 服务,让内网服务也能发邮件

这个场景在客户环境里挺常见的。

有些系统部署在内网,业务服务本身不能直接访问外部网络,但偏偏又需要发邮件。长期来看,当然还是应该让对应节点具备访问 SMTP 服务器的能力;但如果只是临时救急,或者短期内没法推动网络策略调整,也可以让 Nginx 先做一层 TCP 代理。

配置大概像这样:

1
2
3
4
5
6
7
8
load_module /usr/lib/nginx/modules/ngx_stream_module.so;

stream {
server {
listen 1587;
proxy_pass mail.example.com:587;
}
}

这样一来,内网服务就不需要直接连外部 SMTP 服务器了,只要把 Nginx 所在节点 IP + 1587 当成 SMTP 地址使用即可。

这个方案的核心点在于:这里走的不是 HTTP 代理,而是 stream 模块提供的四层转发能力。

如果你用的是 Ubuntu 里通过 apt 安装的 Nginx,通常 ngx_stream_module 都已经编译进去了;如果是自己编译 Nginx,就要确认编译参数里带了 --with-stream

2. 代理企业微信、钉钉、飞书这类外部通知接口

和发邮件是同一类问题。

很多部署在内网的服务,不只是要发邮件,还要给企业微信、钉钉、飞书这类企业通信工具发通知消息。服务本身出不了网时,也可以把这些请求统一转到一台可访问外网的 Nginx 节点上。

以企业微信机器人为例,它的 Webhook 地址一般长这样:

1
https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=3e8b437b-419f-46b2-9495-391cf17a6719

如果内网服务不能直接访问外网,可以在 Nginx 上做一个反向代理:

1
2
3
4
5
6
7
8
server {
listen 80;
server_name wechat-proxy;

location /cgi-bin/webhook {
proxy_pass https://qyapi.weixin.qq.com;
}
}

这样,内网服务发送消息时,就可以把原始地址替换成类似下面这个内部地址:

1
https://wechat-proxy/cgi-bin/webhook/send?key=3e8b437b-419f-46b2-9495-391cf17a6719

本质上,这还是“把不能出网的服务请求,借道一台能出网的节点发出去”。

这种做法的好处是落地快,应用侧改动也不大;缺点也很明显,它更像一种工程上的折中方案。如果后面这类依赖越来越多,还是应该把统一出口、代理策略或者网络规划做得更正式一些。

3. 用 auth_request 给静态资源和内部服务补一层认证

还有一种情况也挺常见:有些内容本身很简单,比如 API 文档、模板文件、安装包,甚至是某个前端直接托管出来的小工具页。开发时大家往往觉得“这就是个静态文件”,于是直接通过 Nginx 暴露了。

但到了客户环境里,这类路径经常会被安全扫描盯上。哪怕文件本身不敏感,只要是“未认证即可访问”,往往就容易被判定为风险项。

这时候,auth_request 就很实用了。它可以让 Nginx 在真正返回资源之前,先去请求一个认证接口,认证通过后再继续返回内容。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
location /docs {
auth_request /auth;
proxy_pass http://frontend/docs;
proxy_set_header Host $host;
error_page 401 = @error401;
}

location = /auth {
proxy_pass http://backend/auth;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}

location @error401 {
return 302 /login;
}

这个思路的好处是,后端只需要提供一个专门的鉴权接口,Nginx 就可以把认证逻辑挂到多个入口前面。

除了静态资源,这个方案也很适合那些“不想自己重复造认证逻辑”的内部服务。服务本身只管功能,对外访问统一经过 Nginx,由 Nginx 先做认证,再把请求放进去。

另外,认证接口还可以顺便返回一些认证后的上下文信息,再由 Nginx 透传给后端使用。比如认证接口返回了用户名:

1
2
auth_request_set $username $upstream_http_x_username;
proxy_set_header X-Username $username;

这样后面的服务就可以直接读取 X-Username,不需要自己再重复解析一遍登录态。

这些年接过不少客户环境里的单点登录需求,最大的感受其实不是“标准方案很多”,而是“真正完全按标准落地的并不多”。

很多项目一到集成阶段,认证链路里总会被加上各种定制要求。理论上可以讲 OAuth2、OIDC、SAML,但真到现场,往往还是得围着客户现有流程做适配。

在这种背景下,我们有时会采用一个比较直接的办法:让系统后端先为指定用户创建好登录态,再通过 Nginx 返回 Set-Cookie,把 session 强行种到当前域下,达到“点一个链接就登录”的效果。

假设系统用 session_key 这个 Cookie 保存登录态,那么可以在 Nginx 里加一个类似的入口:

1
2
3
4
location ~ ^/set-session/(?<session_key>\w+)$ {
add_header Set-Cookie "session_key=$session_key; HttpOnly; Max-Age=21600; Path=/; SameSite=Lax";
return 302 /;
}

配合方式也很直接:

  1. 业务后端提供一个管理员接口,用来为指定用户创建 session。
  2. 这个接口返回生成好的 session_key
  3. 调用方把 session_key 拼到 /set-session/<session_key> 这个地址里。
  4. 用户浏览器访问该地址后,Nginx 回写 Cookie,并跳转到首页。

例如系统地址是:

1
http://example.com

如果生成的 session_key 是:

1
d79769307e7e443ca7abfd780e89217e

那么最终访问的地址就会是:

1
http://example.com/set-session/d79769307e7e443ca7abfd780e89217e

浏览器访问后,就会在当前域下写入对应的 session_key,随后跳回首页,从用户视角看起来就像一次“无感登录”。

这套方式当然谈不上通用,也不一定适合所有系统,但在一些强定制、强集成、时间又比较紧的项目里,确实很好用。它最大的价值不在于优雅,而在于实现简单、改动集中,而且足够可控。

最后

回头看这些例子,会发现 Nginx 在项目里真正有价值的地方,往往不是“它能不能把请求转过去”,而是它能不能在系统边界上帮你快速补上一层能力。

有时候是补网络出口,有时候是补认证,有时候是补一段不那么标准、但又必须落地的集成逻辑。

所以我现在对 Nginx 的感觉一直挺朴素的:它未必是每个问题的最终解法,但在很多实际项目里,它确实是那个最先把事情顶起来的工具。

如果我的文字帮到了您,那么可不可以请我喝罐可乐?

欢迎关注我的其它发布渠道