前端转 AI 100 天 Day 29:Nginx 反代 + HTTPS——让服务“能上公网“

前端转 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 做了什么:

  1. 验证域名所有权
  2. 申请证书
  3. 自动修改 Nginx 配置加 443 监听
  4. 设置 HTTP → HTTPS 跳转
  5. 配置自动续期

一条命令搞定。

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/... → 后端。

一个域名,前后端全打通。


十一、今日踩坑

  1. proxy_pass 末尾 / 忘了 → 路径多了 /api 前缀,404
  2. 没传 X-Forwarded-For → 后端拿到的 IP 是 Nginx 的
  3. FastAPI 没开 --proxy-headers → 即使 Nginx 传了头,FastAPI 也不认
  4. HSTS 过早启用 → 证书过期或配置错误时,用户被锁在 HTTPS,访问不了
  5. 证书路径写错 → Nginx 启动直接报错
  6. try_files 写错 → SPA 刷新 404
  7. 忘了 client_max_body_size → 上传大文件报 413
  8. 限流配置过严 → 正常用户也被拦
  9. 日志没切分 → Nginx 日志文件几周涨到几 GB,需要 logrotate
  10. SSL 证书 90 天后过期 → 忘了配自动续期,网站挂了
  11. ssl_protocols 没限制 → 兼容老 TLS 1.0 有安全风险
  12. 暴露 .git 目录 → 整个仓库可被下载
  13. 限流配在错误的 location → 静态文件也被限流,前端加载慢
  14. 忘了 service_healthy 条件 → API 没起来 Nginx 就开始接请求,502

十二、今日总结

今天核心收获三件事:

  1. Nginx 反向代理是"生产环境的标配":HTTPS 终止 + 转发 + 限流 + 压缩,一次搞定
  2. HTTPS 用 Let's Encrypt 免费证书 + certbot 自动续期:0 成本上专业级 HTTPS
  3. 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 开发者。

相关推荐
麦序子1 小时前
用 Python + python-docx 批量生成带表格样式的装修报价单 Word 文档(含合计公式)
开发语言·python·自然语言处理·word·python3.11
1-1=01 小时前
Python PyCharm
开发语言·python·pycharm
梦影_1 小时前
Langchain简单快速上手教程(九)—— 核心组件之少样本提示
人工智能·python·自然语言处理·中间件·langchain
机械设计扫地僧1 小时前
用PYTHON工具对FAG INA CS 系列薄壁轴承进行初步选型筛选
python
老孟说禅2 小时前
自动化测试入门(一)
开发语言·python
miofly2 小时前
GitHub 今日推荐|Snapintosh:用 ARM EL2 监控器骗过内核,让骁龙芯片笔记本跑 macOS
python·开源·github
打工仔折腾 AI2 小时前
Linux 网络编程:从零掌握 TCP Socket 编程与 C++ 实战落地
linux·网络·人工智能·后端·python·tcp/ip·性能优化
我命由我123452 小时前
Python 开发 - Python 项目部署(Windows 与 Linux)
java·linux·windows·后端·python·java-ee·python3.11
Zella折耳根2 小时前
实验2进阶版数据看板的设计与实现
python