Linux 常用命令:前端第一次上服务器排查,最该先会什么

很多前端对服务器的认知都停在 npm run build 之前,真到线上白屏、日志没人看、运维不在场的时候,才会发现自己连最基本的目录、进程、日志和端口都不会查。

我第一次被这件事正面教育,就是一个已经部署完成却直接白屏的后台项目。构建没报错,部署脚本也跑完了,但页面就是打不开。手里虽然早就有 SSH 账号,之前却一次都没真正上过机器;那晚硬着头皮连上去,才发现自己对终端里最基础的定位和排查动作都不熟。

下面不做 Linux 大而全清单,而是按一次真实线上排查会走到的路径,把前端最该会的那几类命令串起来:先确认自己在哪,再找日志、看进程、查端口、对文件、判断服务到底死在哪一层。

先说一句前提:下面假设你已经能 ssh user@your-server 登进服务器了。如果连这步都还没通,先去运维那要一个账号和密钥,别用密码登,麻烦还不安全。

第一件事:搞清楚自己站在哪

登上服务器,我现在做的第一件事永远是确认"我在哪个目录、这目录里有什么"。在自己机器上这无所谓,但线上一台机器可能跑着好几个项目,搞错目录改错文件是真会出事的。

1pwd          # 我现在在哪
2cd /var/www/app
3ls -la       # 看全部文件,含隐藏文件和权限

ls -la 是我用得最多的形式,-l 给出权限/属主/大小/修改时间,-a.env.nginx 这种点开头的隐藏文件也列出来。前端项目里 .env.production 经常就是配置出错的源头,不加 -a 你根本看不见它。

再加 -h 让大小变成人能读的单位,配 -t 按时间排序——出问题时"哪个文件是刚被动过的"特别关键:

1ls -laht     # 按修改时间倒序,最新的在最上面

那晚的第一个突破口就是这条命令给的。我摸到项目目录敲了 ls -laht,发现 dist 目录的修改时间还是三天前——新包根本没传上来,CI 那一步静默失败了。要不是这一眼,我可能还在前端代码里找半天"白屏的 bug"。

目录层级深的时候,装个 tree 看结构很省事:

1tree -L 2 -a    # 只看两层,省得刷屏

删除:线上最该手抖的地方

排查中途我一度想把旧的 dist 删掉重传:

1rm app.log
2rm -rf dist

