最近我把一个博客项目部署到了阿里云 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 正常和公网可访问不是一回事
- 域名能解析和证书能签发不是一回事
如果让我重新来一遍,我会按这个顺序检查:
- 后端本机健康检查是否通过
- 前端本机端口是否正常
- Nginx 是否正确分流 / 和 /api
- PM2 日志是否真的没有报错
- 域名公网访问是否被备案策略拦截
- 证书申请前先确认挑战路径公网可访问
很多时候,不是技术栈复杂,而是链路比你想象中长。
八、最后给准备上阿里云 ECS 的人一个建议
如果你只是想先把个人博客快速上线,有两个方案:
- 要么老老实实走备案流程
- 要么直接上中国大陆以外的机器,比如香港、新加坡
如果你选中国内地 ECS,又想立刻绑域名、申请证书、对外访问,最后大概率会像我一样,被备案这一步拦下来。
代码问题其实不难,真正折磨人的往往是“环境和平台规则”。