跳到正文
Joeplover
工程·2026-06-27·约 6 分钟阅读

Next.js 博客全流程部署:Ubuntu + Nginx + SSL + PM2 实战记录

从零开始将 Next.js 博客部署到 Ubuntu 22.04 服务器的完整流程,包括 Node.js 环境配置、Nginx 反向代理、Let's Encrypt SSL 证书申请、PM2 进程保活,以及 CentOS 7.6 迁移到 Ubuntu 过程中遇到的坑和解决方案。

服务器机房的蓝色指示灯特写

Next.js 博客全流程部署:Ubuntu + Nginx + SSL + PM2 实战记录

前言

之前博客部署在三丰云的 Ubuntu 22.04 上,重装系统后换了 CentOS 7.6,结果发现 CentOS 7.6 的 yum 源已经全部失效(所有镜像站返回 404),certbot 的 Python 依赖链也断光了。最终决定换回 Ubuntu 22.04。本文记录从零开始的完整部署流程,以及过程中踩过的所有坑。

一、环境准备

安装 Node.js

Ubuntu 22.04 自带 Node.js 版本较老,建议用 NodeSource 安装最新的 LTS 版本:

curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs
node -v  # 确认版本 ≥ 20
npm -v

克隆项目

git clone git@github.com:joeplover/JoeBlog.git /opt/JoeBlog
cd /opt/JoeBlog

如果是第一次部署,注意配置 Git SSH key:

ssh-keygen -t ed25519 -C "你的邮箱"
cat ~/.ssh/id_ed25519.pub  # 添加到 GitHub
ssh -T git@github.com  # 验证

配置环境变量

echo 'DATABASE_URL="file:./prisma/dev.db"' > /opt/JoeBlog/.env.production

注意:DATABASE_URL 是 Prisma 连接 SQLite 必需的。如果数据库文件不在 prisma/dev.db,需要从本地 scp 上传。

cd /opt/JoeBlog
npm install
npm run build

生产启动

npm start

这时博客在 http://服务器IP:3000 运行。但直接暴露 3000 端口不安全,需要 Nginx 反代。

二、Nginx 反向代理

安装 Nginx

apt install -y nginx
systemctl start nginx
systemctl enable nginx

Ubuntu 源装的 Nginx 版本是 1.18.0,虽然不算最新但稳定够用。

配置反向代理

创建站点配置 /etc/nginx/sites-available/joeblog:

server {
    listen 80;
    server_name www.ploverjoe.xyz;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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_cache_bypass $http_upgrade;
    }
}

启用配置:

rm -f /etc/nginx/sites-enabled/default
ln -s /etc/nginx/sites-available/joeblog /etc/nginx/sites-enabled/
nginx -t && systemctl restart nginx

坑:配置文件被误删

部署过程中发现配置文件 /etc/nginx/sites-available/joeblog 意外丢失了,但软链接 /etc/nginx/sites-enabled/joeblog 仍然指向它。结果 Nginx 实际用的是默认配置,导致外部请求被错误处理。

排查方法:检查 ls -la /etc/nginx/sites-enabled/ 确认链接状态,再用 cat /etc/nginx/sites-available/joeblog 确认目标文件存在。

三、SSL 证书——certbot 失败与 acme.sh 替代方案

第一步:尝试 certbot(失败)

apt install -y certbot python3-certbot-nginx

先停 nginx 用 standalone 模式:

systemctl stop nginx
certbot certonly --standalone -d www.ploverjoe.xyz

失败原因: 三丰云(国内机房)的 80/443 端口被上游拦截,即使服务器内 Nginx 在监听,外部请求也无法到达。certbot standalone 启动的临时 web 服务器同样无法被 Let's Encrypt 验证服务器访问到。

错误信息一:Timeout during connect (likely firewall problem) —— 端口上游拦截 错误信息二:Invalid response from ... 404 —— 即使端口通了,请求被三丰云代理层拦截

第二步:UFW 防火墙排查

尝试用 UFW 开放端口:

apt install -y ufw
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

但发现 UFW 在此场景下并没有帮助——问题不出在服务器防火墙,而是云服务商网络层。

第三步:改用 acme.sh DNS 模式(成功)

certbot 走不通时,换用 acme.sh 的 DNS 手动验证模式,完全不需要 80/443 端口:

# 安装 acme.sh
curl https://get.acme.sh | sh
source ~/.bashrc

# 注册邮箱(acme.sh 默认使用 ZeroSSL CA)
~/.acme.sh/acme.sh --register-account -m 1665637752@qq.com

# DNS 手动模式申请
~/.acme.sh/acme.sh --issue -d www.ploverjoe.xyz --dns \\
  --yes-I-know-dns-manual-mode-enough-go-ahead-please

执行后会提示添加一条 TXT 记录:

