Uvicorn+FastAPI本地部署指南:解决外部访问与端口难题
2026/9/23 3:20:27 网站建设 项目流程

先聊个实际场景:最近身边不少朋友在折腾本地大模型,什么 Ollama、Dify、DeepSeek 都装好了,API 也在本机调通了,可一到要让同事电脑、自己手机或者局域网另一台机器访问的时候,就卡住了——http://localhost:8000敲进去只有自己能打开,换成本机局域网 IP 直接超时。同样的戏码也发生在用 FastAPI 写接口的人身上:开发环境跑得好好的,一旦要让测试环境、其他服务或者生产服务器去调它,就各种连不上。这篇东西我从头到尾拆一遍 Uvicorn + FastAPI 本地部署这件事,重点放在"怎么让外部设备真正访问到你起的服务",顺便把那些排行榜上反复出现的坑(比如 uvicorn 一直占用 8000、防火墙挡连接、局域网能通但公网不通)一次性讲透。

我的目标读者很明确:刚开始用 FastAPI 写接口的新手,以及那些把 AI 大模型或数据处理服务部署在本地、又需要对外提供 Web API 的人。这篇文章不堆概念,直接给能落地的步骤,每一步都讲清楚为什么这么做。

1. 先搞懂 Uvicorn 和 FastAPI 各自扮演什么角色

很多教程一上来就让你pip install fastapi uvicorn,然后跑个 hello world 就完事了。但真到部署的时候,你得先理解这两个东西到底谁负责什么。简单说,FastAPI 是你写接口逻辑用的框架,它定义路由、参数校验、数据序列化;而 Uvicorn 是一个 ASGI 服务器,负责真正监听网络端口、接收 HTTP 请求、把请求交给 FastAPI 处理、再把响应返回给客户端。

1.1 没弄懂这个区别的典型后果

你单独写个 FastAPI 应用文件,如果没有 Uvicorn 这类服务器去加载并运行它,那这个应用就只是个"等待被调用的对象",根本不会自己监听端口。反过来,如果你只用 Uvicorn 去运行一个普通的 WSGI 应用(比如 Flask),虽然能跑,但很多 ASGI 特性发挥不出来。所以 Uvicorn 和 FastAPI 的正确关系是:Uvicorn = 服务器FastAPI = 应用,两者配合才能对外提供 Web 服务。

我记得有个同事第一次接触 FastAPI,看到网上说"FastAPI 自带开发服务器",结果直接python main.py,发现命令行报错No module named 'uvicorn',他又去搜"怎么安装 uvicorn",装了一半又来问我为什么uvicorn main:app提示找不到模块。其实核心逻辑很简单:FastAPI 官方文档推荐用 Uvicorn 来跑,是因为 Uvicorn 是目前 ASGI 服务器里性能和稳定性都相当好的选择,而且它是用 uvloop 和 httptools 这两个高性能库封装的,底层事件循环比纯 Python 实现快不少。

1.2 一个最小可运行的 API 长什么样

我把项目先拆成最简单的结构,后面再展开怎么做生产级部署:

myapi/ ├── main.py # 应用入口,定义 FastAPI 实例和路由 └── requirements.txt

main.py里写这么一段:

from fastapi import FastAPI app = FastAPI(title="Demo API") @app.get("/") def read_root(): return {"message": "Hello, FastAPI + Uvicorn"} @app.get("/health") def health_check(): return {"status": "alive"}

requirements.txt里写上:

fastapi==0.115.6 uvicorn[standard]==0.34.0

然后安装依赖、启动服务:

pip install -r requirements.txt uvicorn main:app --host 0.0.0.0 --port 8000

注意命令里的main:app,意思是"从main.py里导入名为app的对象"。这个对象是 FastAPI 实例,Uvicorn 会把 HTTP 请求按 ASGI 协议转发给它。这一步写好之后,你在浏览器访问http://127.0.0.1:8000/health,能看到{"status":"alive"},说明本机服务已经起来了。

2. 外部访问失败的第一道坎:host 绑定 0.0.0.0 而不是 127.0.0.1

