Linux 故障排查(一) 从故障现象到定位原因,掌握 Linux 排障五步法
1 | 作者:李晓辉 |
做 Linux 运维,难免会遇到这样的场景。
凌晨三点,你突然接到一个电话:
“网站访问不了了,赶紧看看服务器怎么回事!”
你登录服务器,发现 Web 服务无法正常工作。这时候,你会怎么做?
刚接触运维的同学,可能会下意识地执行:
1 | systemctl restart httpd.service |
重启之后,如果服务恢复了,就觉得问题解决了。如果还是不行,就继续重启,甚至直接重启整台服务器。
这种做法看似简单,实际上可能让排障变得更加困难。
- 故障现场可能发生变化。 服务重启后,进程状态、临时文件和部分运行信息可能已经改变。
- 真正的原因可能被掩盖。 服务暂时恢复,不代表根因已经消失。
- 故障范围可能扩大。 如果问题来自磁盘空间不足、配置错误或依赖服务异常,盲目重启可能引发更多问题。
- 历史证据可能丢失。 如果日志没有持久化保存,系统重启后,之前的部分日志可能无法再查询。
那么,遇到故障时应该怎么做?
先别急着重启。我们可以按照一套固定的排障流程,逐步确认问题、收集证据、验证原因,最后再进行修复。
这篇文章将从零开始,带你认识 Linux 运维中的排障五步法,并学习使用 journalctl 查询系统日志。
遇到故障,先学会排障五步法
排障不是猜测哪里出了问题,而是根据证据逐步缩小范围,最终找到根因。
你可以把排障理解成看病。
医生不会因为你说头疼,就直接判断你得了什么病。他会先了解症状,再进行检查,根据检查结果提出判断,最后制定治疗方案。
Linux 系统排障也是一样。
flowchart LR
A["第一步:复现并确认问题"] --> B["第二步:收集证据"]
B --> C["第三步:提出可能的原因"]
C --> D["第四步:验证原因"]
D -->|"证据不支持"| C
D -->|"确认根因"| E["第五步:修复并验证"]
E --> F["记录根因与处理过程"]复现并确认问题
假设用户反馈:“网站打不开了。”
这句话只能说明用户遇到了问题,还不能说明服务器上的 Apache 服务一定出了故障。
你需要进一步确认:
- 是所有用户都无法访问,还是只有某个用户无法访问?
- 是网站完全打不开,还是返回 HTTP 500、502 等错误?
- 是所有页面都有问题,还是只有某个页面有问题?
- 故障从什么时候开始?此前是否修改过配置、发布过程序或调整过网络?
这些信息可以帮助你缩小排查范围。
例如,用户访问网站时收到 HTTP 502 错误,可能是反向代理无法连接后端应用,也可能是后端应用异常退出。此时直接重启 Apache,不一定能解决问题。
排障的第一步,是把模糊的故障描述变成可以验证的具体问题。
收集证据
确认问题后,先收集相关信息,而不是立即修改配置。
例如,检查 Apache 服务状态:
1 | systemctl status httpd.service |
查看 Apache 最近的日志:
1 | journalctl -u httpd.service -n 50 --no-pager |
查看磁盘空间:
1 | df -h |
查看内存情况:
1 | free -h |
这些命令分别帮助你了解服务状态、近期日志、磁盘空间和内存使用情况。
为什么要检查这些信息?
假设 Apache 无法启动,日志中出现了 No space left on device,同时根分区已经使用了 100%。这时问题可能与磁盘空间不足有关,而不是 Apache 配置语法错误。
反过来,如果磁盘空间正常,日志却提示配置文件存在语法错误,那么就应该进一步检查配置。
注意,df -h 和 free -h 只能帮助你发现资源方面的线索,不能单凭它们就断定故障原因。
提出可能的原因
收集到证据后,再提出假设。
例如:
| 收集到的证据 | 可以提出的假设 |
|---|---|
| 日志提示配置文件存在语法错误 | 配置文件可能写错了 |
日志出现 Permission denied | 文件权限、目录权限或 SELinux 策略可能阻止了访问 |
| 根分区使用率达到 100% | 磁盘空间不足可能导致服务异常 |
| 服务启动后立即退出 | 应用程序可能发生错误,或者依赖项不可用 |
| 服务正常运行,但外部无法访问 | 可能涉及防火墙、路由、监听地址或上游网络 |
这里有一个重要原则:假设不是结论,日志中的一条报错也不一定就是根因。
例如,看到 Permission denied,不能直接认定是 SELinux 导致的。普通文件权限、目录访问权限、ACL 和 SELinux 都可能造成类似问题,需要继续验证。
验证假设
提出假设后,要通过检查来确认,而不是马上进行大范围修改。
例如,你怀疑 Apache 配置文件有问题,可以先执行配置语法检查:
1 | httpd -t |
如果输出:
1 | Syntax OK |
说明 Apache 配置通过了这次语法检查,但这并不代表网站的所有功能都一定正常。
如果输出包含类似下面的内容:
1 | AH00526: Syntax error on line 47 of /etc/httpd/conf/httpd.conf: |
就说明第 47 行附近存在配置问题,Listenn 很可能是把 Listen 拼错了。
此时应该检查对应配置,而不是继续重启服务。
验证过程中,尽量一次只改变一个因素,并在操作前做好备份。这样才能判断究竟是哪项修改起了作用,也方便在修改失败时恢复。
修复并验证
找到根因后,再实施修复。
例如,确认 Apache 配置中的 Listenn 是拼写错误后,将它修正为 Listen,然后重新执行:
1 | httpd -t |
确认输出为 Syntax OK 后,再根据实际情况启动或重启服务:
1 | systemctl restart httpd.service |
检查服务状态:
1 | systemctl status httpd.service --no-pager |
还需要从业务角度验证,例如检查网站是否可以正常访问、关键接口是否返回预期结果。
最后,把故障现象、根因、处理过程和验证结果记录下来。
服务恢复只是排障的一个阶段,确认根因并验证业务恢复,才算完成一次完整的排障。
Linux 系统中的日志存在哪里?
在开始学习 journalctl 之前,先认识 Linux 系统中的两种常见日志管理组件:systemd-journald 和 rsyslog。
它们经常一起工作,但不是同一个程序,也不是每台 Linux 机器都一定同时安装并启用了它们。
systemd-journald:收集和管理系统日志
systemd-journald 是 systemd 提供的日志服务。
它可以收集内核消息、系统服务输出,以及通过相应接口提交的应用程序日志等信息,并将日志保存在 journal 中。
journal 采用结构化的二进制日志格式,可以保留时间、来源、服务标识、优先级等字段,因此能够按照服务、时间、启动记录和严重级别进行过滤。
在使用 systemd 的 Linux 系统中,常见的 journal 存储位置包括:
/run/log/journal/:位于运行时文件系统中,通常不会跨系统重启保留。/var/log/journal/:位于磁盘上,可以用于持久化保存日志。
实际使用哪个位置,取决于 journald 的配置和系统中的目录情况。
rsyslog:将日志分发到文本文件或远程服务器
rsyslog 是另一个常见的日志服务,可以按照规则将日志写入文本文件、转发到远程日志服务器,或发送到其他目标。
例如,在相应的配置和规则生效时,日志可能会写入:
/var/log/messages:常见的系统消息日志。/var/log/secure:常见的认证和安全相关日志。/var/log/cron:常见的计划任务日志。
这些文件的存在、内容和实际分发规则取决于系统配置。不同 Linux 发行版的默认日志布局也可能不同。
在常见配置中,journald 收集日志,rsyslog 再读取其中的一部分日志并写入文本文件。不过,也有应用直接向 syslog 提交消息,或者直接写入自己的日志文件。
因此,可以把 journald 理解成系统日志收集与查询的重要入口,把 rsyslog 理解成可以进一步分发和保存日志的组件。
两者可能协作,但并不是所有日志都必然经过同一条处理链路。
先确认自己的系统使用什么
在学习后面的命令之前,可以先执行:
1 | systemctl status systemd-journald.service |
查看 journald 服务状态。
再执行:
1 | systemctl status rsyslog.service |
检查 rsyslog 服务状态。
如果系统中没有安装 rsyslog,第二条命令可能会提示找不到对应的单元。这不一定代表系统存在故障,只说明需要进一步确认当前系统的日志配置。
需要注意,systemd-journald 和 rsyslog 是两种不同的日志组件。即使 rsyslog 没有运行,只要 journald 正常收集并保存了相关日志,仍然可以使用 journalctl 查询这些记录。
journalctl:从系统日志中找到线索
journalctl 是查询 systemd journal 日志的命令,适用于使用 systemd 并提供 journal 查询功能的 Linux 系统。
如果直接执行:
1 | journalctl |
它通常会打开日志分页查看器,让你浏览当前能够访问到的 journal 记录。日志量较大时,这种方式不适合直接排查问题。
更有效的办法是根据故障发生的时间、相关服务和日志级别进行过滤。
下面逐个学习常用参数。
按服务过滤:-u
假设你发现 SSH 登录出现异常,想先查看 sshd 服务的日志。
执行:
1 | journalctl -u sshd.service |
部分输出可能如下:
1 | Jun 17 08:15:03 servera systemd[1]: Starting sshd.service - OpenSSH server daemon... |
这些日志可以提供几个线索:
Jun 17 08:15:03:日志记录的时间。servera:记录日志的主机名。systemd[1]:表示日志来自 PID 为 1 的 systemd 进程。sshd[1205]:表示日志来自 PID 为 1205 的 sshd 进程。Server listening on ... port 22:表示 sshd 记录了相应的监听信息。
这里的输出是示例,不同系统版本、配置和运行状态下的实际内容可能不同。
-u 后面跟的是 systemd 单元名称。对于服务单元,通常可以写成:
1 | journalctl -u sshd |
也可以写成:
1 | journalctl -u sshd.service |
如果不确定服务名称,可以先执行:
1 | systemctl list-units --type=service --all |
注意,列出运行时已加载的服务单元,并不等于列出了系统中所有安装过的服务程序。如果需要查找尚未加载的服务单元,可以结合具体服务名称使用 systemctl status 等命令。
按时间过滤:–since 和 –until
排障时,时间范围非常重要。
假设用户反馈,SSH 登录从 2026 年 6 月 17 日上午 8 点开始出现异常。你可以查看指定时间段的日志:
1 | journalctl -u sshd.service \ |
其中:
--since:指定日志查询的起始时间。--until:指定日志查询的结束时间。
这条命令查询的是指定时间范围内的 sshd 日志,而不是整个系统的所有日志。
如果你只关心最近一小时,可以使用相对时间:
1 | journalctl -u httpd.service --since "1 hour ago" |
还可以查询今天以来的日志:
1 | journalctl -u httpd.service --since today |
时间过滤的关键是根据故障发生时间缩小范围,而不是漫无目的地翻阅大量历史记录。
实际排障时,还需要留意服务器时区和系统时间是否准确。时间不准确会影响事件之间的关联判断,这个问题会在本系列的时间同步专题中详细介绍。
按严重级别过滤:-p
日志中有启动信息、状态信息、警告和错误。排查故障时,你可能希望优先查看错误及更严重的消息。
Linux syslog 使用的日志优先级共有八级,数字越小,严重程度越高。
| 数值 | 名称 | 含义 |
|---|---|---|
| 0 | emerg | 系统不可用级别的紧急情况 |
| 1 | alert | 需要立即采取行动 |
| 2 | crit | 严重情况 |
| 3 | err | 错误 |
| 4 | warning | 警告 |
| 5 | notice | 正常但值得注意的事件 |
| 6 | info | 一般信息 |
| 7 | debug | 调试信息 |
例如,只想查看 Apache 的错误及更严重级别的日志:
1 | journalctl -u httpd.service -p err |
这里有个容易误解的地方:-p err 默认不是只显示优先级恰好为 err 的消息,而是显示 err 以及更严重的消息,也就是优先级 0 到 3。
如果你希望只看 warning 及更严重的消息,可以执行:
1 | journalctl -p warning |
如果想查看 warning 到 debug 之间的全部消息,则可以指定优先级范围:
1 | journalctl -p warning..debug |
还要注意,日志优先级反映的是日志记录者赋予这条消息的级别,不一定与业务故障的实际影响完全一致。没有错误级别的日志,不代表服务就一定没有问题。
按系统启动记录过滤:-b
Linux 服务器可能运行了几个月,也可能在故障发生前刚刚重启过。
如果你只想查看本次系统启动以来的日志,可以执行:
1 | journalctl -b |
如果要查看本次启动以来的 Apache 日志:
1 | journalctl -b -u httpd.service |
如果怀疑问题发生在上一次启动过程中,可以使用:
1 | journalctl -b -1 |
查看上一次启动期间的 NetworkManager 日志:
1 | journalctl -b -1 -u NetworkManager.service |
这里的 -b -1 表示上一次启动,-b -2 表示再往前一次启动。
不过,这些命令能否查到历史记录,取决于对应的 journal 日志是否还存在。
例如,如果日志只保存在 /run/log/journal/ 中,系统重启后这些日志通常就会丢失。若希望长期保留历史启动记录,就需要配置 journald 的持久化存储。
需要纠正一个常见误区:不能简单地认为 Linux 系统一定只把日志存放在内存中。journald 的 Storage=auto 等设置会影响存储行为;当 /var/log/journal/ 存在且配置允许时,日志可以持久化保存。
因此,执行 journalctl -b -1 查不到日志时,不要马上认为命令写错了,也要考虑历史日志是否被保存、清理或轮转掉了。
实时跟踪日志:-f
有时候,日志查询不是为了回顾过去,而是为了观察接下来发生的事情。
例如,你准备检查 Apache 配置修改后的运行情况,可以在一个终端执行:
1 | journalctl -u httpd.service -f |
-f 表示跟踪日志。命令会持续等待新的日志记录,并在出现新记录时显示出来。
按 Ctrl+C 可以退出跟踪。
实际工作中,可以在一个终端观察日志,在另一个终端执行配置检查或服务操作。这样,新的日志一出现,就能及时发现问题。
需要注意,journalctl -f 跟踪的是新的日志记录,不会自动判断日志是否代表故障。你仍然需要结合具体内容进行分析。
按关键字搜索:-g 或 –grep
假设你想查找包含 authentication failure 的日志,可以执行:
1 | journalctl -g "authentication failure" |
-g 是 --grep 的简写,用于匹配日志消息中的文本模式。它支持正则表达式,而不只是简单的字符串搜索。
例如:
1 | journalctl -g "Failed" --since today |
这条命令会查找今天以来,日志消息中匹配 Failed 的记录。
需要注意,--grep 的大小写匹配行为与模式有关:全小写模式默认不区分大小写;包含大写字母的模式默认区分大小写。你也可以显式指定:
1 | journalctl -g "failed" --case-sensitive=no |
关键字搜索可以跨多个服务寻找线索,但它也有局限:如果应用没有把相关信息写入 journal,或者日志中的措辞与你搜索的关键字不同,就可能找不到对应记录。
另外,-g 需要较新版本的 systemd 支持。如果你的系统提示不认识这个参数,可以先执行 journalctl --help 查看本机支持的选项,再考虑使用 grep 等其他方法处理输出。
只查看内核日志:-k
如果问题涉及磁盘、网卡、驱动程序或内核本身,可以先查看内核日志:
1 | journalctl -k |
只看本次启动以来的内核日志:
1 | journalctl -k -b |
只查找今天以来,消息中包含 XFS 的内核日志:
1 | journalctl -k -g "XFS" --since today |
这类日志可能包含文件系统错误、设备异常或驱动相关信息。
例如,如果你怀疑磁盘或文件系统出现问题,可以结合内核日志中的设备名称、错误信息和发生时间,进一步检查对应设备。
journalctl -k 与 dmesg 有相似之处,但两者并不完全等同:它们的可见记录范围和访问方式可能有所不同。
优先查看最新日志:-r 和 -n
默认情况下,journalctl 通常按照从旧到新的顺序显示日志。
如果你想优先查看最新的记录,可以使用 -r:
1 | journalctl -u httpd.service -r |
如果只想查看最新的 20 条记录,可以使用 -n 20:
1 | journalctl -u httpd.service -n 20 |
如果想把最新的 20 条记录按照从新到旧的顺序显示,可以组合使用:
1 | journalctl -u httpd.service -n 20 -r |
这在服务刚刚出现异常时尤其方便,因为最新日志往往包含最直接的故障线索。
如果你不希望打开分页查看器,还可以加上 --no-pager:
1 | journalctl -u httpd.service -n 20 --no-pager |
这样更适合快速检查输出,或者将查询结果交给其他命令继续处理。
把多个过滤条件组合起来
前面介绍的参数并不需要单独使用。实际排障时,往往需要同时指定服务、时间和日志级别。
例如,查看今天以来 sshd 的错误及更严重级别日志,只显示最新的 10 条,并按从新到旧的顺序排列:
1 | journalctl -u sshd.service \ |
这条命令可以拆解成几个条件:
-u sshd.service:只看 sshd 服务的日志。-p err:只看错误及更严重级别的日志。--since today:只看今天以来的日志。-n 10:限制为最新的 10 条记录。-r:按从新到旧的顺序显示。--no-pager:不使用分页查看器。
记住,排障的关键不是记住几十个参数,而是知道自己要回答什么问题,再选择合适的过滤条件。
实战:Apache 服务启动失败,应该怎么查?
现在把前面学到的知识串起来。
假设你接手一台 Linux 服务器,发现 Apache 服务无法启动。
不要一上来就连续执行重启命令。按照排障五步法,先确认状态,再收集证据。
检查服务状态
执行:
1 | systemctl status httpd.service --no-pager |
如果服务启动失败,输出中可能会显示:
1 | httpd.service - The Apache HTTP Server |
这说明 systemd 记录的服务状态为 failed。
但仅凭 Active: failed,还不能知道具体原因。我们还需要查看日志。
查询 Apache 的错误日志
执行:
1 | journalctl -u httpd.service -p err -n 50 --no-pager |
假设日志中出现:
1 | Jun 17 10:14:07 servera httpd[12864]: AH00526: Syntax error on line 47 of /etc/httpd/conf/httpd.conf: |
这两条日志告诉我们:
- Apache 报告配置文件存在语法问题。
- 出问题的位置在
/etc/httpd/conf/httpd.conf第 47 行。 - 日志中出现了
Listenn,它可能是一个拼写错误。
这里的输出是示例。真实环境中,错误信息和行号会根据实际配置而变化。
提出假设并验证
根据日志,我们可以提出一个假设:Apache 配置文件中存在拼写错误。
先检查配置:
1 | httpd -t |
如果命令再次报告 Listenn 不是有效指令,就有了进一步的证据。
打开配置文件:
1 | vi /etc/httpd/conf/httpd.conf |
找到第 47 行附近,确认是否存在拼写错误。
如果发现原本应该写成 Listen 的指令被写成了 Listenn,就可以确认这个配置错误是服务启动失败的原因之一。
修复前,最好先备份配置文件:
1 | cp -a /etc/httpd/conf/httpd.conf \ |
然后修正拼写,并再次检查:
1 | httpd -t |
只有在确认配置检查通过后,才继续执行服务操作。
修复后验证服务
如果确认配置正确,可以重启服务:
1 | systemctl restart httpd.service |
随后检查状态:
1 | systemctl status httpd.service --no-pager |
再查看服务最近的日志:
1 | journalctl -u httpd.service -n 30 --no-pager |
最后,还要从业务侧验证网站是否可以正常访问。
如果服务仍然无法启动,就继续收集新证据,而不是反复执行重启命令。
这才是完整的排障过程:确认现象、收集证据、提出假设、验证原因、修复并验证结果。
初学者最容易犯的几个错误
误以为 journalctl 能查到所有应用日志
journalctl 只能查询 journald 收集并保留下来的日志。
有些应用会把日志直接写入自己的文件。例如,Apache 常见的日志文件包括:
/var/log/httpd/access_log/var/log/httpd/error_log
如果相关日志没有被送入 journal,就需要直接查看对应的日志文件。
例如:
1 | tail -n 50 /var/log/httpd/error_log |
如果你发现 journalctl 中没有预期的内容,不要立即认定日志不存在,还要检查应用自身的日志配置。
误以为重启后一定能查到以前的日志
能否查询上一次启动的日志,取决于相关日志是否被持久化保存,以及它们是否仍然存在。
如果历史日志只保存在运行时目录中,系统重启后通常无法继续查看这些记录。
因此,生产环境中需要根据日志保留要求配置 journald 的存储方式和清理策略。具体做法会在下一篇文章中详细介绍。
只看报错,不看时间和上下文
一条错误日志可能只是故障的结果,而不是真正的根因。
例如,服务日志显示连接数据库失败,你还需要查看:
- 错误发生在什么时候?
- 数据库服务当时是否正常?
- 同一时间是否存在网络错误?
- 是否发生过配置变更或资源耗尽?
必要时,应该把多个服务的日志按照时间关联起来分析。
只看到服务启动成功,就认为故障彻底解决
systemctl status 显示 active (running),只能说明 systemd 看到服务处于运行状态,并不能保证业务功能完全正常。
服务可能已经启动,但端口没有按预期监听,后端依赖仍然不可用,或者请求依旧返回错误。
因此,服务状态检查、日志检查和业务验证都很重要。
动手练习:在自己的 Linux 虚拟机上试试
下面这些练习可以帮助你熟悉 journalctl。建议先在测试虚拟机中操作。
练习查询最近一小时的 SSH 日志
执行:
1 | journalctl -u sshd.service \ |
观察输出,看看是否存在服务启动、连接或认证相关的日志。
如果没有记录,不代表 SSH 服务一定没有运行,也可能是这段时间内没有产生相关日志。
另外,部分发行版或安装配置可能使用不同的 SSH 服务名称,请以本机的服务单元为准。
练习查询本次启动以来的警告及更严重日志
执行:
1 | journalctl -p warning -b --no-pager |
观察输出中的日志级别、时间和来源。
如果输出很多,可以限制数量:
1 | journalctl -p warning -b -n 30 --no-pager |
练习查找系统中的失败记录
执行:
1 | journalctl -g "Failed" --since today --no-pager |
观察哪些服务或操作出现了失败信息。
注意,搜索 Failed 只能找到消息中匹配该文本的记录,无法保证找出系统中的所有故障。
如果你的 systemd 版本不支持 -g,可以先查看 journalctl --help,再选择本机支持的替代查询方式。
练习安全地检查 SSH 配置错误
SSH 是远程管理服务器的重要入口。为了避免修改配置后无法登录,初学者不要直接在远程生产服务器上故意制造错误并重启服务。
可以先备份配置:
1 | cp -a /etc/ssh/sshd_config \ |
检查现有配置是否正确:
1 | sshd -t |
如果没有输出,通常表示本次配置语法检查通过。
在隔离的测试虚拟机中,可以额外制作一份测试配置文件,加入一个无效指令,然后使用 sshd -t -f 指定该文件进行检查。例如:
1 | cp -a /etc/ssh/sshd_config /tmp/sshd_config.test |
预期会看到包含无效指令名称的错误信息。
这里需要注意两点:
- 这个练习只是检查一份测试配置,不需要重启真实的 SSH 服务。
sshd -t的错误通常直接输出到终端,不保证会出现在journalctl中。因此,不能把配置检查的终端输出和服务运行日志混为一谈。
如果你想进一步观察真实的服务启动故障,应在可恢复的测试环境中进行,并提前准备控制台访问或其他恢复方式。不要为了练习而冒险切断生产服务器的远程管理通道。
写在最后
这一篇,我们从排障五步法开始,认识了 journald 和 rsyslog,并学习了 journalctl 最常用的过滤方式。
遇到服务故障时,可以先记住下面这组基本命令:
1 | # 查看服务状态 |
这些命令不需要一次全部背下来。真正重要的是,遇到问题时知道该从哪里入手,以及如何根据证据逐步缩小排查范围。
下一篇,我们继续深入 journald:学习如何管理日志磁盘占用、清理历史记录、配置持久化存储,以及认识 /var/log 下常见的日志文件。
先把证据收集好,再决定怎么修复。 这是从“靠重启碰运气”走向规范化运维排障的第一步。