Domain: '_acme-challenge.www.ploverjoe.xyz'
TXT value: 'DdNs_2Xwirt19tH3Bfey4nCPXrSt9Tgh2KDYYcsYYrg'

去域名管理后台(阿里云)添加:

记录类型主机记录记录值
TXT_acme-challenge.www上面生成的字符串

注意:阿里云的主机记录只填前缀,不拼主域名。要得到 _acme-challenge.www.ploverjoe.xyz,主机记录应填 _acme-challenge.www(不是 _acme-challenge)。

添加后等待 2-3 分钟生效,然后 renew 获取证书:

~/.acme.sh/acme.sh --renew -d www.ploverjoe.xyz \\
  --yes-I-know-dns-manual-mode-enough-go-ahead-please

安装证书到 Nginx:

mkdir -p /etc/nginx/ssl
~/.acme.sh/acme.sh --install-cert -d www.ploverjoe.xyz \\
  --key-file /etc/nginx/ssl/www.ploverjoe.xyz.key \\
  --fullchain-file /etc/nginx/ssl/www.ploverjoe.xyz.pem

四、三丰云免备案 CDN

为什么需要免备案

国内云服务器要求 ICP 备案才能使用 80/443 端口。三丰云提供免备案服务,本质是通过他们的 CDN 中转:用户访问域名 → 三丰云 CDN 节点 → 转发到你的服务器。

配置步骤

  1. 在三丰云后台提交域名白名单:www.ploverjoe.xyz
  2. 系统分配别名地址:cloud40099.sfycname.cn
  3. 去阿里云 DNS 把 A 记录改为 CNAME:
记录类型主机记录记录值
CNAMEwwwcloud40099.sfycname.cn

坑:CNAME 生效但 CDN 节点未同步

CNAME 全球生效后(通过 nslookup www.ploverjoe.xyz 8.8.8.8 确认),访问可能遇到三丰云 CDN 的海外错误页面:

当前地址(45.58.185.251)为海外节点!

这说明 CDN 节点还没完全同步你的域名配置。需要等一段时间(半小时到几小时)或联系三丰云客服。

DNS 缓存问题

Windows 本地 DNS 缓存(路由器 192.168.0.1)可能还有旧 A 记录的缓存,清除方法:

ipconfig /flushdns

或用公共 DNS 验证解析:

nslookup www.ploverjoe.xyz 8.8.8.8
nslookup www.ploverjoe.xyz 223.5.5.5

五、IP 变更的问题

重装系统后服务器 IP 可能改变。我这次从旧 IP 171.80.9.218 变成了新 IP 171.80.9.183。排查方法:

# 查看当前服务器 IP
ip addr show | grep inet
# 查看对外的公网 IP
curl -s ifconfig.me

注意:有些云服务器是 NAT 模式,ip addr 看到的是内网 IP(如 171.80.9.183/21),而 ifconfig.me 返回的是公网出口 IP(如 1.27.141.120)。DNS 记录应使用三丰云提供的 IP(通常是内网 IP)。

六、验证与测试

从服务器本地测试:

curl -I http://127.0.0.1:3000    # 确认 Next.js 运行
curl -I http://127.0.0.1         # 确认 Nginx 反代
curl -I https://127.0.0.1 -k    # 确认 HTTPS(跳过证书验证)

从外部测试:

curl -I http://www.ploverjoe.xyz
curl -I https://www.ploverjoe.xyz

正常响应应该是:HTTP → 301 跳转,HTTPS → 200 返回博客 HTML。

七、PM2 进程保活

npm install -g pm2
cd /opt/JoeBlog
pm2 start npm --name "joeblog" -- start
pm2 save
pm2 startup

pm2 startup 会提示执行一行命令(类似 env PATH=... pm2 startup systemd -u root),复制执行即可。

常用命令:

pm2 status            # 查看状态
pm2 logs joeblog      # 查看日志
pm2 restart joeblog   # 重启
pm2 stop joeblog      # 停止

八、坑点总结

问题原因解决
certbot 验证失败三丰云拦截 80/443改用 acme.sh DNS 模式
Nginx 反代不生效配置文件被误删,链接悬空ls -la 检查配置文件和软链接
域名无法访问重装系统后 IP 变更ip addr 确认当前 IP,更新 DNS
CNAME 生效后仍报错CDN 节点未同步等待或联系客服
路由器 DNS 缓存DNS 指向旧 IPipconfig /flushdns 或临时改公共 DNS
CentOS 7 yum 源失效系统太旧镜像已下线改用 Ubuntu 22.04

九、最终架构

用户浏览器 → https://www.ploverjoe.xyz
  → 三丰云 CDN(cloud40099.sfycname.cn)
    → Nginx(:443 SSL → :3000 反代)
      → Next.js(:3000)
        → SQLite(prisma/dev.db)

整个部署流程的核心验证点:npm start 确认 3000 可访问、nginx -t 确认配置正确、浏览器打开 https://域名 确认 HTTPS 生效。