【前情提要】 日志看懂了,404 和 500 也排查了。老周说:“博客上线了,但你不知道它什么时候会挂。”小林问:“那怎么办?”老周说:“你得让它自己告诉你。”

一、老周的三连问

博客上线一周,小林觉得自己已经是个“运维工程师”了。

早上到工位,老周端着保温杯走过来,没有表扬,而是问了三个问题:

“第一,Nginx 现在在跑吗?”

“在跑啊。”

“你怎么知道?”

“我昨天还访问了。”

“第二,80 端口在监听吗?”

“在吧?”

“第三,如果现在有人访问博客,能返回 200 吗?”

“应该能吧?”

老周笑了:“‘在吧’、‘应该能’——这就是你和运维的区别。运维不说‘应该’,运维说‘我验证过了’。”

小林沉默了。

“今天教你三件事:看服务、看端口、看响应。 学完这三个,你就能回答我刚才的问题,而且是用命令回答,不是靠猜。”

二、看服务:systemctl status

“第一个,看服务。”老周说,“Nginx 是一个 systemd 服务。systemd 是 Ubuntu 的服务管理器,所有后台服务都归它管。”

“怎么看?”

“systemctl status 服务名。”

小林敲:

systemctl status nginx

输出:

● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2026-09-13 10:00:00 CST; 1h ago
       Docs: man:nginx(8)
    Process: 1234 ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;' (code=exited, status=0/SUCCESS)
    Process: 1235 ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;' (code=exited, status=0/SUCCESS)
   Main PID: 1236 (nginx)
      Tasks: 3 (limit: 4567)
     Memory: 5.2M
        CPU: 12ms
     CGroup: /system.slice/nginx.service
             ├─1236 nginx: master process /usr/sbin/nginx -g 'daemon on; master_process on;'
             ├─1237 nginx: worker process
             └─1238 nginx: worker process

“信息很多,但你只需要看三个地方。”老周说。

第一,Loaded 行。

“loaded 表示配置文件被加载,enabled 表示开机自启。你看到 enabled,说明 Nginx 开机自动启动。如果看到 disabled,说明开机不启动,你得手动 systemctl start nginx。”

第二,Active 行。

“active (running) 表示正在运行。如果是 inactive (dead),说明没跑。如果是 failed,说明启动失败。这一行是你最常看的。”

第三,Main PID 和 CGroup。

“Main PID 是主进程号,CGroup 下是子进程。Nginx 是 master-worker 模型,一个 master 进程,多个 worker 进程。你看到两个 worker,说明它正常工作。”

“那如果服务挂了,怎么启动?”

“systemctl start nginx。停止是 stop,重启是 restart,重新加载配置是 reload。”老周说,“你记住这四个:start、stop、restart、reload。reload 不中断连接,restart 会断。改配置用 reload,服务挂了用 restart。”

小林记下:

  • systemctl status nginx:看状态
  • systemctl start/stop/restart/reload nginx:启动/停止/重启/重载
  • active (running):正常运行
  • enabled:开机自启

三、看端口:ss -lntp

“第二个,看端口。”老周说,“服务在跑,不代表端口在监听。你得确认 80 端口真的被 Nginx 占用了。”

“怎么看?”

“ss -lntp。”

小林敲:

sudo ss -lntp

输出:

State    Recv-Q   Send-Q   Local Address:Port    Peer Address:Port   Process
LISTEN   0        511            0.0.0.0:80           0.0.0.0:*       users:(("nginx",pid=1236,fd=6),("nginx",pid=1237,fd=6))
LISTEN   0        128            0.0.0.0:22           0.0.0.0:*       users:(("sshd",pid=800,fd=3))
LISTEN   0        511               [::]:80              [::]:*       users:(("nginx",pid=1236,fd=6),("nginx",pid=1237,fd=6))

“看第一行。”老周说,“0.0.0.0:80,说明 80 端口在所有网络接口上监听。users:(("nginx",...)) 说明是 Nginx 占用的。这就是你要的答案:80 端口在监听,Nginx 在跑。”

“那 -lntp 分别啥意思?”

“-l 是 listening,只看监听状态的。-n 是 numeric,不解析服务名,直接显示端口号。-t 是 TCP。-p 是 process,显示进程。这四个组合,是看端口的标准命令。”

“那 0.0.0.0 和 [::] 有啥区别?”

