# nginx 真实 IP 还原 — 生产部署(小白友好版)

> 术语速查:**nginx** = 你这台服务器的"门卫",负责把用户请求分发给后端 / 把静态文件返回给浏览器
> **配置** = nginx 的工作规则,改配置 = 改门卫的工作方式

---

## 我们要做啥(整体目标)

**一句话目标**:`https://itsupport.servyou.com.cn/itadmin/` 之前返回 403(被门卫拦了),原因是门卫把"代理服务器 IP"当成了"用户 IP",而代理 IP 不在白名单里。这次我们改门卫的规则,让它从请求头里读"真实用户 IP"。

**一共 7 个动作**:

| # | 动作 | 大概多久 | 风险 |
|---|---|---|---|
| 1 | PuTTY 连上服务器 | 1 分钟 | ⚪ 无风险 |
| 2 | 备份当前配置 | 几秒 | 🟢 备份原文件,可还原 |
| 3 | 写入 13 行新规则 | 几秒 | 🟡 改配置,但有备份 |
| 4 | 确认写入正确 | 几秒 | ⚪ 只读不写 |
| 5 | 检查配置语法 | 几秒 | ⚪ 只读不写 |
| 6 | 让 nginx 重新读规则 | 1 秒 | 🟡 短暂重载,服务不中断 |
| 7 | 浏览器看效果 | 几秒 | ⚪ 只读 |

**总耗时**:第一次大概 5-10 分钟;熟练了 2 分钟

**整体风险**:🟢 **低** — 每一步都给了"回滚"按钮,改坏了随时能恢复

---

## PuTTY 是啥?在哪儿打开?

**PuTTY** = 一个 SSH 客户端软件,作用是让你从你的 Windows 电脑远程连到公司的 Linux 服务器

**打开方式**:
- 按 `Win 键` → 输入 `putty` → 回车
- 或者开始菜单 → 找到 PuTTY 图标

打开后会看到一个灰底配置界面,我们要填 4 项:

```
┌──────────────────────────────────────┐
│  Host Name (or IP address)           │  ← 填: 10.212.189.210
│  Port                                │  ← 填: 2222
│  Connection type                     │  ← 选: SSH(默认就是)
│  Saved Sessions                      │  ← 填: wecom-bastion(起个名)
└──────────────────────────────────────┘

点 Save 保存 → 点 Open 开始连接
```

连接后会黑底白字,提示 `login as:` → 输入 `sxn` 回车 → 提示 `password:` → 输入你的堡垒机密码(输入时屏幕不显示,正常,输完回车就行)

**注意**:输错密码不会锁账号,直接重新输

---

## 动作 1:PuTTY 连服务器(⚪ 无风险)

> **为啥要连服务器?**:改配置必须在服务器上操作,你 Windows 这边只是"遥控器"

连上堡垒机后,黑底白字会显示一个类似 `sxn@jump-host:~$` 的提示符,说明你已经到堡垒机了。

**决策树**:
```
你现在看到了堡垒机提示符(类似 sxn@jump-host:~$)
   ├─ 是 → 在 PuTTY 里继续输入下面命令
   └─ 否 → 截图发给我,卡哪儿了
```

贴下面的命令(右键 = 粘贴,Enter = 执行):

```bash
# 从堡垒机跳到真正的生产服务器
ssh sxn@10.90.5.110
```

回车后可能要输密码(堡垒机和目标机密码可能不同,试一下你之前用过的那个)

**✅ 成功长这样**:
```text
sxn@prod-server:~$
```

**❌ 失败常见**:
- `Permission denied` → 密码错了,重输
- `Connection timed out` → 网络问题,可能 VPN 没连
- 卡住不动 → 可能需要输 `yes` 确认服务器指纹,看到 `(yes/no/[fingerprint])?` 就输 `yes` 回车

---

## 动作 2:备份当前配置(🟢 低风险,改坏了能还原)

> **为啥要备份?**:运维铁律 — **改任何东西之前先备份**,这样改坏了能用备份还原,不会把生产搞挂

```bash
# 进入 nginx 配置所在目录
cd /opt/wecom-it-desk/nginx

# 复制一份当前配置,文件名带当前时间(分),方便区分
sudo cp nginx.conf nginx.conf.bak-$(date +%H%M)

# 列出所有备份文件,确认刚才那行成功
ls -la nginx.conf.bak-*
```

**为啥用 `$(date +%H%M)`?**:这个写法会自动拼上当前时间(比如 1430 表示 14:30),每次备份文件名都不一样,不会覆盖之前的备份

**✅ 成功长这样**:
```text
-rw-r--r-- 1 root root 4821 Jun 15 14:30 nginx.conf.bak-1430
```

**❌ 失败常见**:
- `cp: cannot stat 'nginx.conf'` → 当前不在 nginx 目录,先 `cd /opt/wecom-it-desk/nginx` 进去
- `Permission denied` → 缺 `sudo`,命令前面加 `sudo` 重试