这是排行榜上"怎么才能外部访问"这类问题里,最常见也是最本质的原因。Uvicorn 默认的--host参数是127.0.0.1,意思是只监听本机回环地址。回环地址的设备接口只在你自己这台电脑上存在,局域网里的其他设备无法通过你的局域网 IP 路由到这个服务。

2.1 127.0.0.1、0.0.0.0 和具体 IP 的区别

我用一个比方来解释:你开了一个家庭派对,门口挂了个牌子写着"仅限本人进入"。127.0.0.1就是这个牌子——只有你本机能进,你邻居、快递员一概不能进。0.0.0.0相当于把牌子换成"欢迎所有人进来,只要走这门",它表示监听本机所有网络接口上的请求。你还可以更精确地用某个具体 IP,如--host 192.168.1.100,表示只允许从这块网卡进来的连接。

所以,想让局域网其他设备通过http://192.168.1.100:8000访问你的 FastAPI 服务,启动命令至少要改成:

uvicorn main:app --host 0.0.0.0 --port 8000

改完之后,原地址http://127.0.0.1:8000依然能访问,因为它绑定的网络接口也包含了回环接口,所以本地调试习惯不受影响。

2.2 用本机局域网 IP 自测

启动改成0.0.0.0之后,先别急着让其他设备连,你自己本机先用局域网 IP 验证一次。查询本机局域网 IP:

# Linux / macOS ip addr show | grep inet # 或者 ifconfig | grep inet # Windows ipconfig

通常在192.168.x.x10.x.x.x网段找到你的地址。然后在浏览器或 curl 里测试:

curl http://192.168.1.100:8000/health

如果这条命令通了,说明服务至少已经从"仅本机可见"变成了"局域网可见"。这是整个外部访问链路里面,最先要解决的网络绑定问题。很多人的服务已经跑在 0.0.0.0 上了,但 Windows 防火墙默认阻止了外部连接,于是卡在第二步。

3. 防火墙策略:明明 host 是 0.0.0.0 了,局域网还是连不上

服务绑定0.0.0.0之后,局域网其他设备访问仍然超时,这种情况十有八九是防火墙挡住了入口。操作系统层面的防火墙会检查每个入站连接,如果端口没有暴露,TCP 握手的 SYN 包直接丢弃,表现就是客户端一直卡在连接阶段,直到超时。

3.1 Windows 防火墙放行端口

Windows 上跑 Uvicorn 时,第一次启动通常会弹出一个对话框问你是否允许 Python 通过防火墙。如果你点了取消,或者压根没看到弹窗,那就得手动放行。操作路径是:控制面板 -> Windows Defender 防火墙 -> 高级设置 -> 入站规则 -> 新建规则 -> 端口 -> TCP -> 特定本地端口填 8000 -> 允许连接 -> 配置文件全选 -> 命名。

这里有个小细节值得注意:只放行 TCP 8000 就行,Uvicorn 用的是 TCP 协议,不需要开 UDP。而且如果你换了端口,比如从 8000 改成 9000,防火墙规则还要再改一次,别指望端口变了规则自动生效。

3.2 Linux 防火墙:firewalld 和 ufw

Linux 服务器更加常见。Debian/Ubuntu 系默认可能是 ufw,CentOS/RHEL 系可能是 firewalld。先看规则加没加:

# ufw sudo ufw status # firewalld sudo firewall-cmd --list-all

如果是 ufw,放行端口:

sudo ufw allow 8000/tcp

如果是 firewalld:

sudo firewall-cmd --zone=public --add-port=8000/tcp --permanent sudo firewall-cmd --reload

很多云服务器的控制台安全组也要单独检查。阿里云、腾讯云这类平台默认会有一层安全组策略,需要到控制台去添加入方向规则,允许 TCP 8000 端口。这个和系统防火墙是两层独立的机制,一层不开就访问不了。我自己踩过最尴尬的一次:服务器 firewall 放行了,ufw 也放行了,但安全组忘了改,局域网访问测试了两小时才发现问题。

3.3 云服务器安全组和系统防火墙的双层注意点

本地局域网部署和云服务器部署的排查路径略有不同。如果你是在一台有公网 IP 的云服务器上跑 Uvicorn,那防火墙链路是:客户端 -> 公网线路 -> 云安全组 -> 系统 iptables/firewalld -> Uvicorn 进程。任何一个环节把端口挡了,连接都到不了应用层。

