很多团队做微服务或者内网系统时,习惯把 Nginx 放在最前面统一处理 HTTPS、域名转发、负载均衡这些脏活累活,后面挂着的才是真正跑业务逻辑的 FastAPI 应用。这个架构本身没什么特殊之处,但坑就藏在细节里------你的应用怎么知道自己是被 HTTPS 访问的?如果 Nginx 把 /api/ 这个前缀吃掉了再转发给后端,FastAPI 生成的文档链接和 OpenAPI schema 会不会跟着乱掉?
这篇文章按照官方文档的思路,把 代理转发请求头 和 代理剥离路径前缀 这两个典型场景拆开讲清楚,附上可以直接抄的 Nginx 配置。
🧭 反向代理到底做了什么
先建立一个直观印象。客户端请求先到 Nginx,Nginx 再把请求转给 Uvicorn 跑的 FastAPI 应用,这中间 Nginx 通常会加一些特殊的头信息,告诉后端这个请求原本长什么样。

问题的关键在于,FastAPI 应用本身跑在内网,Uvicorn 收到的是 Nginx 转发过来的普通 HTTP 请求,它完全不知道外部客户端其实用的是 HTTPS,也不知道真实域名是什么。这些信息全靠 Nginx 塞进 X-Forwarded-* 系列头里,应用这边得主动去读才行。
🔐 场景一:只转发头信息(不剥离路径)
这是最常见的情况------Nginx 直接把 example.com 的所有请求原样转发给后端,路径不做任何裁剪。
需要读取哪些头
反向代理通常会设置这三个头:
- X-Forwarded-For 记录客户端真实 IP(因为直连的 IP 其实是 Nginx 自己)
- X-Forwarded-Proto 记录客户端原始用的是
http还是https - X-Forwarded-Host 记录客户端请求时用的域名
但出于安全考虑,Uvicorn 默认不会 信任这些头------毕竟任何客户端都可以伪造 X-Forwarded-Proto: https 来骗你。所以必须显式告诉服务器我这台机器只接受来自可信代理的转发头。
启用转发头的方式
如果你用 FastAPI CLI 启动,加一个参数就行:
bash
fastapi run --forwarded-allow-ips="*"
* 表示信任所有来源 IP 的转发头。生产环境更严谨的做法是只信任 Nginx 所在的内网 IP,比如写成 --forwarded-allow-ips="10.0.0.1"。如果是直接用 Uvicorn 跑(没走 FastAPI CLI),对应的是 --proxy-headers 配合 --forwarded-allow-ips 这两个参数一起用。
HTTPS 重定向不生效的坑
有个很典型的翻车场景。你的路由用了斜杠自动重定向(比如访问 /items 会自动 307 到 /items/),如果转发头没启用,Uvicorn 会以为客户端一直在用 HTTP,重定向的目标 URL 就会变成 http://example.com/items/ 而不是 https://。浏览器再从 HTTPS 页面跳到一个 HTTP 链接,轻则被浏览器拦截提示不安全,重则直接报错。开启转发头之后,Uvicorn 才会正确读取 X-Forwarded-Proto 生成 HTTPS 的重定向地址。
Nginx 端要怎么配
对应的 Nginx location 配置大概是这样:
nginx
server {
listen 443 ssl;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8000;
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;
proxy_set_header X-Forwarded-Host $host;
}
}
这几行 proxy_set_header 就是在给 Uvicorn 送信息,告诉它外部世界看到的其实是 HTTPS、域名是 example.com。
✂️ 场景二:代理剥离了路径前缀
这个场景更麻烦一点,也更常见于一个域名下挂多个服务 的架构。比如你希望 https://example.com/api/ 这个前缀对应到你的 FastAPI 应用,但应用内部的路由定义仍然是干净的 /users、/items,不想在每个路径里都手写 /api 前缀。
root_path 到底解决什么问题
Nginx 在转发之前会把 /api 这段前缀剥掉 ,只把 /users 转发给后端。这就意味着 FastAPI 应用完全不知道自己其实是挂在 /api 下面的------它自己的路由匹配没问题,但生成的 OpenAPI 文档、Swagger UI 里的请求地址、以及任何需要拼完整 URL 的地方都会漏掉这个前缀,直接访问 /api/docs 打开的页面里发请求会打到错误的 /users 而不是 /api/users。
这时候就需要用 root_path 这个机制,本质上是告诉 ASGI 应用你其实是被挂载在这个前缀下面的,生成链接的时候记得带上。
设置 root_path 有几种方式
方式一,启动时通过命令行传参,这是最推荐的方式,因为部署环境(有没有代理、前缀是什么)跟代码本身解耦:
bash
fastapi run --root-path /api
方式二,直接在代码里写死,适合你很确定这个应用永远只会挂在固定前缀下的情况:
python
from fastapi import FastAPI
app = FastAPI(root_path="/api")
方式三,让代理自己传递前缀信息 ,通过设置 X-Forwarded-Prefix 这样的头,一些较新的方案会依赖服务器(Uvicorn)自动解析这个头来设置 root_path,从而不用在启动命令里手写死路径,部署更灵活。
三种方式效果等价,都是让应用内部知道完整访问路径的前缀是什么,剩下的路由匹配逻辑不受影响,改变的只是生成文档、生成链接时的行为。
对应的 Nginx 完整配置
nginx
server {
listen 443 ssl;
server_name example.com;
location /api/ {
# 关键:末尾的斜杠会让 Nginx 剥离 /api 前缀再转发
proxy_pass http://127.0.0.1:8000/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Prefix /api;
}
}
location /api/ 配 proxy_pass http://127.0.0.1:8000/,两边都带斜杠是 Nginx 里裁剪前缀的经典写法------外部访问 /api/users 会被转成内部的 /users。再配合前面提到的 --root-path /api 或者 X-Forwarded-Prefix 头,FastAPI 就能生成正确的完整链接了。
✅ 怎么验证配置生效
改完配置别急着上线,先自己验证一下。
检查当前 root_path 是否被正确读取 ,可以临时加一个调试路由把 request.scope.get("root_path") 打出来看看,官方文档里也是用类似的思路去确认代理传过来的前缀是不是预期值。
打开 Swagger UI 检查文档链接 ,访问 https://example.com/api/docs,点开任意接口试着发送请求,观察实际请求地址是不是带上了 /api 前缀。如果没带,大概率是 root_path 没生效,或者 Nginx 剥离前缀的配置写错了。
查看 OpenAPI JSON 里的 servers 字段 ,正常情况下 FastAPI 会根据 root_path 自动在 openapi.json 里加一条 servers 配置,标注出正确的服务器地址前缀,这也是文档 UI 能拼出正确请求路径的原因。
💡 几个容易踩的坑
汇总一下实际排查中最常遇到的问题,做成一张表方便对照。
| 现象 | 大概率原因 | 解决方向 |
|---|---|---|
| HTTPS 站点里出现 HTTP 跳转 | 转发头没启用,Uvicorn 不知道原始协议是 HTTPS | 加 --forwarded-allow-ips 并配好 X-Forwarded-Proto |
| Swagger UI 里请求地址少了前缀 | 没设置 root_path,或者代理裁剪前缀和 root_path 不匹配 |
用 --root-path 或代码里显式设置,跟 Nginx 的 location 前缀保持一致 |
| 客户端 IP 全部显示成 Nginx 的内网 IP | 没读取 X-Forwarded-For,应用拿到的是代理连接的 IP |
确认 Nginx 配了 X-Forwarded-For,且服务端信任这个来源 |
| 局域网调试时代理头看起来没生效 | 本地测试常用 Traefik 这类工具,行为跟 Nginx 略有差异 | 官方文档专门有一节用 Traefik 本地测试的示例,逻辑是通用的 |
🎯 小结
整件事拆开来看其实就两条线。一条是让应用知道自己被什么协议、什么域名访问过 ,靠的是 X-Forwarded-Proto、X-Forwarded-Host 这些头,配合 Uvicorn 或 FastAPI CLI 的 --forwarded-allow-ips 开关。另一条是让应用知道自己被挂在了哪个路径前缀下面 ,靠的是 root_path 机制,可以通过启动参数、代码硬编码或者代理传递的 X-Forwarded-Prefix 头来设置。
两条线互不干扰,可以按需组合使用------只转发协议信息不剥离路径就只处理第一条,如果还要把应用挂到子路径下就再补上 root_path 这一层配置。整体思路跟其他框架(Django、Flask 加 gunicorn)背后的代理适配逻辑是一致的,理解了这个模型之后换个框架也能照着思路排查。
参考资料
Behind a Proxy - FastAPI Official Documentation. fastapi.tiangolo.com/advanced/be...
How do I make FastAPI URLs include the proxied URL? - Stack Overflow. stackoverflow.com/questions/7...
Supporting both strip-prefix and pass-through proxies from FastAPI - GitHub Discussion. github.com/fastapi/fas...
How to Get the Real Client IP in FastAPI Behind a Reverse Proxy. python.plainenglish.io/how-to-get-...