前端转 AI 第 29 天:Nginx 反代 + HTTPS - 让服务"能上公网"
昨日回顾
Day 28 上了 CI/CD,push 就自动部署。
但有个尴尬------服务还是 http://your-ip:8000:
- 浏览器标"不安全"
- 微信 / 小程序调不了(强制 HTTPS)
- 前端跨域一堆问题
- 只能绑 IP,用户记不住
今天上 Nginx 反代 + HTTPS,让服务"正式上线"。
Day 29 目标:搞懂反向代理,配上域名 + 免费 SSL,让服务从"裸奔"到"专业上线"。
一、为什么需要 Nginx
现在的架构:
yaml
用户 ────► uvicorn 直接暴露 8000 端口
问题:
| 问题 | 后果 |
|---|---|
| 端口难看 | your-ip:8000 vs api.example.com |
| 没有 HTTPS | 浏览器报警,小程序调不通 |
| 无静态文件能力 | 前端页面没法一起部署 |
| 无限流 / 无压缩 | 容易被刷 |
| 单点风险 | uvicorn 挂了就直接 502 |
| 不能多实例 | 单进程吞吐有限 |
加上 Nginx 后:
markdown
用户 ────► Nginx:443 ─┬──► uvicorn:8000
├──► uvicorn:8001
└──► 静态文件
Nginx 帮你做的事:
- HTTPS 终止(SSL 证书配在 Nginx)
- 反向代理(转发到后端)
- 负载均衡(多个 uvicorn 实例)
- 静态文件服务(前端页面)
- 限流 / 压缩 / 缓存
前端类比: 你在前端项目里肯定用过 proxy 配置------Nginx 就是"生产环境的 proxy"。
二、反向代理 vs 正向代理
两个词听起来像,实际完全相反。
1. 正向代理:代理"客户端"
客户端 → 代理服务器 → 目标服务器
典型场景: 科学上网、公司内网代理。
特点: 客户端知道自己在用代理。
2. 反向代理:代理"服务端"
客户端 → 反向代理 → 多个后端服务器
典型场景: Nginx / CDN / API 网关。
特点: 客户端不知道 后面有几台服务器,只知道访问了 api.example.com。
3. 一张图看清
css
正向代理:
[客户端] ──► [代理] ──► [服务器]
↑
客户端知道
反向代理:
[客户端] ──► [代理] ──► [服务器 1]
│──► [服务器 2]
└──► [服务器 3]
↑
客户端不知道后面有几台
你天天用 Nginx 做的是反向代理。
三、Nginx 基础
1. 安装
Ubuntu / Debian:
bash
sudo apt update
sudo apt install -y nginx
Mac:
bash
brew install nginx
Docker(推荐):
bash
docker run -d --name nginx -p 80:80 nginx:alpine
我们后面用 Docker 方式。
2. 目录结构
bash
/etc/nginx/
├── nginx.conf # 主配置
├── conf.d/ # 站点配置(推荐放这)
│ └── default.conf
├── sites-available/ # 可用站点
├── sites-enabled/ # 启用站点(软链接)
└── snippets/ # 片段
推荐做法:在 conf.d/ 放 *.conf 文件,Nginx 自动加载。
3. 核心概念
| 概念 | 含义 | 前端类比 |
|---|---|---|
| server | 一个虚拟主机(一个域名) | 一个 webpack 入口 |
| location | URL 路径匹配规则 | 前端的路由 |
| upstream | 后端服务器组 | 请求后端接口 |
| proxy_pass | 转发到哪 | axios 的 baseURL |
4. 最简单的配置
nginx
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://localhost:8000;
}
}
作用: 访问 api.example.com → 转发到 localhost:8000。
server_name 匹配规则:
- 完全匹配:
api.example.com - 通配:
*.example.com - 正则:
~^api\.
四、核心配置:反向代理
1. 基础反代
nginx
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://api:8000; # Docker 里用服务名
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;
# 超时
proxy_connect_timeout 30s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
重点解释:
1. proxy_http_version 1.1
HTTP/1.1 支持长连接------性能更好。默认 HTTP/1.0 每次请求都要重连。
2. proxy_set_header 三兄弟
为什么必须传?
后端拿到的 request.client.host 默认是 Nginx 的 IP,不是用户真实 IP。
传了之后:
python
# FastAPI 里拿真实 IP
@app.get("/ip")
def get_ip(request: Request):
# 从 Nginx 传的 X-Forwarded-For 里取
forwarded = request.headers.get("X-Forwarded-For")
if forwarded:
real_ip = forwarded.split(",")[0].strip()
else:
real_ip = request.client.host
return {"ip": real_ip}
三个头的含义:
| Header | 含义 |
|---|---|
X-Real-IP |
客户端 IP(第一个代理写) |
X-Forwarded-For |
完整链条(多个代理追加) |
X-Forwarded-Proto |
原始协议(http / https) |
3. FastAPI 信任代理
要在 FastAPI 里开启 ProxyHeadersMiddleware:
python
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts=["*"])
或启动时:
bash
uvicorn ai_assistant.api.app:app --proxy-headers --forwarded-allow-ips="*"
这样 request.client.host 才是真实用户 IP,而不是 Nginx 的。
2. 用 upstream 支持多实例
nginx
upstream api_backend {
# 负载均衡策略
least_conn; # 最少连接数(默认轮询)
server api1:8000 weight=3; # 权重 3
server api2:8000 weight=1; # 权重 1
server api3:8000 backup; # 备用,前面都挂了才用
keepalive 32; # 保持 32 个长连接
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://api_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
# ... 其他 header
}
}
负载均衡策略:
| 策略 | 说明 |
|---|---|
round-robin |
轮询(默认) |
least_conn |
最少连接(推荐) |
ip_hash |
同 IP 到同一台 |
random |
随机 |
这就是"水平扩展"------加一个 uvicorn 实例,改一行 upstream 就生效。
3. 路径重写
场景: 后端路由是 /api/...,但想让外部访问 /v1/...。
nginx
location /v1/ {
rewrite ^/v1/(.*)$ /api/$1 break;
proxy_pass http://api:8000;
}
更简单的方式------proxy_pass 带路径:
nginx
# 外部 /api/chat → 后端 /chat(去掉 /api 前缀)
location /api/ {
proxy_pass http://api:8000/; # ← 末尾加 / 表示替换掉 /api/
}
关键:proxy_pass 末尾有 / 和没 /,行为完全不同。
| 写法 | 请求 /api/chat |
实际转发 |
|---|---|---|
proxy_pass http://api:8000; |
→ | http://api:8000/api/chat |
proxy_pass http://api:8000/; |
→ | http://api:8000/chat |
这是 Nginx 的经典坑,务必记住。
五、HTTPS:从 HTTP 到 HTTPS
1. 为什么要 HTTPS
不只是"安全"------ 很多场景强制要求 HTTPS:
- 微信小程序:必须 HTTPS
- 浏览器 PWA:必须 HTTPS
- HTTP/2:必须 HTTPS
- Cookie Secure:必须 HTTPS
- SEO:搜索引擎优先
而且:从 2020 年起,主流浏览器对 HTTP 标"不安全",用户看到会劝退。
现代 Web 的默认------HTTPS。
2. 免费 SSL 证书:Let's Encrypt
Let's Encrypt 是"免费、自动、90 天续期"的证书颁发机构。
用 certbot 自动化:
bash
# 安装
sudo apt install -y certbot python3-certbot-nginx
# 申请证书(自动改 Nginx 配置)
sudo certbot --nginx -d api.example.com
# 自动续期(certbot 会加 cron 或 systemd timer)
sudo certbot renew --dry-run
certbot --nginx 做了什么:
- 验证域名所有权
- 申请证书
- 自动修改 Nginx 配置加 443 监听
- 设置 HTTP → HTTPS 跳转
- 配置自动续期
一条命令搞定。
3. 手动配置 HTTPS
如果不用 certbot,也能自己配:
nginx
server {
listen 443 ssl;
http2 on;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
# 只允许 TLS 1.2 / 1.3
ssl_protocols TLSv1.2 TLSv1.3;
# 加密套件(兼容性和安全平衡)
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
# 服务端优先选择加密套件
ssl_prefer_server_ciphers off;
# 会话缓存,提升握手速度
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
location / {
proxy_pass http://api:8000;
# ... 反代配置
}
}
# HTTP → HTTPS 强制跳转
server {
listen 80;
server_name api.example.com;
return 301 https://$host$request_uri;
}
关键点:
1. listen 443 ssl + http2 on
HTTPS 默认端口 443,http2 on 开启 HTTP/2------性能提升明显(多路复用)。
2. ssl_protocols
只用 TLS 1.2 / 1.3。TLS 1.0 / 1.1 已被认为不安全。
3. add_header Strict-Transport-Security
HSTS------告诉浏览器"以后强制用 HTTPS 访问我"。
⚠️ 慎用 ------一旦下发,浏览器在 max-age 内都会强制 HTTPS,回不去了。
4. return 301
HTTP 请求 301 跳到 HTTPS ,用 $request_uri 保留完整路径和参数。
六、Docker + Nginx 组合
1. 项目结构
Day 27 的 docker-compose 加一个 Nginx 服务:
bash
ai-assistant/
├── docker-compose.prod.yml
├── nginx/
│ ├── nginx.conf
│ ├── conf.d/
│ │ └── api.conf
│ └── certs/ # SSL 证书(挂载)
│ ├── fullchain.pem
│ └── privkey.pem
2. nginx/conf.d/api.conf
nginx
upstream api_backend {
least_conn;
server api:8000;
keepalive 32;
}
# HTTP → HTTPS 跳转
server {
listen 80;
server_name api.example.com;
# Let's Encrypt 验证用
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS
server {
listen 443 ssl;
http2 on;
server_name api.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options "nosniff" always;
# 请求体大小限制
client_max_body_size 10M;
# 访问日志
access_log /var/log/nginx/api-access.log;
error_log /var/log/nginx/api-error.log;
# 通用反代配置
location / {
proxy_pass http://api_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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_read_timeout 60s;
}
# 静态文件(如果有)
location /static/ {
alias /var/www/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# 健康检查(不记日志)
location /health/ {
access_log off;
proxy_pass http://api_backend;
}
# 限制 API 速率
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://api_backend;
# ... 其他反代头
}
}
3. nginx/nginx.conf(主配置)
nginx
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/access.log main;
# 基础优化
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
# 限流配置
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# 加载站点配置
include /etc/nginx/conf.d/*.conf;
}
新增点:
1. log_format main:自定义日志格式
加上了 $request_time / $upstream_response_time------能区分"总耗时"和"后端耗时",排查慢请求神器。
2. gzip on:压缩响应
JSON 响应能压缩 60-80%------省带宽、快响应。
3. limit_req_zone:限流
rate=10r/s:每个 IP 每秒最多 10 个请求。
burst=20 nodelay:允许突发 20 个请求,超过的直接拒绝。
防刷、防攻击必备。
4. 更新 docker-compose.prod.yml
yaml
services:
api:
build: .
image: ai-assistant:prod
environment:
- ENV=prod
- API_KEY=${API_KEY}
expose:
- "8000" # 不映射到宿主,只内网可见
restart: always
nginx:
image: nginx:alpine
container_name: ai-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/certs:/etc/nginx/certs:ro
- ./nginx/www:/var/www:ro
- nginx_logs:/var/log/nginx
depends_on:
- api
restart: always
volumes:
nginx_logs:
关键点:
1. expose: 8000 而不是 ports
API 不再直接暴露到宿主机------只在内网,Nginx 才能访问。
这是安全实践------用户只能通过 Nginx 进来,走 HTTPS / 限流 / 日志。
2. Nginx 挂载:
nginx.conf:主配置conf.d/:站点配置certs/:SSL 证书www/:静态文件 + certbot 验证目录nginx_logs:日志持久化
5. 部署流程
服务器上:
bash
# 1. 初始化 certbot 目录
mkdir -p nginx/www/.well-known/acme-challenge
# 2. 先起服务(不配 HTTPS)
docker compose -f docker-compose.prod.yml up -d
# 3. 申请证书(用 certbot 容器)
docker run --rm \
-v $(pwd)/nginx/certs:/etc/letsencrypt \
-v $(pwd)/nginx/www:/var/www/certbot \
certbot/certbot certonly \
--webroot -w /var/www/certbot \
-d api.example.com \
--email you@example.com \
--agree-tos --no-eff-email
# 4. 证书就绪,重载 Nginx
docker compose -f docker-compose.prod.yml exec nginx nginx -s reload
# 5. 验证
curl -I https://api.example.com/health/live
自动续期:
加一个 certbot 服务,每天检查:
yaml
certbot:
image: certbot/certbot
volumes:
- ./nginx/certs:/etc/letsencrypt
- ./nginx/www:/var/www/certbot
entrypoint: /bin/sh
command: -c "trap exit TERM; while :; do certbot renew --webroot -w /var/www/certbot; sleep 12h & wait $${!}; done"
restart: unless-stopped
每 12 小时检查一次------证书快过期前自动续。
七、前端视角:Nginx vs Vite Proxy
做过前端的你会觉得 Nginx 配置似曾相识:
Vite 的 proxy:
js
// vite.config.js
export default {
server: {
proxy: {
"/api": {
target: "http://localhost:8000",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
};
Nginx 的配置:
nginx
location /api/ {
proxy_pass http://api:8000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
核心逻辑一模一样:
| 概念 | Vite | Nginx |
|---|---|---|
| 匹配路径 | proxy["/api"] |
location /api/ |
| 目标 | target |
proxy_pass |
| 路径重写 | rewrite |
proxy_pass 带 / |
| 修改 Host | changeOrigin: true |
proxy_set_header Host $host |
| 真实 IP | 自动 | X-Real-IP / X-Forwarded-For |
Vite proxy 是"开发环境的 Nginx"------思路一样,只是 Nginx 是生产级。
你写的 Vite proxy 配置,就是 Nginx 的"简化版"。
做过前端代理,理解 Nginx 只是"多几个指令"的事。
八、安全加固
上公网 = 被攻击的开始。几个必做项:
1. 隐藏 Nginx 版本
nginx
http {
server_tokens off;
}
响应头里不显示 Server: nginx/1.24.0------少给攻击者一点信息。
2. 限制请求方法
nginx
# 只允许常见方法
if ($request_method !~ ^(GET|POST|PUT|DELETE|OPTIONS|HEAD)$) {
return 405;
}
3. 屏蔽敏感路径
nginx
# 拒绝访问隐藏文件
location ~ /\. {
deny all;
return 404;
}
# 拒绝 .env / .git 等
location ~* \.(env|git|htaccess)$ {
deny all;
return 404;
}
踩坑者常犯: 忘记屏蔽 .git,整个仓库被拖走。
4. 加基础安全头
nginx
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'" always;
前端配置过的安全头,Nginx 里也能配。
5. IP 白名单
/admin 只允许公司 IP 访问:
nginx
location /admin/ {
allow 1.2.3.4;
allow 5.6.7.0/24;
deny all;
proxy_pass http://api_backend;
}
6. 限制请求体大小
nginx
client_max_body_size 10M;
防止超大请求打爆内存------FastAPI 里也有类似限制(Day 22 讲过)。
九、前端视角:Nginx 也是"前端部署工具"
除了反代,Nginx 还能部署前端!
1. 部署 Vite / React 静态文件
nginx
server {
listen 443 ssl;
http2 on;
server_name app.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
root /var/www/app;
index index.html;
# SPA 路由回退(关键)
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源长期缓存
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML 不缓存(保证最新)
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# API 转发到后端
location /api/ {
proxy_pass http://api:8000/api/;
# ... 反代头
}
}
几个关键点:
1. try_files $uri $uri/ /index.html
SPA 路由回退 ------用户访问 /dashboard,文件不存在,回退到 index.html,让前端路由处理。
没有这行,刷新页面就 404。
2. 静态资源长期缓存
nginx
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
Vite 打包的文件带 hash (app-a3f9b2e1.js),内容变 hash 变,缓存 1 年没问题。
3. HTML 不缓存
index.html 引用带 hash 的资源 ------HTML 必须每次拉最新,才能拿到新资源。
2. 前端 + 后端一站式部署
最终架构:
bash
用户 ──► Nginx:443
├── / → 前端静态文件
├── /api/ → FastAPI 后端
└── /ws/ → WebSocket
一个域名,前后端全搞定。 无跨域问题。
这就是"前后端一体部署"------比"前端 Vercel + 后端独立域名"简单得多。
十、实战:把 API 用 Nginx 包一层
1. 项目结构
bash
cp -r day28/ai-assistant-api-ci day29/ai-assistant-api-prod
cd day29/ai-assistant-api-prod
mkdir -p nginx/conf.d nginx/certs nginx/www
目录:
bash
ai-assistant/
├── docker-compose.prod.yml
├── Dockerfile
├── nginx/
│ ├── nginx.conf
│ ├── conf.d/
│ │ └── api.conf
│ ├── certs/ # SSL 证书(放这里)
│ └── www/ # certbot 验证
├── src/
└── ...
2. 开发环境用自签证书
生产用 Let's Encrypt,但开发时用自签证书先跑通流程:
bash
mkdir -p nginx/certs
cd nginx/certs
# 生成自签证书
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout privkey.pem \
-out fullchain.pem \
-subj "/C=CN/ST=Shanghai/L=Shanghai/O=Dev/CN=localhost"
cd ../..
-subj 直接指定证书信息,避免交互式提问。
3. nginx/conf.d/api.conf
nginx
upstream api_backend {
least_conn;
server api:8000;
keepalive 32;
}
# HTTP → HTTPS 跳转
server {
listen 80;
server_name localhost;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS
server {
listen 443 ssl;
http2 on;
server_name localhost;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
client_max_body_size 10M;
access_log /var/log/nginx/api-access.log;
error_log /var/log/nginx/api-error.log;
location / {
proxy_pass http://api_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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_read_timeout 60s;
}
location /health/ {
access_log off;
proxy_pass http://api_backend;
}
}
4. nginx/nginx.conf
复制前面的内容,但不要重复的 include。
nginx
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr [$time_local] "$request" '
'$status $body_bytes_sent rt=$request_time '
'urt=$upstream_response_time';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml;
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
include /etc/nginx/conf.d/*.conf;
}
5. 更新 docker-compose.prod.yml
yaml
services:
api:
build: .
image: ai-assistant:prod
environment:
- ENV=prod
- API_KEY=${API_KEY}
- DATABASE_URL=sqlite:///data/assistant.db
expose:
- "8000"
volumes:
- ./data:/app/data
- ./logs:/app/logs
restart: always
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health/live')"]
interval: 30s
timeout: 5s
retries: 3
nginx:
image: nginx:alpine
container_name: ai-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/certs:/etc/nginx/certs:ro
- ./nginx/www:/var/www/certbot:ro
- nginx_logs:/var/log/nginx
depends_on:
api:
condition: service_healthy
restart: always
volumes:
nginx_logs:
6. 启动测试
bash
# 用生产的 compose 起
docker compose -f docker-compose.prod.yml up -d --build
看容器状态:
bash
docker compose -f docker-compose.prod.yml ps
访问测试:
bash
# 用 -k 忽略自签证书警告
curl -k https://localhost/health/live
# {"status": "alive"}
# 检查 HTTP 跳转
curl -I http://localhost/health/live
# HTTP/1.1 301 Moved Permanently
# Location: https://localhost/health/live
# 检查 HTTPS 头
curl -kI https://localhost/health/live
# HTTP/2 200
# x-content-type-options: nosniff
测试限流:
bash
# 快速发 50 个请求
for i in $(seq 1 50); do
curl -k -s -o /dev/null -w "%{http_code}\n" https://localhost/health/live
done | sort | uniq -c
看到部分 503 说明限流生效。
7. 前后端一体部署示例
加一个前端目录:
bash
nginx/
└── www/
└── app/ # 前端打包产物
├── index.html
└── assets/
在 api.conf 里加前端路由:
nginx
server {
listen 443 ssl;
http2 on;
server_name localhost;
# ... ssl 配置
# 前端
root /var/www/app;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 后端 API
location /api/ {
proxy_pass http://api_backend;
# ... 反代头
}
}
访问 https://localhost → 前端页面;访问 https://localhost/api/... → 后端。
一个域名,前后端全打通。
十一、今日踩坑
proxy_pass末尾/忘了 → 路径多了/api前缀,404- 没传
X-Forwarded-For→ 后端拿到的 IP 是 Nginx 的 - FastAPI 没开
--proxy-headers→ 即使 Nginx 传了头,FastAPI 也不认 - HSTS 过早启用 → 证书过期或配置错误时,用户被锁在 HTTPS,访问不了
- 证书路径写错 → Nginx 启动直接报错
try_files写错 → SPA 刷新 404- 忘了
client_max_body_size→ 上传大文件报 413 - 限流配置过严 → 正常用户也被拦
- 日志没切分 → Nginx 日志文件几周涨到几 GB,需要
logrotate - SSL 证书 90 天后过期 → 忘了配自动续期,网站挂了
ssl_protocols没限制 → 兼容老 TLS 1.0 有安全风险- 暴露
.git目录 → 整个仓库可被下载 - 限流配在错误的 location → 静态文件也被限流,前端加载慢
- 忘了
service_healthy条件 → API 没起来 Nginx 就开始接请求,502
十二、今日总结
今天核心收获三件事:
- Nginx 反向代理是"生产环境的标配":HTTPS 终止 + 转发 + 限流 + 压缩,一次搞定
- HTTPS 用 Let's Encrypt 免费证书 + certbot 自动续期:0 成本上专业级 HTTPS
proxy_pass/X-Forwarded-*/try_files是 Nginx 的三大核心,必须掌握
一个感受:
前端到生产的最后一步,就是"通过一个正式域名对外服务"。
- 前端有 Vercel:
xxx.vercel.app- 后端有 Nginx:
api.example.com从"IP:端口"到"域名 + HTTPS",是"个人 demo"到"线上服务"的最后一步。
你的 API 现在能"正式上线"了:
- ✅ 域名 + HTTPS
- ✅ HTTP 自动跳转
- ✅ 反向代理 + 负载均衡
- ✅ 限流 + 压缩 + 安全头
- ✅ 前后端一体部署
Day 30 会把这一切在真实服务器上跑一遍,正式"上线"。
Day 29 完成度:✅
明日预告(Day 30)
- 正式上线 :
- 租服务器 + 域名解析
- 从零部署一个完整服务
- 上线检查清单
- 日常运维(日志、备份、监控)
- 阶段小结:Day 21-30 后端工程化复盘
准备了 30 天,明天正式上线。
最后
如果这篇对你有用,点个赞让我知道有人在看。
评论区互动: 你之前用过 Nginx 吗?踩过什么坑?欢迎分享。
有问题评论区见,我们第 30 天继续。
100 天,第 30 天见。
附录:系列目录
已完成 ✅
| 阶段 | 天数 | 内容 |
|---|---|---|
| 基础 | 01-06 | Python 语法、数据结构、函数、OOP、文件、依赖管理 |
| 网络 | 07-08 | HTTP 请求、异步 asyncio |
| 工程 | 09-12 | 正则、装饰器、Pydantic、日志调试 |
| 项目 | 13-14 | 命令行 AI 助手 v1 / v2 |
| 复盘 | 15 | 阶段总结 |
| 数据 | 16-17 | SQLite 数据库、SQLAlchemy ORM |
| 测试 | 18-19 | pytest 入门 + 进阶 |
| 工具 | 20 | CLI 打包与发布 |
| 后端 | 21-29 | FastAPI + Docker + CI/CD + Nginx + HTTPS ✅ |
进行中 🎯
| 阶段 | 天数 | 内容 |
|---|---|---|
| 后端 | 30 | 正式上线(阶段复盘) |
| 大模型 | 31-45 | LLM API + Prompt 工程 |
| RAG | 46-60 | 向量数据库 + 知识库 |
| Agent | 61-80 | LangChain / 工具调用 |
| 实战 | 81-100 | 完整 Agent 项目上线 |
100 天后见,一起从切图仔变成 Agent 开发者。