所以排查外部连接问题时,我习惯按照"由外到内逐层检查"的顺序:

1. 确认服务进程在监听 0.0.0.0:8000 2. 确认系统防火墙放行了 8000/tcp 3. 确认云安全组(如果有)放行 8000/tcp 4. 如果都不行,用 tcpdump 抓包看 SYN 是否到达服务器

4. uvicorn 一直占用 8000:端口冲突的完整排查链路

热搜词里"uvicorn 一直占用8000"出现频率很高,这属于部署过程中绕不开的经典问题。第一次在服务器上启动 Uvicorn 时报[Errno 98] Address already in use,或者在 Windows 上报[WinError 10048] Only one usage of each socket address,很多人第一反应是"重启服务器",但基本没用,因为重启后那个进程可能又自动起来了。

4.1 定位谁占用了端口

排查思路很简单:找到当前监听 8000 端口的进程,看它是什么,再决定是杀掉还是换端口。

# Linux ss -tlnp | grep 8000 # 或者 lsof -i :8000 # Windows netstat -ano | findstr :8000 # 输出的最后一列是 PID,再用 tasklist 查进程名 # macOS lsof -i :8000

4.2 常见占用源和处理方式

根据我的经验,8000 端口被占用的常见情况有这么几种:

场景占用进程处理方式
之前启动的 Uvicorn 没杀掉python/uvicorn结束进程并确认无残留子进程
运行了 Django/Flask 等其他开发服务器python换成 8001 端口或停掉旧服务
AI 工具(如某些 WebUI)默认占用了 8000其他服务换端口,这是最省事的
系统服务或 Docker 端口映射docker-proxy 等检查容器并调整映射

我遇到过最"坑"的一种情况是:之前用nohup uvicorn main:app &启动的服务,终端关了但进程还在后台运行。由于没有日志提醒,重新启动又提示端口占用,查ss -tlnp才发现是之前遗留的进程。杀掉它:

kill -9 <PID>

如果进程一直杀不掉,用kill -9强制结束。但我不建议动不动就kill -9杀 Uvicorn 主进程——Uvicorn 正常退出会清理端口资源,强制杀掉可能留下 socket 残留(概率比较低但存在),再加上如果有--workers 4这类参数,会有一组子进程,只杀主进程,worker 子进程可能进入失控状态。最稳妥的做法是:如果服务是 systemd 管的,用systemctl restart;如果只是手动起的,kill -TERM <父PID>先尝试优雅退出,杀不掉再升级到kill -9

4.3 换端口是不是更优解

与其纠结杀掉占用进程,有时候换端口更省心。但换端口不能瞎换,里面有几个细节要注意:

  • 端口范围要在 1024 以上,1024 以下通常需要 root 权限;
  • 商用服务尽量避免常见的 8000、8080、8888 这些"热门"端口,很容易被其他软件抢占。我自己喜欢用 18000、18001 这类不那么常见但好记的端口;
  • 监听端口一旦变更,所有客户端调用地址都得跟着变,如果有前端代码写死了端口,记得同步更新;
  • 防火墙规则也要一起调整,这个前面说过了。

如果你希望 Uvicorn 在端口冲突时自动换端口,可以在启动命令里加上多个端口参数,比如:

uvicorn main:app --host 0.0.0.0 --port 8000 --port 8001

不过这种配置实际意义不大,因为服务重启后端口可能一直在变,反而增加外部调用方的心智负担。更推荐固定端口加 systemd 守护。

5. 从手动启动走向常驻服务:systemd 守护 Uvicorn

如果你把 FastAPI 服务部署到一台服务器上,手动uvicorn main:app --host 0.0.0.0 --port 8000这种方式只能在前台跑,一旦 SSH 断开,终端关闭,服务就跟着没了。虽然可以挂nohup或者screen,但都不够可靠——进程崩溃了不会自动拉起,机器重启了也不会自动恢复。生产环境推荐用 systemd 来管理它。

5.1 一个可用的 systemd 服务文件

