前后端分离项目走到今天,总算是从本地联调切换到了真正的公网环境。这是我这套“社交管理系统”系列笔记的第五篇,前面几篇分别讲了项目结构、接口设计、Vue组件拆分、Node后端模块开发,这一篇进入部署实战——把Vue构建出来的静态文件和Node.js服务,装到一台Ubuntu系统的云服务器上,让外网可以访问。整个过程踩了不少坑,从依赖装不上到页面刷新404,从数据库连不上到服务半夜挂掉没人知道,全都碰过一遍。这篇文章把完整的部署流程和踩过的坑如实写下来,适合刚刚做完前后端分离项目、第一次尝试“搬上云”的同学参考,也欢迎有经验的朋友帮忙补充。
1. 部署前准备:先搞清楚这台服务器要跑什么
1.1 理清整体部署架构
动手之前,先在脑子里把整条数据链路画清楚。我这套社交管理系统,前端是Vue,构建产物是一堆静态文件(html、js、css),它们本身不具备计算能力,只能交给Web服务器托管;不能不等前后端分离。默认情况下有人问“nodejs已经起了服务,为什么还买nginx”,因为前端静态页面和后端接口分开处理更清晰:Nginx负责托管静态文件,同时把接口请求反向代理给Node进程,Node进程再去访问MySQL和Redis。
客户端输入网址后,流程是这样的:浏览器请求域名,Nginx接收到请求,如果是/开头的静态页面,就直接返回dist目录下的文件;如果是/api/开头的接口请求,Nginx就会把请求转发到本机3000端口的Node服务,Node处理完后把JSON返回给Nginx,Nginx再把响应回传给浏览器。数据库层面的MySQL负责持久化用户、动态、关注关系这些数据,Redis负责缓存登录态和热点数据。
这样一台最低配置的2核2G服务器就能跑完全部服务,不必分开买多台机器。
1.2 服务器配置与系统版本选择
云服务器的规格按项目体量来定。我这个社交管理系统目前是学习项目,不会有太大并发,选的是2核4G的入门套餐,系统选了Ubuntu 22.04 LTS。为什么强调LTS?因为LTS版本有长达五年的安全维护周期,能保证apt源里的软件包版本不会很快过期,这对部署稳定性很重要。尽量不选最新的非LTS版本(比如24.10这种短暂支持版),生产环境求的是稳而不是新。
实例创建时还要设置好SSH登录方式。我建议直接用密钥对登录,而不是密码。云厂商后台可以生成密钥对,把私钥下载保存到本地,公钥自动注入到服务器,登录时用ssh -i 私钥文件 root@服务器公网IP,比密码登录安全很多。密码容易暴力破解,密钥基本不会被枚举。
1.3 安全组与防火墙:先把大门打开
这一步最容易漏。云服务器有两层访问控制:一层是云平台控制台里的安全组,一层是服务器系统里的防火墙。很多新手只在系统里放行了端口,却忘了安全组没放行,外部照样访问不了。
我的做法是这样的。云平台安全组里放行22端口(SSH)、80端口(HTTP)、443端口(HTTPS),这三个是做Web服务的基本盘。3306(MySQL)和6379(Redis)一律不对公网放行,数据库只允许内网访问。Node后端用的3000端口也不对公网放行,因为外网请求只会通过80/443进来,再由Nginx转发到3000。
安全组优先于系统防火墙先过一遍。即使系统里的nginx和node跑得再正常,安全组不放行80端口,外网永远访问不了你的页面。这个顺序问题我踩过一次,配置了半天发现浏览器一直拒绝连接,最后检查安全组才找到原因。
服务器系统内的防火墙(ufw)我暂时没有启用,因为Ubuntu默认的ufw是关闭的,而安全组已经起到了边界防护作用。如果后续要多端口对外开放,再考虑启用ufw并放行相应端口。新装的服务器,系统里能关的服务尽量少开,攻击面越小越好。
2. 环境搭建:不要一上来就依赖面板
很多初学者喜欢在服务器上用可视化管理面板,点几下鼠标就能装环境。但我的习惯是命令行手动安装,尤其这个项目是学习笔记,每一步都应该知道装了什么东西、装在哪里、怎么改配置。手动装一遍之后,就算以后换面板管理,也知道面板背后做了什么操作,不会两眼一抹黑。
2.1 安装Node.js:用nvm管理版本
服务器上装Node.js,最简单的方案是apt install nodejs,但Ubuntu软件源里的Node版本往往偏旧,而且和npm不一定配套。更灵活的方式是用nvm(Node Version Manager)来安装指定版本。
先安装nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash安装完成后,重新登录终端或者执行source ~/.bashrc,然后安装需要的Node版本。我这个项目后端用的是Express框架,推荐的稳定版本是Node 16或18:
nvm install 16 nvm use 16 nvm alias default 16最后一行nvm alias default 16很有必要。不加这一句,一旦SSH断开重连,node命令可能就找不到,因为没设置默认版本。折腾了两次之后我学乖了,每次装完必定设置default。
2.2 安装Nginx:Web入口
Nginx在Ubuntu上的安装非常简单:
apt update apt install nginx -y systemctl enable --now nginx安装完成后,访问公网IP应该能看到Nginx的默认欢迎页。这个页面位于/var/www/html,先去找到默认配置文件/etc/nginx/sites-available/default,把默认的server配置替换成自己项目的配置,或者更干净的做法是删掉这个default入口,在/etc/nginx/conf.d/下新建一个自己的配置文件。
Ubuntu的Nginx配置有两套目录比较绕:sites-available和sites-enabled。官方推荐把配置放在available下,然后用软链接方式启用,但初学者其实直接用/etc/nginx/conf.d/目录下的配置文件,主配置里已经include了这个目录,简单直接不绕弯。
2.3 安装MySQL与Redis:数据层就位
数据层依赖两个东西:MySQL存业务数据,Redis做缓存。Ubuntu的源里都有:
apt install mysql-server redis-server -yMySQL安装完成后,先执行安全初始化:
mysql_secure_installation这个过程会提示设置root密码、移除匿名用户、禁止root远程登录等,按照提示操作即可。然后进入MySQL,创建项目专用的数据库和账号:
CREATE DATABASE social_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'social_app'@'localhost' IDENTIFIED BY '这里写一个强密码'; GRANT ALL PRIVILEGES ON social_system.* TO 'social_app'@'localhost'; FLUSH PRIVILEGES;注意@'localhost',我特意限定这个账号只能在本机连接,因为后端Node进程和MySQL在同一台机器上,根本不需要从外部访问3306端口。就算安全组配错把3306端口暴露到公网,MySQL的账号限定也能挡住一部分风险。层防御,少开一个口子就少一份风险。
Redis这里也要说一句。Ubuntu源里的Redis默认配置只绑定了127.0.0.1,也就是说只有本机应用能访问,这是安全默认值,保持这样即可。千万不要为了省事在redis.conf里改成0.0.0.0,Redis如果不设密码暴露到公网,扫描到你IP的恶意脚本很快就能利用未授权访问把你的数据刷掉。
2.4 我为什么没有直接用Docker
有人会问,现在部署不都流行容器化吗,一条docker-compose up -d就能把所有服务拉起来,为什么这里还在手动装环境?
我的观点是:选型没有绝对的对错,分场景。如果你已经把Docker原理搞清楚,镜像构建、数据卷挂载、网络模式这些概念都熟练,那容器化部署确实更干净,迁移也方便。但如果第一次做部署,对Linux本身还不熟,我反而建议先裸装跑通一遍。手动装过Node、Nginx、MySQL之后,你会理解“进程监听端口”“反向代理”“环境变量”到底是什么,这些底层的概念一旦通了,以后抽到Docker只是换一种方式去复用这些理解。
Docker踩坑还得额外加几个:镜像源慢、容器数据在删除后丢失、端口映射和Nginx联动容易晕。先裸装,再容器化,循序渐进更稳。
3. 后端代码上线:让服务真正跑起来
3.1 代码上传的三种方式
后端代码写在本机,要上传到服务器。常用方式有三种:
| 方式 | 命令或操作 | 适用场景 |
|---|---|---|
| Git拉取 | 服务器上git clone你的仓库 | 代码已推送到远程仓库,这是最推荐的方式 |
| scp传输 | scp -i 私钥 -r ./backend root@服务器IP:/opt/ | 文件不多,临时传输 |
| 压缩包上传 | 本地打包tar czf后上传再解压 | 网络较差或文件很多时 |
我个人用的是Git方式,项目代码推到Git仓库,服务器上拉取,后续更新代码只需要在服务器上git pull,还可以在某个分支保持稳定版本。默认放开一条:上传代码的目录我放在/opt/social/backend,前端dist文件后面放在/var/www/social/dist,目录分开,职责清晰。
3.2 环境变量:密码和密钥别写死在代码里
后端连接数据库的密码、JWT加密的密钥、项目端口,这些都不应该硬编码在代码里。项目根目录建一个.env文件(这个文件不进Git仓库),内容大致是:
PORT=3000 DB_HOST=127.0.0.1 DB_PORT=3306 DB_USER=social_app DB_PASS=这里填强密码 DB_NAME=social_system REDIS_HOST=127.0.0.1 REDIS_PORT=6379 JWT_SECRET=这里填一个随机长字符串我的后端代码里使用dotenv这个包来加载环境变量,代码写作process.env.DB_HOST等。这样换一台机器部署,只需要改.env,代码完全不用动。这也是前后端分离项目部署时的一个好习惯——配置与代码分离。
有一点特别提醒:不要为了省事直接把工程里的数据库密码写在config文件里再提交到Git仓库。只要是熟悉的代码仓库,密码一旦提交再撤回来,人家已经能看到历史记录,不具备安全性。用.env加.gitignore过滤掉才是正解。
3.3 安装依赖:node_modules的那些坑
代码到位、环境变量配好之后,安装依赖:
cd /opt/social/backend npm install --production--production参数会跳过开发依赖,只安装运行时需要的包,可以减少体积和安装时间。如果有些包是构建时需要的,再单独装。
这一步往往是新手第一次接触Linux部署最容易崩溃的地方。常见的失败场景是node-sass、sharp这类原生模块需要本地编译,而服务器上缺少编译工具链,导致安装报错一整屏。我当时的解决办法是先把基础工具装上:
apt install build-essential python3 -y然后npm换用国内镜像源(访问速度快):
npm install --production --registry=https://registry.npmmirror.com如果node-sass这类包装不上,还有一种更彻底的办法:把Node版本切换到它官方支持的版本。比如node-sass对Node 16以下的兼容性比较好,如果你原本用Node 18跑,可以nvm use 14再试一次。不同版本对应不同原生模块的编译行为,环境问题能折腾一晚上,换个版本可能一分钟就解决。
3.4 用pm2守护Node进程
node app.js这样直接启动,有一个致命问题:SSH窗口一关,进程就被系统杀掉了,服务就挂了。所以需要进程守护工具,我用的pm2。
npm install -g pm2 cd /opt/social/backend pm2 start app.js --name social-backend pm2 statuspm2会把Node进程托管起来,即使进程崩溃也会自动重启。它还能设置开机自启:
pm2 startup pm2 savepm2 startup会生成一条系统服务配置,让pm2随着系统启动而启动;pm2 save会把当前运行的进程列表保存下来,开机后自动恢复这些进程。这两个命令配套用,缺一个都不行。我遇到过一次:更新服务器内核后重启,发现服务没起来,就是因为当时只做了pm2 start,没执行pm2 save,进程列表没有被记录,重启后自然就丢了。
启动后用pm2 logs social-backend可以实时查看日志输出,排查后端接口问题非常方便。
4. 前端构建与Nginx接入
4.1 前端怎么出构建产物
前端代码不能直接拿给Nginx用,得先构建。构建动作是在本机还是服务器上做?我第一次部署时把代码传到服务器,在服务器上跑npm run build,结果2G内存的服务器直接报了JavaScript heap out of memory,构建进程被系统杀掉。后来学乖了:前端构建在本机完成,只把dist目录传到服务器即可。
为什么强调这点?因为Vue的构建过程(尤其webpack或Vite的打包)需要不少内存,小规格服务器同时跑着MySQL、Redis、Node后端,再叠加一个前端构建进程,内存很容易吃紧。而在本机构建只需要一条命令:
npm run build构建完成后,项目目录下会多出一个dist文件夹。这个文件夹包含index.html和assets/等目录,就是Nginx需要托管的全部内容。
关于前端接口的地址:开发时Vue的代理(proxy)只在本地开发服务器(通常是8080/5173)上生效,构建产物是纯静态文件,不会有代理规则。所以生产环境的接口地址要么通过环境变量注入,要么在构建前把API地址改成你服务器域名的地址。我是用.env.production文件配置的:
VITE_API_BASE_URL=https://你的域名/api构建时Vite读取的是.env.production,本地开发读取.env.development,这样开发、生产两个环境互不干扰。
4.2 上传dist并配置Nginx
将dist目录上传到服务器的/var/www/social/dist:
mkdir -p /var/www/social scp -r -i 你的私钥 ./dist root@服务器IP:/var/www/social/然后新建Nginx配置文件/etc/nginx/conf.d/social.conf:
server { listen 80; server_name _; root /var/www/social/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; 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; } location / { try_files $uri $uri/ /index.html; } }重点说两个配置。
一是location /api/里面的proxy_pass。我这里写的是http://127.0.0.1:3000;,结尾不带斜杠,这样请求/api/user/login会被原样转发到后端的/api/user/login,而我的后端Express路由确实是以/api开头定义的,正好对应。如果你后端路由没有/api前缀,而是直接写/user/login,那这里就要改成proxy_pass http://127.0.0.1:3000/;,多了结尾的/,Nginx会把/api这个前缀截掉再转发,于是/api/user/login到了后端变成/user/login。多了少一个斜杠,行为完全不同,这个细节非常容易踩坑。
二是change。Vue路由如果用history模式(而不是hash模式),访问/profile这样的前端路由时,Nginx在/var/www/social/dist目录下找不到profile这个文件,就会默认返回404。try_files $uri $uri/ /index.html;的意思是:先尝试按原路径找文件,找不到就尝试目录,再找不到就回退到/index.html,由前端路由接管。加了这一行,刷新页面就不会404了。
配置写完后先验证语法,然后重载:
nginx -t systemctl reload nginx每次改完Nginx配置,都建议先
nginx -t检查语法,再systemctl reload。直接重启而非reload也不是不行,但reload可以做到不中断现有连接地应用新配置,更平滑。
4.3 部署完成后怎么验证
环境都跑起来之后,不要急着粘到浏览器里,先在服务器上做一个完整的自测。
先测本地服务是否正常:
curl http://127.0.0.1:3000/api/health如果后端有健康检查接口,这里应该能返回JSON。再测Nginx托管的前端页面:
curl http://127.0.0.1/应该返回index.html的内容。接着测静态资源:
curl http://127.0.0.1/assets/xxx.js这一步确认前端资源确实被Nginx正确托管。最后用公网IP测试:
curl http://服务器公网IP/如果在服务器上可以通过公网IP访问,说明安全组、防火墙、Nginx都正常。然后把IP发给朋友或者另一台机器试试,如果不行,大概率还是安全组漏掉了端口或者Nginx监听的IP不对。
浏览器端再操作一遍核心流程:打开首页、登录、发布一条动态、刷新页面。刷新这条尤其重要,能验证history模式的try_files是否配置正确。
5. 踩坑实录与解决方案:我的排查顺序
5.1 问题速查表
| 现象 | 大概率原因 | 解决办法 |
|---|---|---|
| 外网访问不了页面 | 安全组没放行80端口 | 云平台控制台安全组放行80 |
| 页面能开,接口请求失败 | Nginx代理配置错误 | 检查proxy_pass路径和后端路由是否匹配 |
| 刷新子页面404 | 没有try_files回退 | location / 里补上try_files $uri $uri/ /index.html; |
| npm install报错一堆 | 缺编译工具链或镜像源慢 | 安装build-essential python3,用国内镜像源 |
| 构建时内存溢出 | 服务器内存不够 | 本机构建后上传dist,或加大swap |
| 服务重启后没了 | 没有pm2 save | 执行pm2 startup和pm2 save |
| 时间显示总是差8小时 | 系统时区默认UTC | timedatectl set-timezone Asia/Shanghai |
| MySQL连接报认证错误 | MySQL 8默认认证插件不兼容 | 切换账号认证插件为mysql_native_password |
| 日志里中文变成问号 | 数据库连接没指定utf8mb4 | 数据库连接URL加上charset=utf8mb4 |
5.2 缓存和浏览器导致的“假故障”
有次前端页面改了代码,重新构建上传后,浏览器里却还是旧页面。第一反应是Nginx缓存,但Nginx对静态文件默认其实是不缓存的,真正元凶是浏览器自身的HTTP缓存。dist目录下的静态文件名带有hash,正常情况下每次构建文件名都不同,浏览器会请求新文件;但如果index.html本身被浏览器缓存了,它就会按照旧的hash去请求已经不存在的JS文件。
解决办法有几个层面:Nginx对index.html设置禁止缓存的头,静态资源用Cache-Control加long-cache并依赖文件名hash来更新。最简单的是部署后强制刷新一次(Ctrl+Shift+R),但给用户用的正式环境不应该依赖手动刷新,所以我在Nginx里加了这样一段:
location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate"; }5.3 数据库连接和认证的一连串问题
MySQL 8相比MySQL 5.7,默认的用户认证插件改成了caching_sha2_password,这个插件更安全,但一些老版本的客户端和某些Node数据库驱动不完全支持,报错内容一般是“Authentication plugin 'caching_sha2_password' cannot be loaded”这类。
当时我查了半天,发现是我Node后端用的一个老版本MySQL驱动不认识这个新插件。解决办法是把项目数据库账号的认证方式改回旧版:
ALTER USER 'social_app'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;如果项目用的驱动是mysql2,它对新插件支持很好,不改也行。这里提醒一句:能升级驱动就升级驱动,尽量不降级认证方式来迁就旧客户端,毕竟新插件更安全。但在做学习项目没有生产兼容性约束时,快速切换认证方式也是一种务实的解决方案。
还有一个数据库问题容易被忽略:字符集。刚开始建库时没有显式指定utf8mb4,结果通过后端接口写入的中文变成了问号。处理方案是统一的:建库用DEFAULT CHARACTER SET utf8mb4,Node连接串里带?charset=utf8mb4,这样从表结构到连接层面都保持一致的字符集,乱码问题就不存在了。
5.4 时区差8小时引发的“登录失效”
这个坑早期很坑。服务部署好之后,用户登录后拿到JWT,服务端签发时设置了过期时间。结果过了8小时(正好是一个时区),登录态就莫名其妙失效。查了日志,发现是服务器时间比本地时间慢了8小时,系统时区是UTC,而我们在东八区,于是所有和过期时间相关的逻辑全乱了。
解决办法一句话:
timedatectl set-timezone Asia/Shanghai然后重启Node服务。设置后建议执行date -R确认时区正确。这个细节在本地开发时完全碰不到,因为本机时区本来就是正常的。
5.5 pm2重启后服务丢失
上文提到过,重启服务器后,发现服务没自动跑起来。原因是当初部署时只执行了pm2 start,没有执行pm2 save,pm2的dump文件里是空的,重启后它根本不知道要去恢复什么进程。
正确流程是:每次用pm2 start或pm2 restart之后,确认进程状态正常,再执行一次pm2 save。pm2 startup只需要执行一次,但pm2 save每次变更进程列表之后都应该做。这个习惯我吃了亏才完全记住。
6. 域名与HTTPS:从演示到像样上线
6.1 域名解析
直接用IP访问虽然能跑通,但显得很临时,很多功能也受限(比如某些浏览器对非标准端口或纯IP的兼容性)。我买了一个域名,在云厂商的DNS控制台添加一条A记录,把域名解析到服务器公网IP。
这里有一个需要注意的事实:国内云服务器绑定域名需要完成备案审核,审核通过前不能直接通过80端口访问。如果不想备案,可以考虑使用海外服务器节点,同样需要注意网络连通性和访问速度的差异。我建议根据自己的使用场景来决定,学习项目用海外节点起步比较快,正式商用项目按合规要求走流程。
DNS解析生效后,把Nginx配置里的server_name从_改成你的域名,然后systemctl reload nginx,就可以通过域名访问了。
6.2 用certbot配置免费HTTPS
网站没有HTTPS,浏览器地址栏会显示“不安全”,而且有些浏览器对非HTTPS页面的地理位置、摄像头等API会有权限限制。我用了Let's Encrypt的免费证书,配合certbot自动签发和续期。
apt install certbot python3-certbot-nginx -y certbot --nginx -d 你的域名certbot会自动检测Nginx配置,为域名签证书,然后修改Nginx配置加入SSL相关指令。执行完成后,再次访问网站,地址栏就有了小锁。
证书有90天有效期,certbot会通过系统定时任务自动续期。手动验证一下续期机制是否正常:
certbot renew --dry-run看到“Congratulations”之类的输出,就说明证书续期没问题。以后不用隔三个月手动换一次证书。
HTTPS配置好以后,Nginx的监听会变成listen 443 ssl,同时保留80端口跳转到HTTPS。如果要强制跳转,在certbot生成的配置里加一条:
server { listen 80; server_name 你的域名; return 301 https://$host$request_uri; }这样不管用户输http://还是https://,最终都会走加密连接。
7. 复盘笔记与后续运维习惯
7.1 pm2就是运维里的常驻守护
部署完成不代表事情结束,日常上线后的运维才是更长期的活。pm2的常用命令组合我整理成了一份清单:
pm2 list # 查看所有进程状态 pm2 logs social-backend # 查看后端日志 pm2 restart social-backend # 重启后端进程 pm2 stop social-backend # 停止后端进程 pm2 delete social-backend # 从pm2列表中移除 pm2 save # 保存当前进程列表,防止重启丢服务 pm2 startup # 设置开机自启有小版本更新时,我的操作序列是:拉取新代码、安装新依赖、pm2 restart、pm2 save,四步一条龙。后端接口如果出现大量报错,先pm2 logs看报错堆栈,而不是盲目重启,很多问题都是可以追溯的。
7.2 数据库备份:别让数据成为单点
社交管理系统的核心资产是用户数据和内容数据,用的MySQL必须要有备份。我写了一个简单的定时备份脚本,放到/opt/scripts/backup.sh:
#!/bin/bash BACKUP_DIR=/var/backups/social DATE=$(date +%F) mysqldump -u social_app -p'你的密码' social_system > ${BACKUP_DIR}/social_${DATE}.sql find ${BACKUP_DIR} -name "*.sql" -mtime +7 -delete给脚本加执行权限,然后写进crontab:
0 3 * * * /opt/scripts/backup.sh > /dev/null 2>&1每天凌晨3点自动备份一次,保留最近7天的备份,过期自动删除。恢复环境时用:
mysql -u social_app -p social_system < social_2025-01-01.sql这种简单的备份策略适合中小项目。如果有更高的数据可靠性要求,可以再做binlog或者快照备份,但基础款的备份习惯一定要先养成。
7.3 我个人建议的上线流程
根据这次部署的实际经验,我给同样准备上手的朋友三条建议。
第一,上线前一天先把部署流程整理成文档,哪怕只是给自己看。我这次踩坑后把每一步写成了清单,包括具体的命令、路径、端口,二次部署时照着清单走,效率高很多,也不会漏掉安全组或pm2 save这种小动作。
第二,不要在服务器上直接敲改动。所有代码改动先在本机测试,通过后再上传更新。服务器是“运行环境”,不是“开发环境”,虽然在一台机器上又跑服务又敲代码也能运转,但出了问题就很难区分是代码问题还是环境问题。
第三,部署完成不等于万事大吉,先跑一周日志观察。看pm2日志里有什么异常报错,看MySQL慢查询是不是有接口特别慢,看磁盘空间有没有被日志文件涨满。尽早发现问题,比用户来投诉再处理要好无数倍。
结尾
这次把Vue前端和Node后端部署到Ubuntu云服务器的整个过程,回想下来最大的感受是:本地能跑和线上能跑之间隔着太多工程细节。安全组放行、环境变量配置、Nginx路径匹配、pm2进程守护、时区设置、数据库字符集,任何一个环节出问题,都会表现为“打不开”或者“登录不上”,排查起来相当考验耐心。
我在实际操作中总结的一个小技巧是:遇到问题后,先看“错误是出现在哪个层面”。浏览器能打开但接口报错,问题在后端或Nginx代理;浏览器直接连不上,问题在安全组或Nginx监听;接口能通但数据乱码,问题在数据库字符集或后端配置。按这个思路一层层缩小范围,比瞎试命令高效得多。
最后再分享一个小习惯:每次部署完成后,把服务器上跑的服务、端口、目录结构列成一份清单,放到自己笔记里。过三个月再回来看这个项目,你会感谢当初记下的这些细节的。