【前情提要】 日志看懂了,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
“回来了。”小林松了口气。
排错思路总结:
curl连不上,先看systemctl status nginx,确认服务在跑。- 服务在跑但连不上,看
ss -lntp | grep :80,确认端口监听。 - 端口没监听,检查 Nginx 配置是否写错。
sudo nginx -t测试配置。- 配置错误,改回来,再
reload。 - 记住顺序:改配置 →
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:放行 80sudo ufw allow 22/tcp:放行 SSHsudo ufw enable:启用防火墙- 启用前先放行 22,不然 SSH 断
八、项目小结:博客部署全流程
“博客项目做完了。”老周说,“你回顾一下,从零到上线,你做了哪些事。”
小林打开笔记本,一条一条念:
- 装虚拟机:VMware Fusion + Ubuntu 26.04 LTS ARM64 + NAT 网络
- 基础配置:用户、权限、SSH 密钥、nano
- 装 Hugo:二进制安装,
hugo new site - 装主题:
git clonePaperMod,配置hugo.toml - 写文章:front matter、Markdown、图片、代码块、TOC
- 生成静态文件:
hugo -d /var/www/blog - 装 Nginx:配置
sites-available/blog,try_files,reload - 看日志:
access.log、error.log、grep、awk统计 - 健康检查:
systemctl、ss、curl,写检查脚本
“九步。”老周说,“你从‘终端都不会开’到‘能独立部署一个博客’,用了两周。这个速度,可以。”
小林嘿嘿笑。
“但你别得意。”老周话锋一转,“你现在只会‘部署’,不会‘运维’。部署是一次性的,运维是持续的。博客上线只是开始,它什么时候挂、为什么挂、挂了怎么知道,这才是运维要解决的问题。”
“那怎么解决?”
“监控。”老周说,“让机器自己告诉你,它什么时候不舒服。”
小林眼睛亮了:“这就是下一个项目?”
“对。”老周说,“从明天开始,我们装 Prometheus、Grafana、Alertmanager。让你的博客,会自己报警。”
九、练习
- 练习一:用
systemctl status nginx、sudo ss -lntp | grep :80、curl -I http://localhost三条命令,验证 Nginx 服务、端口、响应都正常。把三条命令的输出保存到一个文件~/nginx_health.txt。 - 练习二:写一个脚本
~/check_nginx.sh,检查 Nginx 服务是否运行、80 端口是否监听、HTTP 是否返回 200。如果任意一项失败,输出[FAIL]并退出码为 1。运行脚本,确认输出。 - 练习三:故意把 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、看日志、做健康检查。但老周说:“博客上线只是开始,它什么时候挂、为什么挂、挂了怎么知道,这才是运维要解决的问题。”
从下一集开始,小林进入第二个项目:监控告警。他要让博客“会自己报警”。
下一集:《老周的灵魂拷问:博客挂了,你怎么知道?》