从 PM2 online 到接口 404:一次真实的阿里云 ECS 博客部署踩坑记录

0 阅读6分钟

最近我把一个博客项目部署到了阿里云 ECS,技术栈不算复杂:

  • 后端:Fastify
  • 前端:Nuxt
  • 进程管理:PM2
  • 反向代理:Nginx
  • 数据库:MySQL

本来以为这件事最多半小时,结果从 git pull 到线上可用,中间踩了一串特别典型的坑:

  • PM2 显示 online,但健康检查 Connection refused
  • 前端页面能打开,接口却全是 404
  • 域名解析好了,证书申请一直失败
  • 本机能访问,公网却被阿里云拦截
  • 想用 HeidiSQL 连线上数据库,结果发现根本不能直接填公网 IP

这篇文章我就把整个部署流程和踩坑过程完整复盘一下。如果你也是把个人博客部署到阿里云 ECS,这篇基本能帮你少踩一半坑。


一、我的部署结构

我最后采用的是一个很常见的结构:

  • p-blog 跑 Nuxt 前端,监听 3001
  • p-fastify 跑 Fastify 后端,监听 3000
  • Nginx 对外暴露 80
  • PM2 托管两个 Node 服务

对应关系大概是这样:

浏览器 -> Nginx -> Nuxt(3001) -> Fastify(3000)

如果只是 IP 访问,我的目标是:

  • / 走前端
  • /api/* 走后端
  • /uploads/* 走后端静态资源
  • /health 走后端健康检查

二、后端部署:看起来成功,其实没完全成功

我一开始跑的是后端部署脚本,大致流程就是:

cd /var/www/p-fastify git pull npm install pm2 restart ecosystem.config.cjs --only p-fastify --update-env curl http://127.0.0.1:3000/health

最开始我看到 pm2 list 里 p-fastify 是 online,以为就结束了。结果健康检查直接报:

curl: (7) Failed to connect to 127.0.0.1 port 3000: Connection refused

这个坑很典型。

PM2 显示 online,不等于你的服务已经真的能提供请求。

后面我去看日志才确认,Fastify 其实已经起来了,只是我检查得太快。正确做法是:

pm2 logs p-fastify --lines 100 curl http://127.0.0.1:3000/health curl http://127.0.0.1:3000/api/posts

只看 PM2 状态不够,一定要再打健康检查和业务接口。


三、前端部署:页面打开了,接口却 404

前端 Nuxt 跑起来以后,页面 /login 是能打开的,但登录请求一直报 404。

浏览器里看到的请求是:

http://47.94.103.11/api/auth/login

返回:

404 Page not found: /api/auth/login

一开始我以为是 Fastify 路由没注册,后来才发现问题根本不在后端,而是 Nginx 没把 /api 转发给后端

当时 Nginx 只做了这件事:

location / { proxy_pass http://127.0.0.1:3001; }

这意味着:

  • 页面请求被转发给 Nuxt
  • /api/auth/login 这种请求也被转发给 Nuxt
  • Nuxt 当然找不到这个接口,所以直接 404

最后修成下面这样才正常:

server { listen 80 default_server; listen [::]:80 default_server; server_name _ 47.94.103.11; location ^~ /api/ { proxy_pass http://127.0.0.1:3000; } location ^~ /uploads/ { proxy_pass http://127.0.0.1:3000; } location = /health { proxy_pass http://127.0.0.1:3000/health; } location / { proxy_pass http://127.0.0.1:3001; } }

这个问题给我的提醒很直接:

页面能打开,不代表前后端链路就是通的。

尤其是前后端分离项目,/api 的转发规则一定要单独配。


四、域名解析没问题,但网站还是打不开

后面我把域名 pankrati.top 解析到了阿里云 ECS:

  • @ -> 47.94.103.11
  • www -> 47.94.103.11
  • api -> 47.94.103.11

DNS 配置本身没问题,dig 查询也都正常。
结果浏览器一打开,直接看到阿里云的提示页:

域名暂时无法访问
该域名当前备案状态不符合访问要求

这个坑如果没经历过,真的很容易懵。

因为阿里云控制台里域名状态显示的是“正常”,但那个“正常”指的是:

  • 域名注册正常
  • DNS 正常
  • 没过期

不代表你已经满足中国内地服务器的网站访问要求。

如果你的服务器在中国内地,域名解析过去以后,通常还要处理备案问题。不然你本机可能能 curl 到,公网访问却直接被平台拦掉。

这也是为什么我后面申请 Let’s Encrypt 证书一直失败。


五、证书不是申请失败,是校验请求根本没到我这里

我一开始用的是:

certbot --nginx -d pankrati.top -d www.pankrati.top -d api.pankrati.top

后面各种报错,先是邮箱参数没填,再后来变成 403。
最关键的一次报错是:

Invalid response from http://pankrati.top/.well-known/acme-challenge/...: 403

我当时还以为是 Nginx 配错了,后面专门加了挑战目录:

location ^~ /.well-known/acme-challenge/ { root /var/www/certbot; default_type "text/plain"; allow all; }

而且本机测试也通:

echo hello > /var/www/certbot/.well-known/acme-challenge/test curl http://pankrati.top/.well-known/acme-challenge/test

结果公网还是 403。

最后看到阿里云那张“域名暂时无法访问”的拦截页,事情才彻底说通:

不是证书申请有问题,是公网校验请求压根没机会访问到我的真实 Nginx。


六、HeidiSQL 连线上数据库,别直接开 3306

部署完以后,我还想本地直接看线上库,第一反应是 HeidiSQL 直接填公网 IP:

  • 主机:47.94.103.11
  • 端口:3306

这个思路不推荐。

因为这意味着你要:

  • 让 MySQL 对公网监听
  • 开放阿里云安全组 3306

这基本是在给数据库挖坑。

我最后用的是 SSH 隧道,本机执行:

ssh -L 3307:127.0.0.1:3306 root@47.94.103.11

然后 HeidiSQL 里填:

  • 主机:127.0.0.1
  • 端口:3307
  • 用户:.env 里的 MYSQL_USER
  • 密码:.env 里的 MYSQL_PASSWORD
  • 数据库:.env 里的 MYSQL_DATABASE

这样数据库还是只监听服务器本机,但我本地可以安全访问。
这个方式比直接暴露 3306 安心得多。


七、这次部署我最大的感受

这次让我最有感触的一点是:

部署从来不只是“把代码跑起来”。

真正难的部分通常在这些地方:

  • 进程状态和服务状态不是一回事
  • 前端能开和接口可用不是一回事
  • DNS 正常和公网可访问不是一回事
  • 域名能解析和证书能签发不是一回事

如果让我重新来一遍,我会按这个顺序检查:

  1. 后端本机健康检查是否通过
  2. 前端本机端口是否正常
  3. Nginx 是否正确分流 / 和 /api
  4. PM2 日志是否真的没有报错
  5. 域名公网访问是否被备案策略拦截
  6. 证书申请前先确认挑战路径公网可访问

很多时候,不是技术栈复杂,而是链路比你想象中长。


八、最后给准备上阿里云 ECS 的人一个建议

如果你只是想先把个人博客快速上线,有两个方案:

  • 要么老老实实走备案流程
  • 要么直接上中国大陆以外的机器,比如香港、新加坡

如果你选中国内地 ECS,又想立刻绑域名、申请证书、对外访问,最后大概率会像我一样,被备案这一步拦下来。

代码问题其实不难,真正折磨人的往往是“环境和平台规则”。