教程·阅读约 6 分钟·
用 Caddy + PM2 自托管 Next.js 应用到 VPS:完整部署指南

用 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

基础环境

连接你的服务器:

code
ssh root@你的服务器IP

更新系统包:

code
apt update && apt upgrade -y

安装 Node.js

建议用 nvm 安装,方便切换版本:

code
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 作为运行时(如本文站点的设置),安装方式如下:

code
curl -fsSL https://bun.sh/install | bash
source ~/.bashrc
bun --version

创建部署用户

不建议直接用 root 运行应用。创建一个专门部署的用户:

code
adduser deploy
usermod -aG sudo deploy
su - deploy

第二步:构建 Next.js 应用

在你的本地开发机上,确保项目可以正常构建。以标准的 Next.js App Router 项目为例,package.json 中的 scripts 一般是这样的:

code
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

构建生产版本:

code
# 如果你用 npm
npm run build
 
# 如果你用 bun
bun run build

构建完成后,你需要把整个项目目录上传到 VPS。常见的方式有几种:

方式一:rsync(推荐)

code
# 在本地开发机上执行
rsync -avz --exclude 'node_modules' --exclude '.git' --exclude '.next' ./your-project/ deploy@你的服务器IP:/home/deploy/your-app/

然后在服务器上再次安装依赖并构建:

code
cd /home/deploy/your-app
npm install --production
npm run build

方式二:Git 部署

在服务器上直接拉取代码:

code
cd /home/deploy
git clone 你的仓库地址 your-app
cd your-app
npm install
npm run build

这种方式更适合有 CI/CD 流程的场景。


第三步:安装并配置 PM2

安装 PM2

code
npm install -g pm2

启动应用

code
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 start3000 端口 上启动 HTTP 服务。

验证启动状态

code
pm2 status
# 应该看到 my-next-app 状态为 online
 
pm2 show my-next-app
# 显示进程详情、内存占用、重启次数等

设置开机自启

PM2 可以配置为系统服务,在服务器重启后自动启动所有应用:

code
pm2 startup
# 会输出一条命令,复制并执行它
 
pm2 save
# 保存当前进程列表

配置 PM2 生态文件(可选)

对于更复杂的场景,你可以创建一个 ecosystem.config.js 文件放在项目根目录:

code
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'
  }]
}

然后启动:

code
pm2 start ecosystem.config.js

第四步:安装并配置 Caddy

安装 Caddy

Caddy 官方提供了便捷的一键安装脚本:

code
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

验证安装:

code
caddy version
# 输出类似:v2.9.1 h1:...

编写 Caddyfile

Caddy 的配置文件叫 Caddyfile,路径在 /etc/caddy/Caddyfile。编辑它:

code
sudo nano /etc/caddy/Caddyfile

写入以下内容:

code
yourdomain.com {
    reverse_proxy 127.0.0.1:3000
}

就这么简单。yourdomain.com 换成你的实际域名(例如 aprilzz.com)。Caddy 会自动为这个域名申请和续期 Let's Encrypt 证书。

如果你有多个域名或子域名,可以这样写:

code
yourdomain.com, www.yourdomain.com {
    reverse_proxy 127.0.0.1:3000
}

配置静态资源缓存(可选)

为提升性能,可以添加静态资源的缓存策略:

code
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

code
sudo systemctl enable caddy
sudo systemctl start caddy
sudo systemctl status caddy

Caddy 启动后会自动监听 80 和 443 端口,为你的域名申请 HTTPS 证书。第一次启动可能需要 30 秒左右完成证书申请。

验证 HTTPS 是否生效:用浏览器访问 https://yourdomain.com,应该能看到绿色的小锁图标。


第五步:防火墙配置

如果你的 VPS 有防火墙(如 ufw),需要开放 80 和 443 端口:

code
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

code
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:服务器 IP
  • VPS_USERNAME:deploy 用户名
  • VPS_SSH_KEY:部署用的 SSH 私钥

零宕机部署

PM2 支持两种零宕机部署策略:

方式一:pm2 reload(推荐)

code
pm2 reload my-next-app

reload 会先启动新进程,等待新进程就绪后再关闭旧进程。相比 pm2 restart(先停后启),reload 不会造成服务中断。

方式二:Graceful Shutdown

在 Next.js 中,你可以在 next.config.js 中配置优雅关闭:

code
// next.config.js
module.exports = {
  serverGracefulShutdown: true,
}

开启后,Next.js 在收到关闭信号时,会先停止接收新请求,等待正在处理的请求完成后再退出。PM2 默认在发送 SIGINT 信号后会等待 1.6 秒,你可以通过 kill_timeout 调整这个时间。


监控与维护

查看应用日志

code
# 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

监控资源使用

code
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 后,验证配置再重载:

code
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

常见问题排查

502 Bad Gateway

通常意味着 Caddy 无法连接到后端 Next.js 服务。原因可能是:

  1. Next.js 没有启动:检查 pm2 status
  2. Next.js 端口与 Caddyfile 中配置的不一致
  3. Next.js 只绑定了 127.0.0.1,但 PM2 配置有问题

解决方法:

code
pm2 status
# 如果进程不在线,重新启动
pm2 start my-next-app
 
# 测试后端是否响应
curl http://127.0.0.1:3000

证书申请失败

Caddy 自动 HTTPS 失败,最可能的原因是:

  1. 域名 DNS 未正确指向服务器 IP
  2. 80 端口被防火墙拦截
  3. 域名刚注册不久,Let's Encrypt 频率限制

检查 DNS:

code
dig +short yourdomain.com
# 应该输出你的服务器 IP

检查端口:

code
sudo ss -tlnp | grep -E ':80|:443'

PM2 进程内存泄漏

如果发现 Next.js 进程内存持续增长,可以在 PM2 配置中设置自动重启阈值:

code
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 应用。

参考来源:

分享到
微博Twitter

© 2026 四月 · CC BY-NC-SA 4.0

原文链接:https://aprilzz.com/tutorials/nextjs-caddy-pm2-deploy-guide