目录
- 一、为什么需要反向代理
- 二、反向代理基础配置
- 三、Let’s Encrypt 免费 SSL:Certbot 安装与申请
- 四、SSL 安全加固
- 五、自动续期:90 天证书不用手动换
- 六、常见踩坑与解决
- 七、完整验证流程
一、为什么需要反向代理
很多人在部署 Web 服务时,习惯直接让应用监听 80/443 端口。但实际生产环境中,Nginx 作为反向代理几乎是标准做法——它负责处理 SSL termination、静态资源缓存、负载均衡,而背后的应用服务(如 WordPress、Django、Node.js)只需要监听本地端口。
直接让应用监听 443 听起来简单,但实际问题一堆:
| 场景 | 直接暴露应用 | Nginx 反向代理 |
|---|---|---|
| SSL 证书管理 | 每个应用各自配 | 统一在 Nginx 层管理 |
| 静态资源 | 应用自行处理 | Nginx 直接服务,节省应用资源 |
| 端口隐藏 | 内部服务端口暴露在外 | 只开放 80/443,数据中心网络更安全 |
| 负载均衡 | 应用自带难搞 | upstream 轻松扩展 |
| HTTP/2、缓存控制 | 应用支持参差不齐 | Nginx 统一处理 |
WordPress 还有一个特殊问题:REST API 的认证依赖 Authorization header。如果 Nginx 没有正确转发这个 header,所有基于 Application Passwords 的 API 调用(创建文章、上传媒体)都会返回 401。这是最常见的 WordPress + Nginx 部署坑。
二、反向代理基础配置
完整配置文件
以 WordPress 部署在 127.0.0.1:21080 为例,完整配置如下:
# /etc/nginx/conf.d/wordpress.conf
upstream wordpress_backend {
server 127.0.0.1:21080;
keepalive 32;
}
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# HTTP -> HTTPS 跳转(推荐 301 永久重定向)
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
# SSL 证书(Certbot 自动写入)
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# 安全响应头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# 反向代理核心配置
location / {
proxy_pass http://wordpress_backend;
proxy_http_version 1.1;
# —— 以下 header 缺一不可 ——
# 1. Host:让后端知道请求的原始域名
proxy_set_header Host $host;
# 2. X-Real-IP:后端记录真实访客 IP,而不是 Nginx 的 IP
proxy_set_header X-Real-IP $remote_addr;
# 3. X-Forwarded-For:传递经过的每一个代理 IP 链
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 4. X-Forwarded-Proto:后端区分真实协议(http 还是 https)
proxy_set_header X-Forwarded-Proto $scheme;
# 5. —— 最容易踩坑的:Authorization header —— #
# 没有这行,所有 WordPress REST API 的认证请求全部 401
proxy_set_header Authorization $http_authorization;
# 6. 升级 WebSocket(如果用到)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时控制
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 静态资源由 Nginx 直接服务,不走代理
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
逐行解释关键 header
很多人知道要配 proxy_set_header,但不清楚每一个的实际作用:
proxy_set_header Host $host
Nginx 默认会把自己的 proxy_pass 地址当作 Host 发送给后端。比如你 proxy_pass http://127.0.0.1:21080,后端收到的 Host 就是 127.0.0.1:21080,WordPress 生成的链接全变成了内网地址。$host 变量保留原始请求的域名。
proxy_set_header X-Real-IP $remote_addr
Nginx 是反向代理,后端看到的连接来源全是 127.0.0.1。$remote_addr 是真实访客的 IP,把它放进 header 让后端能够记录真实访客。
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for
如果访客经过了多层代理(比如 A -> B -> Nginx -> 后端),这个 header 会累积整条链路的所有 IP:client_ip, proxy1_ip, proxy2_ip。$proxy_add_x_forwarded_for 会自动追加当前层的 IP。
proxy_set_header X-Forwarded-Proto $scheme
后端应用看到的所有请求都是 http(因为它确实跑在 http 上)。$scheme 会保留原始协议(https),这样 WordPress 才能生成正确的链接。
proxy_set_header Authorization $http_authorization
这是 WordPress REST API 认证失败最常见的原因。Nginx 默认不会转发非标准 header(以 X- 开头的和 Authorization)。不写这行,Authorization: Basic xxx 到后端就变成空值,所有 API 新建/编辑/删除操作全部 401。
三、Let’s Encrypt 免费 SSL:Certbot 安装与申请
Let’s Encrypt 提供永久免费的 DV SSL 证书,有效期 90 天,Certbot 自动续期后实际永久有效。
方式一:certonly(不修改 Nginx 配置)
适合手动管理 Nginx 配置的场景:
# 安装 certbot(snap 方式,自动获取最新版本)
sudo snap install core
sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
# 申请证书(仅获取证书,不修改 Nginx 配置)
sudo certbot certonly --nginx -d yourdomain.com -d www.yourdomain.com
按提示输入邮箱(用于过期通知),同意条款,稍等片刻证书就到手了。
方式二:–nginx(自动写入 Nginx 配置)
Certbot 直接修改 Nginx 配置文件,插入 SSL 配置和 80→443 重定向,适合首次配置:
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
执行后 Certbot 会自动:
- 验证域名所有权(通过 HTTP/.well-known/acme-challenge/)
- 申请证书
- 修改 Nginx 配置,插入
listen 443 ssl http2和证书路径 - 生成 HTTP→HTTPS 跳转配置
验证证书申请成功
sudo certbot certificates
输出类似:
Certificate Name: yourdomain.com
Serial Number: xxx
Key Type: RSA
Domains: yourdomain.com www.yourdomain.com
Expiry Date: 2026-08-06 12:34:56+00:00 (89 days)
Certificate Path: /etc/letsencrypt/live/yourdomain.com/fullchain.pem
Private Key Path: /etc/letsencrypt/live/yourdomain.com/privkey.pem
四、SSL 安全加固
Certbot 自动生成的配置能用,但安全配置偏保守。下面是生产环境推荐加固项:
TLS 版本控制
只启用 TLS 1.2 和 1.3,禁用 TLS 1.0/1.1 和所有 SSL 版本:
ssl_protocols TLSv1.2 TLSv1.3;
加密套件选择
使用 Mozilla SSL Configuration Generator 的”Modern”级别:
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
会话复用
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
HSTS(HTTP 严格传输安全)
强制浏览器只通过 HTTPS 访问,从根本上避免 HTTP 降级攻击:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
注意:开启 HSTS 后必须保证所有子域名都支持 HTTPS,否则子域名会无法访问。先用 max-age=300 测试一周,确认无误再放大。
OCSP Stapling
让服务器直接提供 CRL 检查结果,省去浏览器自己去 CA 查询,提升 HTTPS 连接速度:
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
完整 SSL 配置块示例
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# TLS 加固
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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# 安全响应头
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 反向代理(省略同前文)
location / {
proxy_pass http://wordpress_backend;
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_set_header Authorization $http_authorization;
}
}
验证 SSL 评分
配置完成后,去 SSL Labs SSL Test 输入域名,检查是否达到 A+ 或 A。
五、自动续期:90 天证书不用手动换
Let’s Encrypt 证书 90 天有效期,Certbot 带自动续期,但需要验证定时任务已生效:
# 测试续期流程(dry-run 不会真的续)
sudo certbot renew --dry-run
成功输出:
** DRY RUN: simulating 'certbot renew' close enough to determine
Congratulations, all renewals succeeded.
定时任务确认
Certbot 安装时会自动写入 systemd timer 或 cron。检查一下:
# systemd timer 方式(Ubuntu 18.04+)
sudo systemctl list-timers | grep certbot
如果没有自动创建,手动加 cron:
sudo crontab -e
# 每天凌晨 3 点检查,到期自动续
0 3 * * * /usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
--deploy-hook 是关键:续期成功后自动 reload Nginx,让新证书生效。
六、常见踩坑与解决
坑 1:WordPress REST API 返回 401
症状:GET /wp-json/wp/v2/posts 正常,但 POST /wp-json/wp/v2/posts 返回 401。
原因:Nginx 没有转发 Authorization header。
解决:在 location / 里加上:
proxy_set_header Authorization $http_authorization;
然后 sudo nginx -t && sudo systemctl reload nginx。
坑 2:申请证书时报 Timeout during connect(域名验证超时)
原因:Let’s Encrypt 服务器无法访问 http://yourdomain.com/.well-known/acme-challenge/xxx。
排查:
# 本地验证 Nginx 是否正常服务 .well-known 路径
curl -v http://yourdomain.com/.well-known/acme-challenge/test
解决:确认防火墙开放 80 端口;确认 Nginx 配置里没有把 .well-known 路径重定向到 HTTPS(Certbot 验证时只能是 HTTP)。
坑 3:多个子域名申请一张证书
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com -d blog.yourdomain.com
所有域名共享同一张证书,不需要分别申请。
坑 4:proxy_pass 用 localhost 还是 127.0.0.1
在容器环境(如 Docker)中,localhost 有时解析到 IPv6 ::1,而应用只监听 IPv4。
建议:始终用 127.0.0.1 或 upstream 名称,避免歧义。
# 避免
proxy_pass http://localhost:21080; # 可能走 IPv6
# 推荐
proxy_pass http://127.0.0.1:21080;
# 或
proxy_pass http://wordpress_backend; # upstream 中定义
坑 5:Nginx 配置变更后不生效
# 每次改完必须验证语法
sudo nginx -t
# 语法无误再重载
sudo systemctl reload nginx
用 nginx -t 能发现大多数配置错误,避免重载失败导致服务中断。
坑 6:续期成功但证书路径变了
用 Certbot 的 --deploy-hook 确保每次续期后 reload Nginx:
0 3 * * * /usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
如果不用 --deploy-hook,即使证书文件更新了,Nginx 还在用旧证书。
七、完整验证流程
配置完成后,按以下步骤逐一验证:
# 1. Nginx 语法检查
sudo nginx -t
# 2. 重载配置
sudo systemctl reload nginx
# 3. HTTP→HTTPS 跳转验证
curl -I http://yourdomain.com
# 期望:HTTP/1.1 301 Moved Permanently,Location: https://...
# 4. HTTPS 证书信息
curl -Ik https://yourdomain.com
# 期望:SSL handshake successful,证书域名匹配
# 5. WordPress REST API 认证
curl -s -o /dev/null -w "%{http_code}" \
-u "252009134:YOUR_APP_PASSWORD" \
https://yourdomain.com/wp-json/wp/v2/users/me
# 期望:200(返回用户信息)
# 6. SSL Labs 评分
# 浏览器访问 https://www.ssllabs.com/ssltest/analyze.html?d=yourdomain.com
# 期望:A+ 或 A
# 7. 续期 dry-run
sudo certbot renew --dry-run
# 期望:Congratulations, all renewals succeeded
总结
Nginx 反向代理 + SSL 不是”锦上添花”,而是生产环境的标准配置。它带来的价值:统一入口、隐藏内部结构、简化证书管理、更好的性能和安全性。
最核心的 takeaways:
proxy_set_header Authorization $http_authorization— WordPress API 认证能否工作的分水岭snap install certbot— 最省心的安装方式,自动更新--deploy-hook "systemctl reload nginx"— 证书续期后必须自动重载nginx -t验证语法 — 每次改配置后必做
搞懂这些,生产环境的 Web 服务部署基本不会再踩坑。
评论区