---

## 动作 3:写入 13 行新规则(🟡 中风险,但有备份兜底)

> **写入啥?**:13 行 nginx 配置,告诉 nginx"从请求头 X-Forwarded-For 里读真实用户 IP"
>
> **为啥要这样做?**:用户通过公司 WAF/堡垒机访问,WAF 会把真实 IP 放在 `X-Forwarded-For` 请求头里,但 nginx 默认只看直连 IP,所以才误判 403

**重要**:把下面**从 `cat > /tmp/patch.py` 到 `PYEOF`** 的**整段**一次性粘贴进 PuTTY(右键 = 粘贴)。整段会作为一条命令执行。

```bash
# 创建一个 python 脚本到 /tmp/patch.py
cat > /tmp/patch.py << 'PYEOF'
fp = '/opt/wecom-it-desk/nginx/nginx.conf'
with open(fp) as f:
    c = f.read()
patch = '''
    # ------------------------------------------------------------------
    # 真实 IP 还原(2026-06-15 v0.5.1 修复)
    # ------------------------------------------------------------------
    set_real_ip_from 10.0.0.0/8;
    set_real_ip_from 172.16.0.0/12;
    set_real_ip_from 192.168.0.0/16;
    set_real_ip_from 10.212.0.0/16;
    real_ip_header    X-Forwarded-For;
    real_ip_recursive on;
'''
old = 'error_log  /var/log/nginx/error.log warn;'
new = old + patch
new_c = c.replace(old, new, 1)
with open(fp, 'w') as f:
    f.write(new_c)
print('patched, +{} bytes'.format(len(new_c) - len(c)))
PYEOF

# 运行这个 python 脚本,它会自动把上面那 13 行插入到 nginx.conf
sudo python3 /tmp/patch.py
```

**术语解释**:
- `cat > /tmp/patch.py` → 创建一个文件,内容是后面所有内容
- `<< 'PYEOF' ... PYEOF` → 这种写法叫 **heredoc**(直译"这里是文档"),作用是把多行文字原样写入文件
- `sudo` → 以管理员身份运行(改系统文件需要权限)

**✅ 成功长这样**:
```text
patched, +492 bytes
```

**❌ 失败常见**:
- `Permission denied` → 缺 `sudo`,或者 nginx.conf 不存在
- `NameError: name 'fp' is not defined` → heredoc 没贴完整,最末尾的 `PYEOF` 没贴上
- 没任何输出 → python 没运行,看光标有没有新行,可能没回车

---

## 动作 4:确认写入正确(⚪ 无风险,只读)

> **为啥要确认?**:虽然脚本说写入了,但**人眼看到才真的算**。这步只读不写,放心跑

```bash
# 在 nginx.conf 里搜索"真实 IP 还原"关键字,并显示后面 13 行
sudo grep -A 13 "真实 IP 还原" /opt/wecom-it-desk/nginx/nginx.conf
```

**✅ 成功长这样**(应该看到完整 13 行):
```nginx
    # 真实 IP 还原(2026-06-15 v0.5.1 修复)
    # ------------------------------------------------------------------
    set_real_ip_from 10.0.0.0/8;
    set_real_ip_from 172.16.0.0/12;
    set_real_ip_from 192.168.0.0/16;
    set_real_ip_from 10.212.0.0/16;
    real_ip_header    X-Forwarded-For;
    real_ip_recursive on;
```

**❌ 失败**:
- 啥也没输出 → 写入失败,回到动作 3 重做
- 只输出一两行 → heredoc 没贴全,需要回滚后重来

---

## 动作 5:检查配置语法(⚪ 无风险,只读不执行)

> **为啥要检查?**:这个命令 nginx 会"假装"按新配置启动,只检查语法,不会真的重启。**通过 = 配置写得对,放心用;不通过 = 写得有问题,继续走会出问题**

```bash
# 在 nginx 容器(就是跑 nginx 服务的那个小 Linux)内,做配置语法检查
docker compose exec nginx nginx -t
```

**术语解释**:
- `docker compose` → 管理这台服务器上所有"容器"的命令
- `exec` → "钻进"某个容器里执行命令
- `nginx -t` → nginx 自带的"语法检查"工具(全称 `--test`)

**✅ 成功长这样**:
```text
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
```

**❌ 失败**(`test is successful` 没出现):
- `unexpected "}"` / `unknown directive` → 写错字了,回去动作 4 看看哪里对不上
- **直接停下,不要继续** → 复制错误信息贴回给我

---

## 动作 6:让 nginx 重新读规则(🟡 中风险,但服务不中断)

> **"重新读"是啥意思?**:nginx 现在用的还是旧配置,我们让 nginx 不用重启(不会断服务)就把新配置加载进来。这个动作叫"热加载"或 "reload"
>
> **会断网吗?**:不会,reload 是无缝的,用户那边无感知