“0.0.0.0 是 IPv4,[::] 是 IPv6。”老周说,“Nginx 默认同时监听 IPv4 和 IPv6。你看到两行,说明两个协议都监听了。”

“那如果我只想确认 80 端口,有没有更简单的命令?”

“有。”老周说,“ss -lntp | grep :80,或者 lsof -i :80。但 lsof 不一定装了,ss 是系统自带的,更稳。”

小林记下:

  • sudo ss -lntp:查看所有监听端口
  • sudo ss -lntp | grep :80:只看 80 端口
  • 0.0.0.0:80:IPv4 监听
  • [::]:80:IPv6 监听
  • users:(("nginx",...)):哪个进程占用

四、看响应:curl -I

“第三个,看响应。”老周说,“服务和端口都对,但返回什么状态码?你得用 curl 验证。”

“curl 不是下载工具吗?”

“curl 是 HTTP 客户端,能发请求、看响应。你用它验证服务是否正常。”老周说,“curl -I 只看响应头,不下载内容。”

小林敲:

curl -I http://localhost

输出:

HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Date: Sat, 13 Sep 2026 10:30:00 GMT
Content-Type: text/html
Content-Length: 1234
Last-Modified: Sat, 13 Sep 2026 10:00:00 GMT
Connection: keep-alive
ETag: "..."
Accept-Ranges: bytes

“第一行,HTTP/1.1 200 OK。”老周说,“200 表示成功。如果是 404,说明路径不对。如果是 502,说明后端有问题。如果是 000,说明连不上。”

“那 Server 行呢?”

“nginx/1.24.0 (Ubuntu),说明是 Ubuntu 的 Nginx。你以后排查问题,知道版本很重要。”

“Content-Length 呢?”

“1234 字节,说明首页大小。你以后监控响应大小,就看这个。”

“那 curl -I 和 curl -i 有啥区别?”

“-I 只发 HEAD 请求,只看响应头。-i 发 GET 请求,响应头和内容都看。你验证服务健康,用 -I 就够了,快。”

“那如果我想看响应时间呢?”

“curl -o /dev/null -s -w "%{http_code} %{time_total}\n" http://localhost。”老周说,“-o /dev/null 把内容丢掉,-s 静默模式,-w 输出格式。%{http_code} 是状态码,%{time_total} 是总耗时。”

小林试了一下:

curl -o /dev/null -s -w "%{http_code} %{time_total}\n" http://localhost

输出:

200 0.003

“3 毫秒。”小林说,“挺快。”

“静态博客,当然快。”老周说,“你以后监控,可以把响应时间作为指标。超过 1 秒就告警。”

小林记下:

  • curl -I http://localhost:只看响应头
  • curl -i http://localhost:看响应头和内容
  • curl -o /dev/null -s -w "%{http_code} %{time_total}\n":输出状态码和耗时
  • 200:成功,404:找不到,502:后端错误,000:连不上

五、服务健康检查三件套

“现在你把三件事串起来。”老周说,“一个完整的服务健康检查,就是三步:”

第一步:服务在跑吗?

systemctl is-active nginx

“is-active 只输出 active 或 inactive,适合脚本里判断。”

第二步:端口在监听吗?

ss -lntp | grep -q ':80 '

“-q 是 quiet,找到就返回 0,找不到返回 1。适合脚本里判断。”

第三步:响应正常吗?

curl -o /dev/null -s -w "%{http_code}" http://localhost

“输出 200 就是正常。”

“现在你把这三步写成一个脚本。”老周说。

小林打开 nano:

nano ~/check_nginx.sh

写入:

#!/bin/bash

echo "检查 Nginx 服务状态..."
if systemctl is-active --quiet nginx; then
    echo "  [OK] Nginx 正在运行"
else
    echo "  [FAIL] Nginx 未运行"
    exit 1
fi

echo "检查 80 端口监听..."
if ss -lntp | grep -q ':80 '; then
    echo "  [OK] 80 端口正在监听"
else
    echo "  [FAIL] 80 端口未监听"
    exit 1
fi

