Nginx 反向代理 Docker Nuxt 项目的完整配置过程
Category(分类): Backend Status: 已验证
本文记录一次完整的生产部署实践:Nuxt 应用运行在 Docker 容器中,宿主机使用 Nginx 提供公网入口、HTTP 到 HTTPS 跳转、TLS 证书加载和反向代理。
案例使用以下参数:
| 项目 | 值 |
|---|---|
| 操作系统 | Ubuntu / Debian 系 Linux |
| 公网域名 | articlet.rivers.pub |
| Nuxt 容器端口 | 3031 |
| 宿主机监听地址 | 127.0.0.1:3031 |
| HTTPS 入口 | Nginx 443 端口 |
| HTTP 入口 | Nginx 80 端口,重定向到 HTTPS |
| 证书文件 | /etc/nginx/ssl/articlet.rivers.pub_bundle.crt |
| 私钥文件 | /etc/nginx/ssl/articlet.rivers.pub.key |
本文假设 Nuxt 已经通过 Docker Compose 启动。Nginx 不运行在容器中,而是直接安装在宿主机上。
一、整体架构
请求链路如下:
浏览器
│
│ https://articlet.rivers.pub:443
▼
宿主机 Nginx
│
│ http://127.0.0.1:3031
▼
Docker 端口映射
│
▼
Nuxt / Nitro 容器
这种结构有几个优点:
- Nginx 统一处理 TLS、域名和公网端口;
- Nuxt 容器只需要处理应用请求;
- 应用端口只绑定到
127.0.0.1,不会直接暴露到公网; - 更新应用容器通常不需要修改 Nginx;
- 手动替换证书后只需重新加载 Nginx,无须重启应用。
Docker Compose 中建议这样映射端口:
services:
app:
ports:
- '127.0.0.1:3031:3031'
不要写成下面这样:
ports:
- '3031:3031'
后者会让 3031 监听宿主机所有网卡。即使云安全组暂时没有放行,也没有必要增加额外的公网暴露面。
二、部署前检查
1. 检查域名解析
在服务器执行:
getent hosts articlet.rivers.pub
结果应该指向当前云服务器的公网 IP。也可以在本地检查:
nslookup articlet.rivers.pub
如果域名解析尚未生效,应先在 DNS 服务商处添加 A 记录:
articlet.rivers.pub → 云服务器公网 IPv4
2. 检查云安全组
腾讯云安全组至少需要允许:
| 协议 | 端口 | 用途 |
|---|---|---|
| TCP | 22 | SSH 管理服务器 |
| TCP | 80 | HTTP 访问及跳转 HTTPS |
| TCP | 443 | HTTPS 访问 |
不需要开放 3031,因为该端口只允许宿主机 Nginx 访问。
3. 检查 Nuxt 容器
进入项目目录:
cd /root/techArticle_nuxt
检查容器:
docker compose ps
docker compose logs --tail=100 app
正常情况下应该看到类似输出:
STATUS: Up
Listening on http://0.0.0.0:3031
从宿主机直接访问 Nuxt:
curl --noproxy '*' -I http://127.0.0.1:3031/
预期返回:
HTTP/1.1 200 OK
这里显式使用 --noproxy '*',可以避免服务器 Shell 中的 HTTP 代理干扰本地回环地址测试。
在本地端口尚未返回正常响应前,不要继续配置 Nginx,否则最终只会得到 502 Bad Gateway。
三、安装和启动 Nginx
Ubuntu 或 Debian 可以执行:
apt update
apt install -y nginx
设置开机启动并立即启动:
systemctl enable --now nginx
检查状态:
systemctl status nginx --no-pager
nginx -v
检查端口占用:
ss -lntp | grep -E ':80|:443|:3031'
预期结构是:
- Nginx 监听
0.0.0.0:80和0.0.0.0:443; - Docker 映射只监听
127.0.0.1:3031。
四、准备 SSL 证书
本案例手动维护 SSL 证书,已有文件为:
/etc/nginx/ssl/articlet.rivers.pub_bundle.crt
/etc/nginx/ssl/articlet.rivers.pub.key
文件不要求必须重命名为 fullchain.pem 或 privkey.pem。Nginx 只关心配置中的路径是否准确、证书格式是否正确,以及证书与私钥是否匹配。
1. 创建目录
mkdir -p /etc/nginx/ssl
2. 上传证书
在本地电脑执行:
scp articlet.rivers.pub_bundle.crt \
root@服务器公网IP:/etc/nginx/ssl/articlet.rivers.pub_bundle.crt
scp articlet.rivers.pub.key \
root@服务器公网IP:/etc/nginx/ssl/articlet.rivers.pub.key
3. 设置权限
chmod 644 /etc/nginx/ssl/articlet.rivers.pub_bundle.crt
chmod 600 /etc/nginx/ssl/articlet.rivers.pub.key
证书可以公开读取,但私钥不应允许普通用户读取。私钥也绝对不能提交到 Git 仓库。
4. 检查证书信息
openssl x509 \
-in /etc/nginx/ssl/articlet.rivers.pub_bundle.crt \
-noout -subject -issuer -dates
检查证书是否覆盖当前域名:
openssl x509 \
-in /etc/nginx/ssl/articlet.rivers.pub_bundle.crt \
-noout -checkhost articlet.rivers.pub
预期输出:
Certificate will match
5. 检查证书与私钥是否匹配
分别计算证书公钥和私钥公钥的摘要:
openssl x509 \
-in /etc/nginx/ssl/articlet.rivers.pub_bundle.crt \
-pubkey -noout | openssl sha256
openssl pkey \
-in /etc/nginx/ssl/articlet.rivers.pub.key \
-pubout | openssl sha256
两条命令输出的 SHA256 必须一致。否则说明证书与私钥不是一对,Nginx 无法正常加载。
五、理解 sites-available 和 sites-enabled
Ubuntu、Debian 的 Nginx 通常采用以下目录结构:
/etc/nginx/sites-available/ 存放所有站点配置
/etc/nginx/sites-enabled/ 存放当前启用站点的符号链接
仅在 sites-available 中创建配置,并不代表站点已经启用。还需要在 sites-enabled 中创建符号链接:
/etc/nginx/sites-enabled/techarticle
→ /etc/nginx/sites-available/techarticle
这种设计允许保留站点配置,同时通过添加或删除符号链接快速启用、停用站点。
六、创建完整 Nginx 配置
创建配置文件:
nano /etc/nginx/sites-available/techarticle
写入以下内容:
server {
listen 80;
listen [::]:80;
server_name articlet.rivers.pub;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name articlet.rivers.pub;
ssl_certificate /etc/nginx/ssl/articlet.rivers.pub_bundle.crt;
ssl_certificate_key /etc/nginx/ssl/articlet.rivers.pub.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
location / {
proxy_pass http://127.0.0.1:3031;
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 X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header Connection "";
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
也可以通过 Here Document 一次写入:
cat > /etc/nginx/sites-available/techarticle <<'EOF'
server {
listen 80;
listen [::]:80;
server_name articlet.rivers.pub;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name articlet.rivers.pub;
ssl_certificate /etc/nginx/ssl/articlet.rivers.pub_bundle.crt;
ssl_certificate_key /etc/nginx/ssl/articlet.rivers.pub.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
location / {
proxy_pass http://127.0.0.1:3031;
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 X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header Connection "";
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
EOF
七、逐项解释配置
1. HTTP 跳转 HTTPS
server {
listen 80;
server_name articlet.rivers.pub;
return 301 https://$host$request_uri;
}
访问:
http://articlet.rivers.pub/articles/example
会跳转到:
https://articlet.rivers.pub/articles/example
$request_uri 会保留原始路径和查询参数。
2. 加载证书
ssl_certificate /etc/nginx/ssl/articlet.rivers.pub_bundle.crt;
ssl_certificate_key /etc/nginx/ssl/articlet.rivers.pub.key;
ssl_certificate 应使用包含完整证书链的 bundle 文件,否则部分浏览器或客户端可能提示证书链不完整。
3. 反向代理
proxy_pass http://127.0.0.1:3031;
这里的 127.0.0.1:3031 是宿主机上的 Docker 映射端口。由于 Nginx 运行在宿主机上,所以不能写 Compose 服务名 app:3031。
只有当 Nginx 也运行在同一个 Docker Compose 网络中时,才使用:
proxy_pass http://app:3031;
4. 保留原始域名
proxy_set_header Host $host;
Nuxt 收到的 Host 将保持为 articlet.rivers.pub,而不是 127.0.0.1:3031。
5. 传递客户端 IP
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
应用可以通过这些请求头获取客户端来源 IP。登录限流、访问日志和审计通常会使用这些信息。
6. 告诉应用原请求是 HTTPS
proxy_set_header X-Forwarded-Proto $scheme;
浏览器与 Nginx 之间使用 HTTPS,但 Nginx 到 Nuxt 使用本地 HTTP。该请求头用于告诉 Nuxt:外部原始协议是 HTTPS。
生产环境中的安全 Cookie 配置通常还需要:
NUXT_AUTH_COOKIE_SECURE=true
Secure Cookie 只能通过 HTTPS 发送,因此登录功能应使用正式域名验证,不应使用 HTTP IP 地址验证。
7. 超时时间
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_connect_timeout:Nginx 连接 Nuxt 的超时;proxy_send_timeout:Nginx 向 Nuxt 发送请求的超时;proxy_read_timeout:Nginx 等待 Nuxt 响应数据的超时。
八、启用站点
创建符号链接:
ln -s /etc/nginx/sites-available/techarticle \
/etc/nginx/sites-enabled/techarticle
为了避免重复执行时报错,可以写成:
test -L /etc/nginx/sites-enabled/techarticle \
|| ln -s /etc/nginx/sites-available/techarticle \
/etc/nginx/sites-enabled/techarticle
其中:
test -L检查符号链接是否存在;||表示前面的检查失败时才执行后面的命令;ln -s创建符号链接。
查看启用结果:
ls -l /etc/nginx/sites-enabled/
应该看到:
techarticle -> /etc/nginx/sites-available/techarticle
九、停用默认站点
Nginx 安装后通常启用了默认站点:
/etc/nginx/sites-enabled/default
可以删除默认站点的符号链接:
test -L /etc/nginx/sites-enabled/default \
&& unlink /etc/nginx/sites-enabled/default
其中:
&&表示链接存在时才执行unlink;unlink只删除sites-enabled中的符号链接;/etc/nginx/sites-available/default原始配置不会被删除。
停用默认站点可以避免域名匹配异常时显示 Nginx 默认欢迎页。
十、检查并应用配置
任何 Nginx 配置变更都应该先执行:
nginx -t
正常输出类似:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
确认成功后重新加载:
systemctl reload nginx
也可以合并为:
nginx -t && systemctl reload nginx
使用 reload 而不是 restart,Nginx 会平滑加载新配置,通常不会中断现有连接。
检查运行状态:
systemctl status nginx --no-pager
查看 Nginx 最终加载的完整配置:
nginx -T
十一、验证部署结果
1. 验证 HTTP 跳转
curl --noproxy '*' -I http://articlet.rivers.pub
预期结果:
HTTP/1.1 301 Moved Permanently
Location: https://articlet.rivers.pub/
2. 验证 HTTPS
curl --noproxy '*' -I https://articlet.rivers.pub
预期返回 200 OK。如果客户端和 Nginx 支持 HTTP/2,也可能显示:
HTTP/2 200
3. 验证证书
openssl s_client \
-connect articlet.rivers.pub:443 \
-servername articlet.rivers.pub </dev/null
重点检查:
- 证书域名是否正确;
- 证书链是否完整;
- 验证结果是否为
Verify return code: 0 (ok); - 到期时间是否正确。
4. 浏览器验证
依次检查:
http://articlet.rivers.pub是否自动跳转 HTTPS;- 首页是否正常渲染;
- 静态资源是否正常加载;
- 公开文章是否可以访问;
- 登录 Cookie 是否带有 Secure 属性;
- 登录后私有文章是否可见;
- 登出后私有内容是否重新隐藏;
- 中文、空格和特殊字符文章路径是否正常。
十二、缓存与登录态注意事项
该 Nuxt 项目的页面内容会根据登录 Cookie 区分公开内容和私有内容,因此不要在 Nginx 中为 HTML 或 /api/ 配置共享 proxy_cache。
危险示例:
location / {
proxy_cache my_cache;
proxy_pass http://127.0.0.1:3031;
}
如果缓存键没有正确包含身份信息,登录用户看到的私有页面可能被缓存后返回给匿名用户。
Nginx 默认不会自动开启 proxy_cache。没有明确需求时,保持不配置即可。公开静态资源可以根据应用返回的 Cache-Control 交给浏览器缓存。
十三、常见故障排查
1. 502 Bad Gateway
含义:Nginx 无法连接上游 Nuxt。
检查:
curl --noproxy '*' -I http://127.0.0.1:3031/
cd /root/techArticle_nuxt
docker compose ps
docker compose logs --tail=100 app
常见原因:
- 容器没有启动;
- Nuxt 启动失败;
- 端口映射不是
3031; proxy_pass地址写错;- 容器正在重新构建或重启。
2. Nginx 配置测试失败
执行:
nginx -t
常见原因:
- 配置缺少分号;
- 花括号不匹配;
- 证书路径错误;
- 私钥格式错误;
- 证书与私钥不匹配;
- 同一地址和域名存在冲突配置。
3. 显示 Nginx 默认欢迎页
检查启用目录:
ls -l /etc/nginx/sites-enabled/
确认:
techarticle链接存在;- 默认站点已停用;
server_name拼写正确;- 修改后已经执行
nginx -t && systemctl reload nginx。
4. HTTPS 无法访问
检查监听:
ss -lntp | grep ':443'
检查云安全组是否放行 TCP 443,并查看日志:
tail -n 100 /var/log/nginx/error.log
5. 登录后仍然没有 Cookie
检查:
- 是否通过
https://articlet.rivers.pub访问; - Compose 是否设置
NUXT_AUTH_COOKIE_SECURE=true; - Nginx 是否传递
X-Forwarded-Proto $scheme; - 浏览器中是否存在过期 Cookie;
- 认证数据库目录是否可写。
查看应用日志:
cd /root/techArticle_nuxt
docker compose logs --tail=100 app
6. curl 输出受到代理影响
服务器可能设置了 HTTP_PROXY、HTTPS_PROXY 或 ALL_PROXY。测试本机和正式域名时可以显式绕过代理:
curl --noproxy '*' -I http://127.0.0.1:3031/
curl --noproxy '*' -I https://articlet.rivers.pub/
十四、查看访问日志和错误日志
实时查看访问日志:
tail -f /var/log/nginx/access.log
实时查看错误日志:
tail -f /var/log/nginx/error.log
查看最近 100 行:
tail -n 100 /var/log/nginx/access.log
tail -n 100 /var/log/nginx/error.log
查看 Nuxt 日志:
cd /root/techArticle_nuxt
docker compose logs -f app
排查请求问题时,可以同时观察 Nginx 和 Nuxt 日志,确认请求是停在公网入口、反向代理层,还是应用层。
十五、更新应用
代码推送到 Git 仓库后,服务器执行:
cd /root/techArticle_nuxt
git pull --ff-only origin main
docker compose --progress=plain build app \
&& docker compose up -d
检查:
docker compose ps
docker compose logs --tail=100 app
curl --noproxy '*' -I http://127.0.0.1:3031/
curl --noproxy '*' -I https://articlet.rivers.pub/
应用更新通常不需要重新加载 Nginx。只有域名、证书、上游端口或 Nginx 配置发生变化时,才需要:
nginx -t && systemctl reload nginx
十六、手动更新 SSL 证书
新证书签发后,替换:
/etc/nginx/ssl/articlet.rivers.pub_bundle.crt
/etc/nginx/ssl/articlet.rivers.pub.key
替换前先检查新证书:
openssl x509 \
-in /etc/nginx/ssl/articlet.rivers.pub_bundle.crt \
-noout -subject -issuer -dates \
-checkhost articlet.rivers.pub
然后检查并平滑加载:
nginx -t && systemctl reload nginx
查看当前证书到期时间:
openssl x509 \
-in /etc/nginx/ssl/articlet.rivers.pub_bundle.crt \
-noout -enddate
更新证书不需要重启 Docker 容器。
十七、最终检查清单
部署完成后逐项确认:
- 域名解析到正确公网 IP;
- 腾讯云安全组放行 TCP 80、443;
- Nuxt 容器处于
Up状态; -
127.0.0.1:3031返回200; - 3031 没有直接暴露到公网;
- 证书覆盖
articlet.rivers.pub; - 证书与私钥匹配;
-
sites-enabled/techarticle符号链接存在; - Nginx 默认站点已停用;
-
nginx -t检查成功; - HTTP 自动跳转 HTTPS;
- HTTPS 页面返回
200; - 登录、登出和私有文章权限正常;
- HTML 和
/api/没有启用共享代理缓存; - 已记录证书到期时间和更新流程。
总结
宿主机 Nginx 反向代理 Docker Nuxt 的核心步骤是:
- Nuxt 容器只绑定
127.0.0.1:3031; - Nginx 监听公网
80和443; - HTTP 请求通过
301跳转 HTTPS; - Nginx 加载证书和私钥;
- HTTPS 请求转发到
http://127.0.0.1:3031; - 通过
X-Forwarded-*请求头保留域名、协议和客户端 IP; - 在
sites-enabled中创建符号链接启用站点; - 每次修改后先执行
nginx -t,成功后再 reload; - 登录态页面和 API 不启用共享代理缓存;
- 证书更新只需替换文件并重新加载 Nginx。
对于单机部署的 Nuxt SSR 应用,这种结构简单、清晰,既保留了 Docker 的环境隔离,也利用了 Nginx 成熟的 TLS 和反向代理能力。