Python FastAPI部署到云服务器

本文以Ubuntu 24.04 LTS、Python 3.12、FastAPI 0.162.0、Uvicorn 0.54.0部署一个最小 API。Uvicorn 监听本机地址,Nginx 处理域名入口,systemd 负责后台运行。适合新建测试实例;已有面板管理的环境应使用面板对应入口,不再安装一套互相争用端口的 Web 服务。

建立独立目录和Python环境

以下系统操作使用具备 sudo 权限的管理员。先检查 python3 --version,本例应是 Python 3.12;其他版本先确认项目及依赖兼容。目录和服务名称均为本例专用,已存在时先核对用途。

sudo apt update
sudo apt install -y python3 python3-venv ca-certificates curl nginx nano
sudo adduser --system --group --home /srv/baole-api --no-create-home baole-api
sudo install -d -o root -g baole-api -m 0750 /srv/baole-api
sudo python3 -m venv /srv/baole-api/.venv
sudo /srv/baole-api/.venv/bin/python -m pip install 'fastapi==0.162.0' 'uvicorn==0.54.0'
sudo /srv/baole-api/.venv/bin/python -m pip check
sudo /srv/baole-api/.venv/bin/python -m pip freeze

这组命令只在项目虚拟环境中安装依赖,不改系统 Python 的全局包。pip check应没有依赖冲突。保存 pip freeze输出,作为本次实际依赖记录;已有项目应先按自己的锁文件重建环境,不盲目升级全部包。

写一个能直接验收的接口

使用 sudo nano /srv/baole-api/main.py创建文件:

from fastapi import FastAPI

app = FastAPI(title="宝乐云接口测试")

@app.get("/health")
def health():
    return {"status": "ok", "name": "宝乐云接口测试"}
sudo chmod 0644 /srv/baole-api/main.py
sudo -u baole-api /srv/baole-api/.venv/bin/python -c 'import fastapi, uvicorn; print(fastapi.__version__, uvicorn.__version__)'
sudo -u baole-api /srv/baole-api/.venv/bin/python -m uvicorn main:app --app-dir /srv/baole-api --host 127.0.0.1 --port 8000

最后一条会占用当前终端,属于前台验证。在第二个 SSH 窗口执行 curl --fail --show-error --retry 5 --retry-connrefused --retry-delay 1 --max-time 5 http://127.0.0.1:8000/health,应得到包含 "status":"ok"的 JSON。验证后回到第一个窗口按 Ctrl+C停止前台进程,避免与下一步争用 8000 端口。

改为systemd后台运行

新建 /etc/systemd/system/baole-api.service,填写:

[Unit]
Description=Baole FastAPI application
After=network.target

[Service]
Type=exec
User=baole-api
Group=baole-api
WorkingDirectory=/srv/baole-api
Environment=PYTHONDONTWRITEBYTECODE=1
Environment=PYTHONUNBUFFERED=1
ExecStart=/srv/baole-api/.venv/bin/python -m uvicorn main:app --host 127.0.0.1 --port 8000 --proxy-headers --forwarded-allow-ips=127.0.0.1
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now baole-api
sudo systemctl status baole-api --no-pager
curl --fail --show-error --retry 5 --retry-connrefused --retry-delay 1 --max-time 5 http://127.0.0.1:8000/health

这里使用虚拟环境 Python 的绝对路径,因此不需要在服务中执行 source activate。生产启动不加 --reload。示例先用一个进程;增加 workers 前应评估内存和任务行为,不能让启动钩子重复创建同一批定时任务。

让域名请求进入这个接口

新建 /etc/nginx/sites-available/baole-api,把域名换成自己的实际名称:

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        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;
    }
}
sudo ln -s /etc/nginx/sites-available/baole-api /etc/nginx/sites-enabled/baole-api
sudo nginx -t && sudo systemctl reload nginx

配置检查通过后才重载,已有同名文件时先核对,不强制覆盖。按DNS教程设置解析和 Web 入口,访问 http://实际域名/health;随后按Certbot教程部署 HTTPS、验证实际证书和续期。

8000 端口只供本机 Nginx 连接,不开放到公网。服务只信任来自 127.0.0.1的转发信息;以后增加其他代理时,需要按真实代理来源重新配置。proxy_pass在这里不附加新路径,/health会按原路径传给应用。

检查服务恢复和常见报错

退出 SSH 后,从本地电脑访问 https://实际域名/health。再重新登录服务器,重启应用服务并复测:

sudo systemctl restart baole-api
systemctl is-enabled baole-api
curl --fail --show-error --retry 5 --retry-connrefused --retry-delay 1 --max-time 5 http://127.0.0.1:8000/health
sudo journalctl -u baole-api --since '10 minutes ago' --no-pager
错误或现象 处理方向
Could not import module "main" 核对main.py的位置、WorkingDirectory和main:app名称。实际项目放在子包时,入口也要按包路径修改。
Address already in use 查看8000端口占用;确认前台测试进程已退出,不要直接杀掉未知进程。
ModuleNotFoundError 确认依赖装在本服务使用的.venv中,核对绝对Python路径及锁文件。
本机正常,公网返回502 检查Nginx加载的代理端口和错误日志,再看服务状态。
HTTPS请求跳回HTTP 检查实际HTTPS入口是否传递X-Forwarded-Proto,以及Uvicorn信任的代理来源。

部署自己的接口时,替换示例代码,并单独配置认证、业务依赖和可写数据目录。数据库连接成功、上传和任务处理等业务检查需要逐项完成;/health成功只代表这个测试接口可用。接口文档页 /docs是否对公网开放也应按自己的业务决定。

更新依赖前保留上一版代码和依赖锁定记录,在新的虚拟环境中验证后切换。失败时恢复原代码与对应环境再重启服务;有数据库迁移时,还应保留相应恢复步骤。