假设你的项目放在/opt/myapi,依赖安装在系统的 Python 环境中(或者你自己有 venv,路径对应改)。创建一个/etc/systemd/system/myapi.service文件:

[Unit] Description=FastAPI Demo Service After=network.target [Service] User=www-data WorkingDirectory=/opt/myapi ExecStart=/usr/bin/python3 -m uvicorn main:app --host 0.0.0.0 --port 8000 Restart=always RestartSec=3 Environment="PYTHONUNBUFFERED=1" [Install] WantedBy=multi-user.target

然后重新加载并启动:

sudo systemctl daemon-reload sudo systemctl enable --now myapi sudo systemctl status myapi

Restart=always是生产部署的关键配置。它保证进程意外退出时,systemd 会在 3 秒后重新拉起。这在本地部署 AI 模型服务时尤其重要,因为模型推理进程有时会因显存不足或内存溢出退出,如果没人盯着,手动重启会很被动。

5.2 用虚拟环境隔离依赖

刚才那个 ExecStart 用的是系统 Python,实际项目中我建议使用 venv 或者 conda 环境,避免依赖冲突。特别是你同时跑很多 AI 相关的 Python 服务时(比如 Ollama 的 Python SDK、FastAPI、数据处理的 pandas、深度学习框架),依赖之间冲突概率很高。

修改 ExecStart 指向虚拟环境目录即可:

ExecStart=/opt/myapi/venv/bin/python -m uvicorn main:app --host 0.0.0.0 --port 8000

注意,不要直接ExecStart=/opt/myapi/venv/bin/uvicorn ...,虽然也能跑,但用python -m uvicorn能保证和当前 Python 解释器配套,避免 PATH 里多个 Python 版本把你搞晕。

5.3 多 worker 配置要谨慎

网上很多教程建议加--workers 4来提升并发能力,但在本地部署阶段我反而不是很推荐。原因有两个:

第一,--workers在 Uvicorn 中是启动多个进程,每个进程都会加载一遍 FastAPI 应用。如果你的应用里有大的 AI 模型加载(比如本地部署了语言模型),那每个 worker 都会占一份显存。8G 显存的机器,模型加载两份可能直接 OOM。

第二,并发能力不是靠随便加 worker 就能提升的,还要看你的应用有没有状态共享问题。FastAPI 默认你是无状态的,但如果用了内存存储(比如内存里缓存了向量库索引),多 worker 之间数据不共享,就会出现"请求打到了 worker A 有缓存,打到 worker B 就没有"这种诡异表现。

如果需要多进程,先确认你的应用是无状态的,再考虑加--workers。本地部署阶段,--workers 2通常已经够用,没必要盲目上 8 个。

6. 让局域网以外的设备能访问:公网部署的几条路径

前面讲的都是"局域网内外部访问"。但很多时候你需要的不是局域网,而是让外网设备(比如另一座城市的电脑、你手机 4G 网络)也能访问到本地部署的服务。这个场景有几个可行路径,按操作难度从低到高排列。

6.1 云服务器反向代理:最稳的方案

如果你的本机或者内网服务器已经跑着 FastAPI,但带宽和稳定性不够,或者不想把内网服务直接暴露到公网,那么最推荐的方式是:在一台有公网 IP 的云服务器上部署 Nginx,再由 Nginx 把请求转发到你内网的 Uvicorn 服务。

举个例子,你在云服务器上有域名api.example.com,Nginx 配置:

server { listen 80; server_name api.example.com; location / { proxy_pass http://192.168.1.100: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; } }

这样外部用户访问http://api.example.com,实际上是 Nginx 接收请求,再转发到你内网的 FastAPI。这种架构的好处是:Nginx 可以同时做 HTTPS 终结、负载均衡、静态文件服务,你的 FastAPI 服务只需要专心处理业务逻辑。

6.2 内网穿透工具:没有公网 IP 时的替代方案

如果你的服务运行在家里或者公司内网,没有公网 IP,也申请不到端口映射权限,那就得用内网穿透类工具。这类工具的原理是:内网机器主动建立一个到公网中转服务器的连接,外部用户访问中转服务器的某个端口,中转服务器再通过这个已经建立的隧道把请求转发到你的内网服务。

常见工具有 frp、ngrok、cpolar 等。以 frp 为例,你在一台有公网 IP 的服务器上运行 frps(server),在内网运行 frpc(client),就行了。frpc.ini 里配置:

[common] server_addr = 你的云服务器IP server_port = 7000 [web] type = tcp local_ip = 127.0.0.1 local_port = 8000 remote_port = 8000

然后外部用户直接访问你的云服务器 IP:8000,流量经过 frp 隧道导到内网 FastAPI。这里要提醒一句:内网穿透工具让服务暴露到了公网,安全风险也同步放大。如果还是 HTTP 明文传输,能抓到数据包的人都能看到请求内容。强烈建议穿透后再套一层 HTTPS,或者至少放一个 Token 校验在前面。

6.3 安全底线:公开到公网之前必须做的事

不管用哪种方式把 FastAPI 暴露到公网,至少要做好几件事,否则我可以负责任地说:不出 72 小时,就有人来扫描你的服务。

第一,不要把 Uvicorn 直接裸露在公网上。Uvicorn 是 ASGI 服务器,不是安全边界。最优架构是公网 -> Nginx(HTTPS)-> FastAPI。如果非要直连 Uvicorn,也要确保服务本身有鉴权。

第二,给 API 加认证。FastAPI 里可以用 OAuth2 密码模式、JWT、简单的 API Key。最简单粗暴的版本是依赖注入加 Header 校验:

from fastapi import FastAPI, Header, HTTPException app = FastAPI() @app.get("/secure") def secure_read(x_api_key: str = Header(...)): if x_api_key != "你的密钥": raise HTTPException(status_code=403, detail="Forbidden") return {"data": "secret"}

第三,限制来源 IP。用 Nginx 限制到只有特定 IP 段能访问,或者 Uvicorn 层面配合 allow_origins 做跨域限制。这一步看具体需求,如果服务只给同事用,直接把办公室出口 IP 加到白名单里就行。

7. 本地部署 AI 大模型场景的额外心得

这段时间热搜词里密集出现"本地部署 DeepSeek""Ollama 本地部署""Dify 本地部署"这类关键词,可见很多人其实不是写业务接口,而是在把大模型工具链部署到本地后需要暴露 API。这个场景和普通的 FastAPI 部署有几个明显不同的注意点,我单独拉出来讲。

7.1 Uvicorn 只是入口,模型加载才是重点

用 FastAPI 封装本地大模型推理服务的时候,Uvicorn 的配置反而简单,难点在模型加载上。比如你部署 Llama 系列或者 DeepSeek 量化模型,通常流程是:启动时加载模型到显存/内存,每个 HTTP 请求进来后调用模型进行推理,返回结果。

一个常见的错误是每个请求都重新加载模型,这样性能会奇差无比。正确做法是启动时加载一次,全程复用:

from fastapi import FastAPI from transformers import AutoModelForCausalLM, AutoTokenizer app = FastAPI() tokenizer = AutoTokenizer.from_pretrained("./deepseek-local") model = AutoModelForCausalLM.from_pretrained("./deepseek-local") @app.post("/generate") def generate(prompt: str): inputs = tokenizer(prompt, return_tensors="pt") outputs = model.generate(**inputs, max_new_tokens=512) return {"text": tokenizer.decode(outputs[0], skip_special_tokens=True)}

Ollama 这类工具把模型加载细节封装掉了,你只要用它的 HTTP API 就行。但如果你是自己用 FastAPI 封装 Transformers 模型,记得把模型加载放到全局变量,而不是函数内部。

7.2 模型推理超时和 Uvicorn 的 timeout 设置

大模型推理通常耗时较长,特别是没有 GPU、纯 CPU 跑的情况下,生成几百个 token 可能需要几十秒甚至几分钟。而 Uvicorn 默认对请求处理没有超时限制(超时主要由中间的代理层控制)。如果你在 Nginx 后面,Nginx 默认的proxy_read_timeout是 60 秒,大模型推理一慢就会被掐断。

所以要修改 Nginx 超时时间:

location /generate { proxy_pass http://127.0.0.1:8000; proxy_read_timeout 300s; proxy_send_timeout 300s; proxy_connect_timeout 60s; }

如果是公网穿透,frp 也有可能因为长时间没有数据交互而判定连接空闲断开,需要适当调大心跳间隔。这类隐蔽的问题排查起来很费劲,我一开始还以为是 Uvicorn 的问题,后来抓包才发现是中间层超时。

7.3 显存占用和并发请求的矛盾

本地部署大模型服务时,还容易遇到一个"看着像是 Uvicorn 崩了,其实是 OOM"的问题。并发请求一多,显存瞬间溢满,Python 进程直接崩溃。这时候检查服务日志会发现有 CUDA out of memory 的报错。

解决办法有几个方向:模型加载时加上半精度或 int8 量化,减少显存占用;Uvicorn 只开单 worker;在应用层做并发控制,比如用信号量限制同时只有 1 个推理任务执行。比如:

import asyncio from fastapi import FastAPI app = FastAPI() semaphore = asyncio.Semaphore(1) @app.post("/generate") async def generate(prompt: str): async with semaphore: # 你的推理代码 return {"text": result}

这样即使外部并发请求很多,推理部分仍然是串行的,不会打爆显存。代价是高峰请求排队,但对本地部署场景来说,稳定比吞吐量重要得多。

8. 从踩坑经验里提炼出的几条部署建议

前面七章把从本机到局域网再到公网的完整链路讲完了,最后分享几个我没法归类到某一节但特别重要的经验,都是实际部署中反复遇到的问题。

8.1 日志管理不能省

手动uvicorn main:app跑起来,日志直接打印到终端。一旦升级成 systemd 服务,日志就进入 journald。如果服务出了问题,对应去看日志:

journalctl -u myapi -f

Uvicorn 自带访问日志,默认会打印每个请求的状态码。调试阶段可以开着,生产环境如果想要日志少一点,可以加参数:

--no-access-log

但我的建议是生产环境不要关访问日志。一旦出现安全问题或者异常请求,访问日志是最直接的溯源依据。

8.2 启动脚本容易忽略 PYTHONUNBUFFERED

之前 systemd 那个文件里我写了Environment="PYTHONUNBUFFERED=1"。这个不是随手加的。默认情况下 Python 的输出是块缓冲的,日志不会实时刷到 stdout,systemd 里看日志会有延迟。加了PYTHONUNBUFFERED=1后强制逐行输出,排查问题的时候能看到即时的打印信息。

8.3 善用 Uvicorn 的 --reload 但仅限开发环境

FastAPI 开发时,改代码后要手动重启服务很烦人,所以--reload参数很好用:

uvicorn main:app --host 0.0.0.0 --port 8000 --reload

开启后,文件变化自动重启。但生产环境千万不要加--reload,这不仅仅是性能问题,更重要的是它会让运行状态变得不可控:系统可能在不知道改了哪个文件的情况下无声重启,而且--reload默认会启动一个监控进程,资源占用也会多一份。

8.4 本地部署的 FastAPI 也要考虑 CORS

如果你的 FastAPI 是给前端页面调的,而前端跑在另一个域名或端口,就会遇到跨域问题。FastAPI 处理这个很简单:

from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:5173"], allow_methods=["*"], allow_headers=["*"], )

但这里有个安全细节:开发时可以allow_origins=["*"],生产环境最好收敛到具体域名。如果你把 API 开放给所有人,而这个 API 又恰好有写操作,那么任何恶意网页都能从用户浏览器发起跨域请求,这是 CSRF 攻击的高发场景。

现在回头看,最让我踩得深的一个坑就是把所有精力花在"如何让外部访问"的第一步——改 host 和防火墙,却忽视了部署架构的完整链条:服务绑定、防火墙、安全组、进程守护、代理层、超时设置。每一步单独看都不难,但串起来之后,任何一个环节遗漏都会导致访问失败。这篇东西写出来,本质上就是把我自己踩过的坑和验证过的方案整理成了一份可以照着操作的清单。你在部署的时候如果又碰到新问题,记住一个原则:先从网络链路逐层排查,再从进程状态和服务日志里找线索,别急着重启机器,大多数问题都不是重启能解决的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询