echo "检查 HTTP 响应..."
HTTP_CODE=$(curl -o /dev/null -s -w "%{http_code}" http://localhost)
if [ "$HTTP_CODE" = "200" ]; then
    echo "  [OK] HTTP 返回 200"
else
    echo "  [FAIL] HTTP 返回 $HTTP_CODE"
    exit 1
fi

echo "全部检查通过。"

保存,加执行权限:

chmod +x ~/check_nginx.sh

运行:

~/check_nginx.sh

输出:

检查 Nginx 服务状态...
  [OK] Nginx 正在运行
检查 80 端口监听...
  [OK] 80 端口正在监听
检查 HTTP 响应...
  [OK] HTTP 返回 200
全部检查通过。

“成了。”小林说。

“这就是运维的思维方式。”老周说,“不要猜,要验证。不要说‘在吧’,要说‘我检查过了,在!’。”

六、排错挑战:Nginx 在跑,但 curl 连不上

下午,小林想验证一下博客,敲:

curl -I http://localhost

输出:

curl: (7) Failed to connect to localhost port 80: Connection refused

“啥?”小林懵了,“刚才还好好的。”

他检查服务:

systemctl status nginx

输出:

Active: active (running)

“在跑啊。”

他检查端口:

sudo ss -lntp | grep :80

没有输出。

“80 端口没监听?”小林更懵了,“服务在跑,端口没监听?”

他喊来老周。

老周看了一眼,问:“你刚才是不是改过 Nginx 配置?”

“我就改了一个 server_name……”

“改了之后 nginx -t 了吗?”

“没有……”

“reload 了吗?”

“没有……”

“那 Nginx 还在用旧配置跑。”老周说,“你 systemctl status 看到 active (running),但那是旧配置。你改的新配置没生效,而且可能写错了,导致 reload 失败,但服务还在跑旧配置。”

“那怎么办?”

“先测配置。”

sudo nginx -t

输出:

nginx: [emerg] unknown directive "server_nam" in /etc/nginx/sites-enabled/blog:5
nginx: configuration file /etc/nginx/nginx.conf test failed

“看到了吗?server_nam 少了个 e。”老周说,“你 nginx -t 能测出来。你以后改配置,先 nginx -t,再 reload。 顺序不能反。”

小林改回来:

sudo nano /etc/nginx/sites-enabled/blog

把 server_nam 改成 server_name。

sudo nginx -t
sudo systemctl reload nginx

再次验证:

curl -I http://localhost

输出:

HTTP/1.1 200 OK

“回来了。”小林松了口气。

排错思路总结:

  1. curl 连不上,先看 systemctl status nginx,确认服务在跑。
  2. 服务在跑但连不上,看 ss -lntp | grep :80,确认端口监听。
  3. 端口没监听,检查 Nginx 配置是否写错。
  4. sudo nginx -t 测试配置。
  5. 配置错误,改回来,再 reload。
  6. 记住顺序:改配置 → nginx -t → reload。 永远不要跳过 nginx -t。

“还有一个可能。”老周补充,“你 systemctl status 看到 active,但 ss 没看到 80 端口,还有一种情况:防火墙拦截。 不过你现在是本地 curl,不经过防火墙。如果是从 Mac 访问不了,就要检查 ufw。”

小林记下:

  • 本地 curl 连不上:检查配置、nginx -t、reload
  • Mac 访问不了:检查 ufw 是否放行 80
  • 改配置顺序:nano → nginx -t → reload

七、防火墙:ufw 入门

“既然提到防火墙,我就顺便讲了。”老周说,“Ubuntu 默认有个 ufw,简单防火墙。你 Mac 访问不了 Ubuntu 的 Nginx,很可能就是它拦的。”

“怎么看?”

sudo ufw status

输出:

Status: inactive

“没启用。”小林说。

“对。Ubuntu 默认不启用 ufw。但如果你以后启用了,就得放行 80 端口。”

sudo ufw allow 80/tcp
sudo ufw allow 22/tcp
sudo ufw enable

“allow 80/tcp 放行 80 端口,allow 22/tcp 放行 SSH,enable 启用。启用前一定要先放行 22,不然你 SSH 就断了。”

“那 Mac 访问不了,怎么排查?”

“三步。”老周说,“第一,sudo ufw status 看防火墙状态。第二,如果 active,看 80 端口有没有放行。第三,如果没放行,sudo ufw allow 80/tcp。如果防火墙没启用,那问题不在防火墙,在 Nginx 或网络。”

小林记下:

  • sudo ufw status:查看防火墙状态
  • sudo ufw allow 80/tcp:放行 80
  • sudo ufw allow 22/tcp:放行 SSH
  • sudo ufw enable:启用防火墙
  • 启用前先放行 22,不然 SSH 断

八、项目小结:博客部署全流程

“博客项目做完了。”老周说,“你回顾一下,从零到上线,你做了哪些事。”

小林打开笔记本,一条一条念:

  1. 装虚拟机:VMware Fusion + Ubuntu 26.04 LTS ARM64 + NAT 网络
  2. 基础配置:用户、权限、SSH 密钥、nano
  3. 装 Hugo:二进制安装,hugo new site
  4. 装主题:git clone PaperMod,配置 hugo.toml
  5. 写文章:front matter、Markdown、图片、代码块、TOC
  6. 生成静态文件:hugo -d /var/www/blog
  7. 装 Nginx:配置 sites-available/blog,try_files,reload
  8. 看日志:access.log、error.log、grep、awk 统计
  9. 健康检查:systemctl、ss、curl,写检查脚本

“九步。”老周说,“你从‘终端都不会开’到‘能独立部署一个博客’,用了两周。这个速度,可以。”

小林嘿嘿笑。

“但你别得意。”老周话锋一转,“你现在只会‘部署’,不会‘运维’。部署是一次性的,运维是持续的。博客上线只是开始,它什么时候挂、为什么挂、挂了怎么知道,这才是运维要解决的问题。”

“那怎么解决?”

“监控。”老周说,“让机器自己告诉你,它什么时候不舒服。”

小林眼睛亮了:“这就是下一个项目?”

“对。”老周说,“从明天开始,我们装 Prometheus、Grafana、Alertmanager。让你的博客,会自己报警。”

九、练习

  1. 练习一:用 systemctl status nginx、sudo ss -lntp | grep :80、curl -I http://localhost 三条命令,验证 Nginx 服务、端口、响应都正常。把三条命令的输出保存到一个文件 ~/nginx_health.txt。
  2. 练习二:写一个脚本 ~/check_nginx.sh,检查 Nginx 服务是否运行、80 端口是否监听、HTTP 是否返回 200。如果任意一项失败,输出 [FAIL] 并退出码为 1。运行脚本,确认输出。
  3. 练习三:故意把 Nginx 配置里的 server_name 改成 server_nam,保存后执行 sudo nginx -t,观察报错。改回来后执行 sudo nginx -t 和 sudo systemctl reload nginx,再次验证 curl -I http://localhost 返回 200。

参考答案

练习一:

{
    echo "=== systemctl status ==="
    systemctl status nginx | head -5
    echo "=== ss port 80 ==="
    sudo ss -lntp | grep :80
    echo "=== curl response ==="
    curl -I http://localhost
} > ~/nginx_health.txt

cat ~/nginx_health.txt

练习二:

nano ~/check_nginx.sh

内容:

#!/bin/bash

if ! systemctl is-active --quiet nginx; then
    echo "[FAIL] Nginx 未运行"
    exit 1
fi

if ! ss -lntp | grep -q ':80 '; then
    echo "[FAIL] 80 端口未监听"
    exit 1
fi

HTTP_CODE=$(curl -o /dev/null -s -w "%{http_code}" http://localhost)
if [ "$HTTP_CODE" != "200" ]; then
    echo "[FAIL] HTTP 返回 $HTTP_CODE"
    exit 1
fi

echo "[OK] 全部检查通过"
chmod +x ~/check_nginx.sh
~/check_nginx.sh

练习三:

sudo nano /etc/nginx/sites-enabled/blog

把 server_name localhost; 改成 server_nam localhost;。

sudo nginx -t

输出:

nginx: [emerg] unknown directive "server_nam" in /etc/nginx/sites-enabled/blog:5
nginx: configuration file /etc/nginx/nginx.conf test failed

改回来:

sudo nano /etc/nginx/sites-enabled/blog

改回 server_name localhost;。

sudo nginx -t
sudo systemctl reload nginx
curl -I http://localhost

输出 HTTP/1.1 200 OK。

十、下集预告

博客部署项目完结。小林学会了装虚拟机、配 Hugo、写文章、部署 Nginx、看日志、做健康检查。但老周说:“博客上线只是开始,它什么时候挂、为什么挂、挂了怎么知道,这才是运维要解决的问题。”

从下一集开始,小林进入第二个项目:监控告警。他要让博客“会自己报警”。

下一集:《老周的灵魂拷问:博客挂了,你怎么知道?》