
用 Caddy + PM2 自托管 Next.js 应用到 VPS:完整部署指南
手把手教你用 Caddy(自动 HTTPS 反向代理)和 PM2(进程守护)在 VPS 上部署 Next.js 应用,从零开始的完整教程。
原创。读完本文你将学会:在一台 Ubuntu VPS 上,用 Caddy 自动管理 SSL 证书、用 PM2 守护 Next.js 应用进程,实现零宕机部署。
为什么选 Caddy + PM2 + Next.js
当你有一个 Next.js 项目需要部署到自己的服务器上时,最常见的选择是 Nginx + Node.js 的组合。但 Nginx 配置 SSL 证书需要手动处理 Let's Encrypt,撰写复杂的配置段落,这对很多人来说是一道门槛。
Caddy 的出现改变了这一点。它会自动申请和续期 HTTPS 证书,不需要你碰 Certbot 或任何证书文件。一个最简单的反向代理配置只需要两行。
PM2 是 Node.js 生态中最成熟的进程守护工具。它能让你的应用在崩溃后自动重启,支持零宕机部署,还自带一个简单的监控面板。
Next.js 自 v12 起就支持自托管模式,使用内置的 next start 命令即可启动生产服务器。官方文档也推荐结合 PM2 来管理进程。
这三者组合起来,你可以在 30 分钟内把一个 Next.js 应用部署到生产环境,获得自动 HTTPS、进程守护、负载均衡等能力。
本文使用的环境:Ubuntu 22.04 LTS,Node.js 20 LTS,Next.js 14+。
第一步:准备 VPS
基础环境
连接你的服务器:
ssh root@你的服务器IP更新系统包:
apt update && apt upgrade -y安装 Node.js
建议用 nvm 安装,方便切换版本:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install 20
nvm alias default 20
node --version # 确认输出 v20.x如果你用 Bun 作为运行时(如本文站点的设置),安装方式如下:
curl -fsSL https://bun.sh/install | bash
source ~/.bashrc
bun --version创建部署用户
不建议直接用 root 运行应用。创建一个专门部署的用户:
adduser deploy
usermod -aG sudo deploy
su - deploy第二步:构建 Next.js 应用
在你的本地开发机上,确保项目可以正常构建。以标准的 Next.js App Router 项目为例,package.json 中的 scripts 一般是这样的:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}构建生产版本:
# 如果你用 npm
npm run build
# 如果你用 bun
bun run build构建完成后,你需要把整个项目目录上传到 VPS。常见的方式有几种:
方式一:rsync(推荐)
# 在本地开发机上执行
rsync -avz --exclude 'node_modules' --exclude '.git' --exclude '.next' ./your-project/ deploy@你的服务器IP:/home/deploy/your-app/然后在服务器上再次安装依赖并构建:
cd /home/deploy/your-app
npm install --production
npm run build方式二:Git 部署
在服务器上直接拉取代码:
cd /home/deploy
git clone 你的仓库地址 your-app
cd your-app
npm install
npm run build这种方式更适合有 CI/CD 流程的场景。
第三步:安装并配置 PM2
安装 PM2
npm install -g pm2启动应用
cd /home/deploy/your-app
# 如果用 npm
pm2 start npm --name "my-next-app" -- start
# 如果用 bun
pm2 start bun --name "my-next-app" -- run start默认情况下,next start 在 3000 端口 上启动 HTTP 服务。
验证启动状态
pm2 status
# 应该看到 my-next-app 状态为 online
pm2 show my-next-app
# 显示进程详情、内存占用、重启次数等设置开机自启
PM2 可以配置为系统服务,在服务器重启后自动启动所有应用:
pm2 startup
# 会输出一条命令,复制并执行它
pm2 save
# 保存当前进程列表配置 PM2 生态文件(可选)
对于更复杂的场景,你可以创建一个 ecosystem.config.js 文件放在项目根目录:
module.exports = {
apps: [{
name: 'my-next-app',
script: 'node_modules/next/dist/bin/next',
args: 'start',
env: {
NODE_ENV: 'production',
PORT: 3000
},
// 当内存超过 500MB 时自动重启
max_memory_restart: '500M',
// 日志配置
error_file: './logs/err.log',
out_file: './logs/out.log',
// 零宕机部署
instances: 1,
exec_mode: 'fork'
}]
}然后启动:
pm2 start ecosystem.config.js第四步:安装并配置 Caddy
安装 Caddy
Caddy 官方提供了便捷的一键安装脚本:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy验证安装:
caddy version
# 输出类似:v2.9.1 h1:...编写 Caddyfile
Caddy 的配置文件叫 Caddyfile,路径在 /etc/caddy/Caddyfile。编辑它:
sudo nano /etc/caddy/Caddyfile写入以下内容:
yourdomain.com {
reverse_proxy 127.0.0.1:3000
}
就这么简单。yourdomain.com 换成你的实际域名(例如 aprilzz.com)。Caddy 会自动为这个域名申请和续期 Let's Encrypt 证书。
如果你有多个域名或子域名,可以这样写:
yourdomain.com, www.yourdomain.com {
reverse_proxy 127.0.0.1:3000
}
配置静态资源缓存(可选)
为提升性能,可以添加静态资源的缓存策略:
yourdomain.com {
# 将 /public 目录下的静态资源做长时间缓存
@static {
path /_next/static/* /fonts/* /images/*
}
header @static Cache-Control "public, max-age=31536000, immutable"
# 压缩
encode gzip zstd
# 日志
log {
output file /var/log/caddy/access.log
}
reverse_proxy 127.0.0.1:3000
}
启动 Caddy
sudo systemctl enable caddy
sudo systemctl start caddy
sudo systemctl status caddyCaddy 启动后会自动监听 80 和 443 端口,为你的域名申请 HTTPS 证书。第一次启动可能需要 30 秒左右完成证书申请。
验证 HTTPS 是否生效:用浏览器访问 https://yourdomain.com,应该能看到绿色的小锁图标。
第五步:防火墙配置
如果你的 VPS 有防火墙(如 ufw),需要开放 80 和 443 端口:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 22/tcp # SSH 端口
sudo ufw enable
sudo ufw status注意:Caddy 的自动 HTTPS 需要 80 端口可访问,因为 Let's Encrypt 通过 HTTP 验证域名所有权。
第六步:配置自动部署(GitHub Actions)
每次手动上传代码到服务器显然不现实。用 GitHub Actions 可以实现在 push 到 main 分支时自动部署。
在项目根目录创建 .github/workflows/deploy.yml:
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Bun
uses: oven-sh/setup-bun@v1
- name: Install dependencies
run: bun install
- name: Build
run: bun run build
- name: Deploy to VPS
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USERNAME }}
key: ${{ secrets.VPS_SSH_KEY }}
source: ".,!node_modules,!*.md"
target: "/home/deploy/your-app"
- name: Restart app
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USERNAME }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd /home/deploy/your-app
bun install --production
bun run build
pm2 reload my-next-app在 GitHub 仓库的 Settings > Secrets 中配置以下变量:
VPS_HOST:服务器 IPVPS_USERNAME:deploy 用户名VPS_SSH_KEY:部署用的 SSH 私钥
零宕机部署
PM2 支持两种零宕机部署策略:
方式一:pm2 reload(推荐)
pm2 reload my-next-appreload 会先启动新进程,等待新进程就绪后再关闭旧进程。相比 pm2 restart(先停后启),reload 不会造成服务中断。
方式二:Graceful Shutdown
在 Next.js 中,你可以在 next.config.js 中配置优雅关闭:
// next.config.js
module.exports = {
serverGracefulShutdown: true,
}开启后,Next.js 在收到关闭信号时,会先停止接收新请求,等待正在处理的请求完成后再退出。PM2 默认在发送 SIGINT 信号后会等待 1.6 秒,你可以通过 kill_timeout 调整这个时间。
监控与维护
查看应用日志
# PM2 日志
pm2 logs my-next-app
pm2 logs my-next-app --lines 100 # 只看最近 100 行
# Caddy 日志
sudo journalctl -u caddy -f
sudo tail -f /var/log/caddy/access.log监控资源使用
pm2 monit
# 打开一个终端监控面板,显示 CPU/内存使用、请求数等PM2 常用命令速查
| 命令 | 说明 |
|---|---|
pm2 status | 查看所有应用状态 |
pm2 show <name> | 查看应用详情 |
pm2 restart <name> | 重启应用 |
pm2 reload <name> | 零宕机重载 |
pm2 stop <name> | 停止应用 |
pm2 delete <name> | 从 PM2 列表移除 |
pm2 logs <name> | 查看日志 |
pm2 monit | 监控面板 |
pm2 startup | 配置开机自启 |
pm2 save | 保存进程列表 |
Caddy 常用命令速查
| 命令 | 说明 |
|---|---|
sudo systemctl start caddy | 启动 Caddy |
sudo systemctl stop caddy | 停止 Caddy |
sudo systemctl restart caddy | 重启 Caddy |
sudo systemctl reload caddy | 重载配置(不中断连接) |
sudo caddy validate | 验证 Caddyfile 语法 |
sudo caddy fmt | 格式化 Caddyfile |
修改 Caddyfile 后,验证配置再重载:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy常见问题排查
502 Bad Gateway
通常意味着 Caddy 无法连接到后端 Next.js 服务。原因可能是:
- Next.js 没有启动:检查
pm2 status - Next.js 端口与 Caddyfile 中配置的不一致
- Next.js 只绑定了
127.0.0.1,但 PM2 配置有问题
解决方法:
pm2 status
# 如果进程不在线,重新启动
pm2 start my-next-app
# 测试后端是否响应
curl http://127.0.0.1:3000证书申请失败
Caddy 自动 HTTPS 失败,最可能的原因是:
- 域名 DNS 未正确指向服务器 IP
- 80 端口被防火墙拦截
- 域名刚注册不久,Let's Encrypt 频率限制
检查 DNS:
dig +short yourdomain.com
# 应该输出你的服务器 IP检查端口:
sudo ss -tlnp | grep -E ':80|:443'PM2 进程内存泄漏
如果发现 Next.js 进程内存持续增长,可以在 PM2 配置中设置自动重启阈值:
pm2 start npm --name "my-next-app" -- start --max-memory-restart 500M或者使用 --exp-backoff-restart-delay 让 PM2 在崩溃后以指数退避方式重启,避免频繁重启耗尽系统资源。
总结
本文完整覆盖了从零开始部署 Next.js 应用到自托管 VPS 的全流程。这套 Caddy + PM2 + Next.js 组合的优势在于:
- Caddy 消除了 SSL 证书管理的所有痛点
- PM2 提供了可靠的应用守护和零宕机部署能力
- Next.js 开箱即用的自托管支持
这套方案特别适合独立开发者——不需要用 Vercel(虽然 Vercel 也很好),也不需要在 Nginx 配置上折腾几个小时。一台 2 核 2G 的服务器,加上一个域名,就能稳定运行多个 Next.js 应用。
参考来源:
© 2026 四月 · CC BY-NC-SA 4.0
原文链接:https://aprilzz.com/tutorials/nextjs-caddy-pm2-deploy-guide