rm -rf 这条我每次敲都心里发紧。它不进回收站,敲下回车文件就没了。补课时我给自己定了几条铁律:

  • rm -rf 后面带变量或通配符时,先 echo 一遍。比如想删 rm -rf $BUILD_DIR/*,先 echo rm -rf $BUILD_DIR/* 看清楚展开成什么再说。$BUILD_DIR 万一是空的,那条命令就成了 rm -rf /*,能把整台机器抹平。
  • 删之前先 pwd 确认路径,别在 / 或者 /home 这种地方手贱。
  • 重要文件先 mv 改名留底,而不是直接删,比如 mv app.log app.log.bak。那晚我就是把旧 distmv dist dist.bak,事后证明这个习惯值钱——新包传上来还有别的问题时,我还能随时切回去。

看日志:排查的主战场

线上白屏、500、接口超时,第一反应都该是去看日志。前端这边主要看两类:Nginx 的访问/错误日志,还有 Node 服务(如果是 SSR 或 BFF)自己的日志。

文件小的直接 cat

1cat package.json

但日志文件动辄几百兆,cat 一下整个终端就刷爆了,还可能把机器内存吃满。那晚我不懂事,真的 cat 了一下 access.log,终端滚了一分多钟,Ctrl+C 都按出了火气。日志别 cat,用下面几招。

分页看,能上下翻、能搜:

1less /var/log/nginx/error.log

less 里按 /error 搜关键词,n 跳下一个,G 跳到文件末尾,q 退出。比 vim 打开大文件快太多,而且只读不怕手抖改坏。

只关心最近发生了什么,就看尾巴:

1tail -n 100 /var/log/nginx/error.log

实时盯日志,现在是我每次上线必开的一个窗口:

1tail -f /var/log/nginx/error.log

tail -f 会一直挂着,新日志滚进来立刻显示。重新部署那一刻我一边在另一个窗口刷页面,一边盯着这个,请求一进来报没报错一目了然。如果日志被切割(logrotate)后想跟着新文件,用 tail -F(大写),它会在文件被重建后自动重新打开。

补课之后我最常用的组合是边过滤边实时跟:

1tail -f /var/log/nginx/access.log | grep " 500 "

这样满屏的正常请求都被滤掉,只有真出 500 的时候才跳一行出来,眼睛不累。

踩坑提醒:页面 404 别急着改 Nginx 配置。那晚我一度怀疑是 nginx.conf 的锅,差点动手改 try_files,幸好先看了错误日志——先看错误日志里的真实路径,它会告诉你 Nginx 到底去哪个文件找了没找到,比瞎猜配置快一个数量级。我的问题根本不在配置,在包没传上来。

grep:在一堆文本里捞针

grep 是我这周补课后用得仅次于 ls 的命令。日志里找报错、配置里找某个域名、代码里找某个变量,全靠它。

1grep "error" app.log
2grep -i "error" app.log        # 忽略大小写,Error/ERROR 都能匹配

常用的几个参数:

1grep -n "timeout" app.log      # 带行号,方便定位
2grep -C 5 "Exception" app.log  # 命中行的上下各 5 行一起显示,看上下文
3grep -c "500" access.log       # 只数有多少行,统计错误量

-C(context)这个我特别推荐。光看到 "Exception" 一行没用,往往是上下几行的堆栈和请求参数才说明问题。

查 Nginx 配置时递归搜整个目录:

1grep -rn "server_name" /etc/nginx/

-r 递归,-n 带行号,一下就能定位某个域名配在哪个 conf 文件的第几行。Nginx 配置经常被拆成 sites-enabled/ 下一堆文件,那晚我想确认域名指向哪个根目录,就是靠这条找到的,不然一个个文件翻能翻到天亮。

进阶一点,统计访问日志里哪个 IP 请求最多(防刷/排查异常流量时有用),这是经典的"命令拼装":

1awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20

awk 取第一列(IP),sort 排序好让 uniq -c 能去重计数,再 sort -rn 按次数从大到小,head -20 取前 20。Linux 命令的威力一大半就在这种管道拼装上,单个命令都很笨,串起来就很强

进程和端口:服务到底活着没

接口全挂的时候,先别怀疑代码,确认进程还在不在:

1ps aux | grep node

ps aux 列出所有进程,管道给 grep node 过滤出 Node 相关的。这里有个我当场就踩的坑:结果里会多出一行 grep node 自己——因为 grep node 这个进程本身也包含 "node" 这个字符串。我盯着那行"多出来的进程"研究了好几分钟才明白怎么回事。解法是用正则把它排掉:

1ps aux | grep '[n]ode'

[n]ode 能匹配到 node,但 grep '[n]ode' 这个命令行字符串里写的是 [n]ode 不是 node,于是它匹配不到自己。小技巧,但天天用。

如果项目用 pm2 管 Node 进程(现在这几乎是标配),直接:

1pm2 list
2pm2 logs app-name --lines 100

看端口被谁占了——服务启动报 EADDRINUSE 时的标准动作:

1lsof -i :3000

它会列出占用 3000 端口的进程和 PID。补课时带教的运维同事给我讲过他遇过最坑的一次:上次部署的旧进程没杀干净,端口被占着,新进程起不来,日志里只甩一句 address already in use。拿到 PID 后确认确实是僵尸进程,再杀掉:

1kill 12345        # 先温柔地发 SIGTERM,让进程自己清理后退出
2kill -9 12345     # 实在不退再用 -9 强杀,但这会丢失未保存状态,慎用

netstat 也能看端口,但有些精简系统没装,ss 是更现代的替代:

1ss -tlnp | grep 3000    # t=tcp l=listening n=不解析域名 p=显示进程

机器本身的体检:top、free 和 df

补课时他还教了我一套"先给机器把个脉"的动作。有时候服务慢不是代码慢,是机器本身不行了——CPU 打满、内存耗尽、磁盘写满,代码再对也白搭。

看 CPU 和进程负载:

1top             # 实时看,Shift+M 按内存排序,Shift+P 按 CPU 排序,q 退出

top 顶部那行 load average 的三个数是 1/5/15 分钟的平均负载,粗略地说,长期超过 CPU 核数就是过载了。往下能看到哪个进程在吃 CPU——上个月他们有个 Node 服务写了死循环,就是 top 一眼看到某进程 CPU 100% 定的位。

看内存:

1free -h

-h 换成人能读的单位。这里有个新手必懵的点:free 显示的 available 才是真正可用的内存,free 那列很小不代表内存不够——Linux 会把空闲内存拿去做缓存,需要时会让出来。别看到 free 快见底就慌着重启机器。

看磁盘,这个跟前端关系比想象中大:

1df -h              # 各分区还剩多少
2du -sh /var/log/*  # 看哪个目录占了多少,揪出大头

他说运维那边真出过一次"磁盘写满导致全站 502"的事故:日志没配切割,access.log 悄悄涨到几十个 G 把盘吃满,Node 写不了文件直接崩。从那以后我上服务器都会顺手 df -h 瞄一眼。磁盘满导致的故障,报错信息往往牛头不对马嘴,先看 df -h 能少走很多弯路。

还有一个偷师用的命令:

1history | tail -50

看这台机器上最近敲过什么命令。接手别人维护过的服务器时,history 就是现成的操作日志——运维之前在这台机器上怎么重启服务、日志在哪个路径,翻一翻历史全知道了,比问人快。

curl:不开浏览器也能测接口

服务器上没浏览器,curl 就是命令行浏览器。那晚我重传完包,想确认服务通没通,第一反应竟然是切回自己电脑开 Chrome——运维同事在群里发来一句"你 curl 一下啊",我才知道有这个东西。

只看响应头,确认服务通不通、状态码对不对:

1curl -I https://example.com

-I 发 HEAD 请求,只回头不回 body,看 HTTP/1.1 200 还是 502 一眼就清楚。

完整请求一个接口:

1curl https://example.com/api/health

排查时常用的加强版:

1curl -v https://example.com/api/health          # -v 打印整个握手+请求+响应过程
2curl -i https://example.com/api/health           # 响应头和 body 一起看
3curl -X POST https://example.com/api/login \
4     -H "Content-Type: application/json" \
5     -d '{"name":"test"}'                          # 带 header 和 body 的 POST

-v 在 HTTPS 出问题时尤其有用,证书过期、握手失败都会在那段输出里暴露。

有个区分特别重要:在服务器上 curl 接口通,不代表浏览器里就能通。服务器之间是机器对机器,没有跨域(CORS)这回事;浏览器有同源策略、有 OPTIONS 预检、有 cookie 和 referer 限制。所以前端报"接口跨域失败"时,你在服务器 curl 出来一切正常是正常的——问题在浏览器和后端的响应头(Access-Control-Allow-Origin 这些),不在接口本身。我之前没分清这一点,对着后端说"我这边明明能通啊",两边吵了半天才意识到是在说两件事。

测一下接口耗时,定位是不是后端慢:

1curl -o /dev/null -s -w "总耗时: %{time_total}s\n" https://example.com/api/health

权限:那晚的第二个坑,403

新包我用 scp 手动传上去了,刷新页面——白屏变成了 403。这就是那晚的第二个坑,也是前端部署里最典型的权限事故:Nginx 跑在 www-data 用户下,但我 scp 上去的文件属主是我自己的账号,Nginx 读不了,于是 403

碰到 permission denied 或者莫名 403,先 ls -l 看那一串 -rwxr-xr-x:第一段是属主权限,后面跟着属主和属组。改权限用 chmod

1chmod 755 deploy.sh
2./deploy.sh

755 是脚本的常用权限:属主可读写执行(7),同组和其他人可读可执行(5)。理解这三位数字其实不难——每位是 读(4)+写(2)+执行(1) 的和。配置文件这种不需要执行的,给 644(属主读写,别人只读)。

属主不对就用 chown 改(需要 sudo):

1sudo chown www-data:www-data -R /var/www/app

这条敲下去,刷新页面,那晚的白屏终于变回了正常页面,群里的 @ 也终于停了。现在我手动传完文件都会顺手把属主和权限校正一遍,成了肌肉记忆。

传文件:scp 和它的同类

顺着说传文件。临时往服务器丢个包,scp 最直接:

1scp dist.zip [email protected]:/var/www/app/

传整个目录加 -r

1scp -r dist/ [email protected]:/var/www/app/

scp 有个毛病:每次都全量传,目录大了又慢又费流量。补课后我改用 rsync,它只传有变化的部分,断了还能续:

1rsync -avz --delete dist/ [email protected]:/var/www/app/dist/

-a 保留权限/时间等属性,-v 显示过程,-z 传输时压缩,--delete 让远端删掉本地已经不存在的文件(保证两边完全一致,但用前要想清楚,它真的会删)。第二次同步同一个项目,rsync 通常几秒就完事,因为绝大多数文件没变。

当然,正经项目都该走 CI/CD(我们用 Jenkins,隔壁组在试 GitLab CI)自动部署,手动 scp 只该是应急或者验证时的手段。但应急的时候,会不会这一手,区别就是"五分钟搞定"还是"等运维明早上班"。那晚 CI 静默失败,恰恰就是手动传包救的场——事后我们也给 CI 加了部署结果的通知,静默失败这种事不该有第二次。

把那晚重走一遍:现在的我需要几分钟

事故之后我做了个练习:假设同样的场景再来一次,用这周学的命令把流程重走一遍,大概是这样:

1ssh user@prod-server                          # 登上去
2df -h && free -h                               # 机器本身健康吗
3cd /var/www/app && ls -laht                   # 看包是不是新的、有没有刚被动过
4pm2 list                                       # Node 服务还活着吗
5pm2 logs app-name --lines 50                   # 翻服务日志
6tail -n 50 /var/log/nginx/error.log           # 看 Nginx 把错误归因于什么
7curl -I http://127.0.0.1:3000/api/health      # 本机直连服务,确认不是 Nginx 的锅

走完这几步,问题就能从"线上挂了"收敛成一句具体的描述——"包没传上来"或者"Node 服务连不上数据库",能直接甩给对应的人去处理。那晚我摸索了两个小时的事,现在两分钟就能走完,没改一行代码。这就是命令行的价值——它让你能快速描述清楚现场,而不是只会在群里说"反正挂了"。

线上出问题,九成情况不需要你去改代码,而是需要你先把现场搞清楚——日志里报了什么、进程还活着没、端口通不通、Nginx 把请求转到哪去了。这些事 SSH 上去敲几条命令就能看明白,比在群里来回猜快得多。

前端工程师不需要成为 Linux 专家,但这套"看现场"的基本功躲不掉:

  • 定位pwd / ls -laht / tree——先搞清在哪、有什么、谁最新。
  • 看日志less / tail -f / 配合 grep 过滤——别 cat 大文件。
  • 搜内容grep -rn / -C 看上下文 / awk|sort|uniq 做统计。
  • 体检top / free -h / df -h——先排除机器本身的问题。
  • 查服务ps aux | grep '[n]ode' / lsof -i :端口 / kill
  • 测接口curl -I / -v,并且分清服务器和浏览器的差别。
  • 权限与传输chmod / chown / rsync

这些命令别死记,真到服务器上敲几遍、踩几次坑,自然就长在手上。第二天我把排查过程跟带教的运维同事复述了一遍,他回了四个字:"可以出师。"我知道差得还远,但至少下一个手忙脚乱的周四晚上,我不会再对着黑终端发呆了。