```bash
# 通知 nginx 容器内的 master 进程重新读配置
docker compose exec nginx nginx -s reload
```

**术语解释**:`-s reload` = 发信号(英文 signal)给 nginx,告诉它"重读配置"

**✅ 成功长这样**(没报错即成功):
```text
2026/06/15 14:35:12 [notice] 1#1: signal process started
```

**❌ 失败**:
- `nginx: [error]` 开头 → 配置没通过,回去动作 5 看哪里没对
- 啥也没输出 → 命令没执行,看光标位置

---

## 动作 7:浏览器看效果(⚪ 无风险)

**为啥这步是浏览器而不是 curl?**:curl 看响应头,浏览器看真实页面。**人眼看到才作数**

**操作步骤**:
1. 打开浏览器
2. **开隐身模式**(`Ctrl + Shift + N`,Chrome / Edge 都是这个快捷键)
   - **为啥要隐身?**:隐身模式不读本地缓存,看到的就是 nginx **当下**返回的
3. 地址栏输入 `https://itsupport.servyou.com.cn/itadmin/`
4. 按回车

**✅ 成功长这样**:
- 页面正常显示
- 按 `F12` 打开开发者工具 → `Network` 选项卡 → 顶部那一行状态码是 **200**(不是 403)

**❌ 失败**:
- 仍然是 403 → 见下面"如果还是 403"段
- 502 / 504 → nginx 后面那个服务挂了,贴错误给我
- 页面打不开(连接被拒) → DNS 没配,联系 IT 运维

---

## 如果还是 403 — 看 WAF 出口 IP(诊断)

> **啥是 WAF?**:公司部署在 nginx 前面的"统一入口",所有用户请求先经过 WAF 再到 nginx。WAF 自己的 IP 不一定在你写的 4 段内网里,所以还得加

```bash
# 看 nginx 最后 20 条访问日志,找 $remote_addr 是不是 WAF 的 IP
docker compose exec nginx tail -20 /var/log/nginx/access.log
```

**日志长这样**:
```text
10.80.5.123 - - [15/Jun/2026:14:35:45 +0800] "GET /itadmin/ HTTP/1.1" 403 ...
^^^^^^^
这就是 $remote_addr
```

把那个 IP 数字(比如 `10.80.5.123`)贴回给我,我会:
1. 给你追加一行 `set_real_ip_from 10.80.5.123;`
2. 让你重跑动作 5 + 动作 6

---

## 如果改坏了 — 回滚(啥时候都能用)

> **啥时候用?**:任何一个动作出问题,你都可以直接回滚到动作 2 备份的版本

```bash
# 列出所有备份,挑最近的一个
ls -la /opt/wecom-it-desk/nginx/nginx.conf.bak-*
```

```bash
# 用最近那个备份覆盖当前配置(把 1430 换成上面列出的真实时间)
sudo cp /opt/wecom-it-desk/nginx/nginx.conf.bak-1430 /opt/wecom-it-desk/nginx/nginx.conf
```

```bash
# 重新加载回滚后的配置
docker compose exec nginx nginx -s reload
```

回滚后页面应该回到改之前的状态(403 回来),说明回滚成功

---

## 一张图看懂流程

```
PuTTY 连服务器
    │
    ▼
备份原配置
    │
    ▼
写入 13 行新规则
    │
    ▼
确认写入正确 ──→ ❌ 不对 ──→ 重做写入 / 回滚
    │ ✅
    ▼
检查配置语法 ──→ ❌ 语法错 ──→ 复制错误贴回给我,不要继续
    │ ✅
    ▼
重载 nginx ─────→ ❌ 报错 ──→ 检查容器状态 / 找 Claude
    │ ✅
    ▼
浏览器看效果 ──→ ❌ 还是 403 ──→ 看 WAF 出口 IP,贴给 Claude
    │ ✅
    ▼
🎉 完成
```

---

## 我建议你第一次做

**第一次建议**:动作 1 → 动作 2 → **停一下,截图发给我** → 我确认备份成功 → 你再继续动作 3 之后

**熟练了以后**:一口气跑完动作 1-7,中间不打断

---

## 关联

- 评审报告:`review-p0-security-2026-06-14.md` P0-3
- 待办:`ip-whitelist-trust-proxies-todo.md` — v1.0 前必须收窄 4 段 → 4 个 IP
- 本地配置:`deploy-server/nginx/nginx.conf`(已包含 patch,下次重打包自动带)
- 服务器 IP 变更:`project-production-server-ip-2026-06-15.md` — 10.80.0.136 已下线,用 10.90.5.110
- 客户端约束:`feedback-putty-not-openssh.md` — 用 PuTTY,不用 `ssh -J`
- 命令行规范:`feedback-cmd-step-by-step.md` — 每行一条 + 中文注释
- 小白引导规范:`feedback-beginner-friendly-guide.md` — 讲清目标+风险、术语解释、出错兜底
