Nuxt 项目部署到宝塔:PM2、Nginx 与 HTTPS
从构建完整产物到配置 PM2、Nginx 和 HTTPS,介绍 Nuxt SSR 项目部署到宝塔的完整流程,并提供更新发布、版本回退和常见故障排查方法。文中使用通用域名与路径示例。
本文使用虚构的项目名、域名和通用路径,介绍 Nuxt SSR 项目通过宝塔、PM2 和 Nginx 部署的完整流程。示例按 npm 包管理器和 Nitro Node 服务构建方式编写。
1. 示例配置与占位符
| 项目 | 示例配置 |
|---|---|
| 本地源码目录 | /path/to/nuxt-app |
| 服务器部署目录 | /www/wwwroot/nuxt-app |
| 宝塔项目名称 / PM2 进程名 | nuxt-app |
| 网站地址 | https://app.example.com |
| 生产接口地址 | https://api.example.com |
| Nuxt 监听地址 | 127.0.0.1:3000 |
| 运行用户 | www |
| Node 版本占位符 | NODE_VERSION,替换成面板安装的兼容版本目录名 |
| Node 可执行文件 | /www/server/nodejs/NODE_VERSION/bin/node |
操作前,把示例域名、源码路径、部署目录和 NODE_VERSION 替换成自己的配置。example.com 是示例域名,不能用于申请自己的证书。NODE_VERSION 不是命令自动识别的变量,需要手动替换成真实版本目录名。
请求经过的服务:
text
访客 → 网站域名的 Nginx(80 / 443)→ Nuxt(127.0.0.1:3000)
↓
后端接口(api.example.com)
SSR 渲染时,Nuxt 服务端也会请求后端;浏览器中的交互也可能直接请求后端。这里使用两端都能访问的完整 HTTPS 接口地址。
2. 准备服务器与目录
在宝塔中准备好:
- Nginx。
- 项目兼容的 Node.js 版本,可通过「网站 → Node 项目 → Node 版本管理器」安装。
- Node 项目中的 PM2 管理功能。支持的面板版本会提供「PM2 项目」入口,具体位置以面板界面为准。
- 将网站域名解析到服务器,确保云安全组及服务器防火墙允许网站使用的 80、443 端口。
需要执行本文的 pm2 命令时,在 Node 项目页把「命令行版本」设为对应的 Node 版本,再打开新的宝塔终端,确认 node -v 和 pm2 -v 能正常输出。
Nuxt 监听本机 127.0.0.1:3000,由 Nginx 转发访问,因此无需对公网放行 3000 端口。
先通过宝塔文件管理器或服务器终端创建独立目录:
bash
mkdir -p /www/wwwroot/nuxt-app
部署完成后的主要结构:
text
/www/wwwroot/nuxt-app/
├── .output/
│ ├── public/ # 前端静态资源
│ ├── server/ # 服务端代码与构建时收集的依赖
│ │ └── index.mjs # Nuxt 实际入口
│ └── nitro.json
├── ecosystem.config.cjs # PM2 运行配置
├── .well-known/
│ └── acme-challenge/ # SSL 文件验证目录
└── deploy-backups/ # 发布与配置备份
压缩包、__MACOSX 等归档附属文件也可以放在 nuxt-app 内。运行所需的是完整的 .output 和 PM2 配置;已有归档文件可以按自己的备份习惯保留。
3. 确认 Nuxt 的接口配置
在 nuxt.config.ts 中声明可覆盖的接口地址,例如:
ts
export default defineNuxtConfig({
ssr: true,
runtimeConfig: {
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || 'http://localhost:8080',
},
},
})
请求封装通过 useRuntimeConfig().public.apiBase 读取接口地址。生产环境示例:
dotenv
NUXT_PUBLIC_API_BASE=https://api.example.com
例如文章列表的最终地址是:
text
https://api.example.com/articles
环境变量名 NUXT_PUBLIC_API_BASE 对应 runtimeConfig.public.apiBase。其他配置键需要使用对应的 Nuxt 环境变量名。
开发和构建时,Nuxt 会读取源码根目录中的 .env。直接启动构建后的服务器时,Nuxt 不会自动读取 .env,生产变量应由启动进程传入。本文统一放在下一节的 PM2 配置中。Nuxt 环境变量说明
本教程采用完整接口地址。若改为 /api 等相对地址,还需要处理 Nuxt 服务端的代理路由;只给浏览器所在域名配置 Nginx 代理,并不能保证 SSR 内部请求也能访问该路径。
4. 构建并上传完整产物
4.1 本地构建
在电脑终端执行:
bash
cd /path/to/nuxt-app
npm ci
npm run build
示例假定项目已有 package-lock.json,因此使用 npm ci 安装锁定版本的依赖。使用 pnpm 或 yarn 的项目应采用对应的安装、构建命令和锁文件。日常依赖未变化时,可以直接重新构建。
确认构建结果包含:
text
.output/public/
.output/server/index.mjs
.output/server/node_modules/
Nuxt 的 Node 服务入口是 .output/server/index.mjs。服务器运行时需要完整 .output;静态资源、服务端代码和收集到的依赖应一起上传。Nuxt Node 部署说明
如果构建环境自动选择了其他托管平台的 preset,可以明确使用:
bash
NITRO_PRESET=node-server npm run build
4.2 打包
在源码根目录执行,macOS 可以使用:
bash
COPYFILE_DISABLE=1 tar --exclude='.DS_Store' -czf nuxt-app-output.tar.gz .output
这样打包会包含隐藏的 .output 目录,并减少 macOS 附属文件。也可以直接上传完整 .output 文件夹,或使用已有的 .output.zip。
4.3 上传和解压
通过宝塔「文件」进入:
text
/www/wwwroot/nuxt-app
上传压缩包,并在这个目录内解压。使用上述 tar 包时,服务器终端执行:
bash
cd /www/wwwroot/nuxt-app
tar -xzf nuxt-app-output.tar.gz
ls -l .output/server/index.mjs
正确入口是:
text
/www/wwwroot/nuxt-app/.output/server/index.mjs
确认没有多套一层同名文件夹。宝塔文件管理器需要显示隐藏文件,才能看到 .output。
本地构建后可以上传完整产物。若项目包含原生二进制依赖,部署时出现平台不兼容错误,应在与服务器兼容的 Linux 环境重新安装依赖并构建。
5. 创建 PM2 配置
在服务器的 /www/wwwroot/nuxt-app 下新建 ecosystem.config.cjs:
js
module.exports = {
apps: [
{
name: 'nuxt-app',
cwd: '/www/wwwroot/nuxt-app',
script: '/www/wwwroot/nuxt-app/.output/server/index.mjs',
interpreter: '/www/server/nodejs/NODE_VERSION/bin/node',
uid: 'www',
gid: 'www',
exec_mode: 'fork',
instances: 1,
watch: false,
autorestart: true,
max_memory_restart: '512M',
out_file: '/www/wwwlogs/nodejs/nuxt-app/out.log',
error_file: '/www/wwwlogs/nodejs/nuxt-app/err.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
env: {
NODE_ENV: 'production',
NITRO_HOST: '127.0.0.1',
NITRO_PORT: '3000',
NUXT_PUBLIC_API_BASE: 'https://api.example.com',
},
},
],
}
服务器终端创建日志目录,并检查配置语法:
bash
mkdir -p /www/wwwlogs/nodejs/nuxt-app
/www/server/nodejs/NODE_VERSION/bin/node --check /www/wwwroot/nuxt-app/ecosystem.config.cjs
主要字段含义:
| 字段 | 用途 |
|---|---|
name |
PM2 进程名,保持与宝塔项目名一致 |
cwd |
进程工作目录 |
script |
Nuxt 真正的启动入口 |
interpreter |
明确使用哪个 Node 可执行文件 |
uid / gid |
使用 www 用户与用户组运行应用 |
instances: 1 |
使用一个实例,可根据服务器容量调整 |
watch: false |
上传文件时不自动触发反复重启 |
max_memory_restart |
超过设置的内存阈值后由 PM2 重启应用,并非操作系统硬性内存限制 |
env |
生产运行环境变量 |
使用 .cjs 扩展名,可以明确采用 CommonJS 的 module.exports 写法。本教程后续以这个文件作为运行参数的统一来源。修改其 env 后,应重启应用让参数生效。PM2 配置说明
6. 在宝塔创建 PM2 项目
入口:网站 → Node 项目 → 添加项目 → PM2 项目。
| 表单字段 | 填写内容 |
|---|---|
| 项目名称 | nuxt-app |
| Node 版本 | 选择与 interpreter 对应的已安装版本 |
| 启动文件 | /www/wwwroot/nuxt-app/ecosystem.config.cjs |
| 运行目录 | /www/wwwroot/nuxt-app |
| 负载实例数量 | 1 |
| 内存上限 | 512 MB,根据实际容量调整 |
| 自动重载 | 关闭 |
| 不安装 node_module | 勾选,使用完整构建产物 |
点击表单内的 「更多配置」,继续填写:
| 字段 | 填写内容 |
|---|---|
| 参数 | 留空 |
| 环境变量 | 已在 ecosystem.config.cjs 中集中设置,可留空;已有同名配置时保持一致 |
| 运行用户 | www |
| 项目端口 | 3000 |
| 放行端口 | 不勾选 |
| 绑定域名 | app.example.com,不带协议和路径 |
宝塔表单的「启动文件」填写 PM2 配置文件;配置文件中的 script 再指向 Nuxt 的 index.mjs。
不同宝塔版本处理启动文件的方式可能不同。如果直接填写 index.mjs 后,出现进程被命名为 index、表单变量未生效或状态识别不一致,应检查实际启动命令。显式的 ecosystem 配置便于统一进程名和运行参数,最终以运行中的进程信息为准。
保存创建后确认:
- 项目显示「运行中」,有 PID。
- 根目录显示
/www/wwwroot/nuxt-app。 - 项目日志里没有持续报错或重复重启。
在宝塔终端中也可以核对:
bash
pm2 list
pm2 describe nuxt-app
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/articles
本机页面应返回 200。Nuxt 的端口、监听地址可以由 NITRO_PORT 和 NITRO_HOST 设置。Nuxt 服务运行参数
确认进程正确后保存 PM2 列表:
bash
pm2 save
pm2 save 保存的是进程列表。服务器重启后的自动恢复,还需要宝塔或 PM2 的开机启动机制支持,应单独确认。
7. 检查 Nginx 外网映射
进入项目设置,检查域名管理和外网映射。网站域名应代理到:
text
http://127.0.0.1:3000
最终反向代理应具有下面这样的效果:
nginx
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
宝塔通常会生成代理配置,可能放在主配置引用的文件里。若已经存在 location /,检查或修改已有规则,避免重复添加相同的 location。
页面路由由 Nuxt 接收,例如 /articles、/search;实际测试时使用项目已有的路由。保持请求路径完整地转发到 Nuxt。
SSL 文件验证目录
示例项目的宝塔 Node 站点主配置文件通常类似:
text
/www/server/panel/vhost/nginx/node_nuxt-app.conf
在对应的 server { ... } 内补充:
nginx
location ^~ /.well-known/acme-challenge/ {
root /www/wwwroot/nuxt-app;
default_type text/plain;
try_files $uri =404;
}
同时确保目录存在:
bash
mkdir -p /www/wwwroot/nuxt-app/.well-known/acme-challenge
映射关系为:
text
请求:/.well-known/acme-challenge/某个验证文件
文件:/www/wwwroot/nuxt-app/.well-known/acme-challenge/某个验证文件
这里的 ^~ 使这条匹配到的最长前缀规则跳过同层正则 location 的检查,验证请求会由这个目录处理。Nginx location 说明
修改前备份站点配置,并保留宝塔原有的 SSL 管理标记、证书验证 include 和其他设置。以上是局部片段,不应直接覆盖整个站点配置。
先检查语法:
bash
/www/server/nginx/sbin/nginx -t
只有显示检查通过后,再执行重载:
bash
/www/server/nginx/sbin/nginx -s reload
8. 申请 SSL 并开启 HTTPS
- 确认宝塔项目处于「运行中」。
- 确认
http://app.example.com能访问,域名解析正确。 - 打开项目设置 → SSL → 免费证书 → 申请证书。
- 品牌选择 Let's Encrypt,验证方式选择 文件验证。
- 勾选
app.example.com,申请并完成部署;如果签发后没有自动部署,再点击该证书的「部署」。 - 打开
https://app.example.com确认证书有效,再开启「强制 HTTPS」。
常见的 SSL 配置问题有两类:
- 提示先启动项目:先修复宝塔识别不到进程的问题,再进入 SSL 设置。
- 验证 URL 返回 404:检查验证文件实际写入目录,确保与上面的 Nginx
root一致。
需要验证路径时,可以在该目录手动创建一个临时文本文件,例如 nuxt-acme-path-check.txt,写入 nuxt-acme-ok,然后访问:
text
http://app.example.com/.well-known/acme-challenge/nuxt-acme-path-check.txt
返回相同文本后,删除自己创建的临时测试文件。这个检查只用于核对目录映射,不代替证书机构生成的实际验证文件。
项目目录迁移后,宝塔项目运行目录与 Nginx 验证目录也要同步更新,供后续文件验证和续签使用。
9. 部署后验证
9.1 网页、HTTPS 与静态资源
在浏览器中检查:
- 访问 HTTP 地址后能跳转到 HTTPS。
- 刷新
/articles、/search?q=example等地址正常。 - 文章列表、分类、搜索结果正常。
- 开发者工具 Network 中的
/_nuxt/JavaScript 和 CSS 能正常加载。
服务器或电脑终端可以执行:
bash
curl -sS -L -o /dev/null -w '%{url_effective} %{http_code}\n' http://app.example.com/articles
应最终到达 HTTPS 页面并返回 200。
9.2 后端接口
bash
curl -sS 'https://api.example.com/articles?page=1&pageSize=10'
将示例路径 /articles 替换成后端实际提供的公开接口,并按后端约定检查 JSON 内容。例如,若后端以 code: 200 表示成功,应同时检查这个业务字段。HTTP 状态为 200 本身不能证明业务请求成功。
跨域响应头可以这样检查:
bash
curl -sS -D - -o /dev/null \
-H 'Origin: https://app.example.com' \
'https://api.example.com/articles?page=1&pageSize=10'
后端应按业务需要允许示例前端 Origin,并返回相应的跨域响应头。浏览器还需实际完成一次搜索等交互,确认客户端请求正常。
诊断公开后端接口时,优先使用真实的 GET 请求。curl -I 发送的是 HEAD,而后端可能对 HEAD 和 GET 使用不同的鉴权规则。
9.3 进程与日志
bash
pm2 describe nuxt-app
tail -n 80 /www/wwwlogs/nodejs/nuxt-app/err.log
确认进程入口和工作目录都在 /www/wwwroot/nuxt-app 下,进程持续运行,日志没有新增的启动错误。
10. 后续更新与回退
更新发布
- 本地重新执行
npm run build,打包新的完整.output。 - 把新的
nuxt-app-output.tar.gz上传到/www/wwwroot/nuxt-app。 - 在宝塔中停止
nuxt-app项目,准备切换产物。 - 在服务器终端运行下面的脚本。脚本先解压检查新入口,再备份旧产物并切换。
bash
bash <<'BASH'
set -euo pipefail
cd /www/wwwroot/nuxt-app
test -f nuxt-app-output.tar.gz
nuxt_release_dir=$(mktemp -d /www/wwwroot/nuxt-app/.release.XXXXXX)
tar -xzf nuxt-app-output.tar.gz -C "$nuxt_release_dir"
test -f "$nuxt_release_dir/.output/server/index.mjs"
test -d "$nuxt_release_dir/.output/public"
nuxt_deploy_stamp=$(date +%Y%m%d-%H%M%S)
mkdir -p deploy-backups
mv .output "deploy-backups/output-$nuxt_deploy_stamp"
mv "$nuxt_release_dir/.output" .output
printf '旧产物备份:%s\n' "/www/wwwroot/nuxt-app/deploy-backups/output-$nuxt_deploy_stamp"
printf '临时解压目录:%s\n' "$nuxt_release_dir"
BASH
- 确认脚本成功后,从宝塔启动项目;检查网页、接口、日志,再执行
pm2 save。 - 临时解压目录和归档文件可以在确认发布成功后清理,上一版产物按自己的备份周期保留。
更新时保留 ecosystem.config.cjs、.well-known 和备份目录。只有运行变量变化时才修改 ecosystem 文件;更改域名、目录、监听端口或证书配置时,再检查对应的 Nginx 设置。
如果是只修改 PM2 环境变量,可以在配置文件保存后,通过宝塔重启项目;也可以在同一套 PM2 管理环境中运行:
bash
pm2 restart /www/wwwroot/nuxt-app/ecosystem.config.cjs --update-env
pm2 save
更新失败时回退
- 在宝塔中停止项目。
- 将当前有问题的
.output移到deploy-backups下,使用一个新的名字保存。 - 找到刚才打印的
deploy-backups/output-时间戳,把它移回.output。 - 如果同时改过运行配置,恢复对应的配置备份。
- 启动项目,重新检查页面、接口与日志。
如果脚本在解压或入口检查阶段失败,旧 .output 尚未切换;检查错误后可以直接启动原有项目。
11. 常见故障对照
| 现象 | 优先检查 | 处理方向 |
|---|---|---|
| HTTP 能访问,宝塔显示未启动 | pm2 list 中的名字与宝塔项目名是否一致 |
使用显式 ecosystem 文件,统一为 nuxt-app,再通过宝塔保存配置并重启 |
| 提示启动成功,但仍显示未启动 | 是否只是启动命令执行成功;日志里实际进程叫什么 | 结合 PM2 状态、PID、端口和错误日志判断,不只看提示框 |
| SSL 提示先启动项目 | 宝塔是否已正确识别进程 | 先修复 PM2 名称与状态,再配置 SSL |
| SSL 文件验证 404 | 实际验证文件路径与 Nginx root 是否一致 | 检查 .well-known/acme-challenge 映射,修正后测试并重载 Nginx |
| Nginx 返回 502 | Nuxt 是否运行、是否监听 3000、代理目标是否一致 | 检查本机 curl http://127.0.0.1:3000 和项目错误日志 |
| 日志出现 EADDRINUSE | 3000 端口是否被另一个进程占用 | 使用 ss -lntp 查清占用进程归属,再处理重复启动 |
| 页面有内容,浏览器交互请求失败 | 实际接口地址、HTTPS 证书、跨域响应 | 检查完整请求 URL 和返回内容;核对 ecosystem 的 env 是否生效 |
| 接口请求指向 localhost:8080 | 运行环境仍使用源码中的默认值 | 传入 NUXT_PUBLIC_API_BASE 并重启;公共配置会被浏览器使用 |
/_nuxt/ 资源 404 |
是否上传了完整 .output/public,新进程是否已切换到新目录 |
补全构建产物,核对启动路径、工作目录和文件权限 |
修改 .env 后线上没变化 |
是否直接运行构建后的服务器 | 修改 ecosystem 的 env,再重启进程 |
| 移动目录后网站或续签异常 | 宝塔运行目录、PM2 cwd/script、Nginx 验证目录 | 同步更新这几处,验证成功后保存 PM2 列表 |
部署完成后,应确认独立的项目目录、PM2 运行参数、Nginx 代理目标和证书验证目录相互一致。