服务器重启后日志全没了?问题在 journald
很多用 Debian/Ubuntu 或 CentOS 的个人站长都遇到过这个场景:服务器半夜重启了一次,第二天想查昨天下午的报错,敲下 journalctl -b -1,结果被告知 No journal files were found,或者更让人绝望的一句 Specified boot ID is not found。原因很简单:systemd-journald 默认把日志写在内存里(/run/log/journal),重启即清空。重启前的那次故障,恰恰是你最需要日志的时候。
这篇讲的是把 journald 改造成「持久化 + 有容量上限 + 能被正常轮转」的可靠日志系统。它不是简单加一行配置,因为 journald 有自己独立的一套存储和清理机制,和 logrotate 完全不是一回事,混用会踩坑。
一、确认当前是易失还是持久模式
先看你的 journald 把日志放在哪:
ls -l /var/log/journal 2>/dev/null
ls -l /run/log/journal 2>/dev/null
journalctl --disk-usage判断规则很直接:
- 如果
/var/log/journal存在且里面有<machine-id>目录,就是持久模式,重启后日志保留。 - 如果只有
/run/log/journal存在,就是易失模式,重启丢失。 - 如果两个都不存在但
journalctl能输出日志,那也是易失(内存)模式。
journalctl --disk-usage 会告诉你当前日志占了多少磁盘(或内存)。这个数字很重要,因为它是后面设置容量上限的依据。
二、开启持久化:最保险的两步法
很多教程只让你改 /etc/systemd/journald.conf 里的 Storage=persistent,然后就让你重启。这在实际操作里有两个坑:一是改完配置没建目录,二是重启 journald 时可能有短暂日志丢失。推荐按下面两步走。
第一步:先建好目录并设置权限
mkdir -p /var/log/journal
systemd-tmpfiles --create --prefix /var/log/journal
systemctl restart systemd-journald为什么必须先建目录?因为 journald 启动时如果 /var/log/journal 不存在,即使配置里写了 Storage=persistent 也会回退到内存模式。用 systemd-tmpfiles 建目录能保证属主和权限完全正确(属主 root、属组 systemd-journal、权限 2755 带 setgid),手工 mkdir 很容易漏掉 setgid 位,导致普通用户读不到日志。
第二步:在配置文件里明确声明
编辑 /etc/systemd/journald.conf,注意整份文件里的有效配置项几乎全是注释状态,直接在对应的注释行下面加一行自己的配置即可:
[Journal]
Storage=persistent
Compress=yes
SystemMaxUse=500M
SystemKeepFree=1G
SystemMaxFileSize=50M
SystemMaxFiles=10
MaxRetentionSec=1month
ForwardToSyslog=no
改完执行:
systemctl restart systemd-journald
journalctl --disk-usage
ls /var/log/journal/如果此时 ls /var/log/journal/ 出现了一个以 machine-id 命名的目录,里面是 system.journal 文件,说明持久化成功。
三、每个配置项到底在控制什么
上面那几个参数不是凑数的,每一个都对应一个真实的运维问题。
- Storage=persistent:日志落盘到
/var/log/journal。可选值还有volatile(内存)、auto(有目录就持久、没有就内存)、none(关闭)。建议直接写 persistent,语义明确,不依赖目录是否存在。 - Compress=yes:日志默认用 XZ/LZ4 压缩存储。纯文本日志压缩比通常能到 5~10 倍,对个人服务器非常划算,几乎必开。
- SystemMaxUse=500M:整个 journal 目录占用的硬上限。超过这个值,journald 会按时间从最旧的日志开始删除,直到降回水位以下。这是防止日志把磁盘撑爆的核心参数。设多大合适?看你磁盘和保留需求,一般站点设 500M~2G。
- SystemKeepFree=1G:无论如何要给磁盘留出的空闲空间。它比 SystemMaxUse 优先级更高——如果磁盘剩余空间低于这个值,journald 会主动删除旧日志腾空间,甚至在你设定的 SystemMaxUse 之下也继续删。这个参数是磁盘爆满的最后一道保险,务必保留。
- SystemMaxFileSize=50M:单个 journal 文件的最大体积。文件越大,轮转越少但单文件磁盘占用越集中。50M 是个均衡值。
- SystemMaxFiles=10:最多保留多少个 journal 文件。这个是文件数量上限,和体积上限是两个独立维度,两者谁先触发都生效。
- MaxRetentionSec=1month:按时间保留,超过一个月的日志会被清理。体积和时间两个维度的限制是「或」的关系。
- ForwardToSyslog=no:不把日志再转发一份给 rsyslog。如果你同时跑着 rsyslog,转发会导致同一份日志存两遍、占双倍磁盘。个人服务器通常直接用 journald,这一项关掉更省空间。
四、journald 和 logrotate 不是一回事
这是最容易出错的地方。journald 的日志文件是二进制的 .journal 文件,logrotate 完全管不了它,也不该去管。网上有教程教你给 /var/log/journal 写 logrotate 规则,那是错的——logrotate 会把正在使用的 journal 文件移走或截断,导致 journald 写入异常,甚至出现「日志文件损坏」的报错。
正确的心智模型是:
- journald 管理的:
/var/log/journal/*/system.journal,轮转和清理由SystemMaxUse/MaxRetentionSec等参数控制,不要写进 logrotate。 - logrotate 管理的:Nginx 的 access.log / error.log、MySQL 的 slow.log、PHP-FPM 的日志等传统文本日志。
/etc/logrotate.d/nginx这些才是 logrotate 的地盘。 - rsyslog 管理的:
/var/log/syslog、/var/log/messages等文本日志,走 logrotate 的/etc/logrotate.d/rsyslog。
如果你只想要 journald 一份日志、并且已经关掉 ForwardToSyslog,那 /var/log/syslog 会逐渐不再更新,这是正常现象,不是坏了。反之如果你想保留文本日志文件方便 grep,就把 ForwardToSyslog 打开,并接受双份存储的成本。
五、手工清理与容量检查
即使设了上限,有时也会临时需要腾空间,或者你接手了一台从没管过日志的服务器,发现 /var/log/journal 已经占了几个 G。这时候用官方命令清理,不要用 rm 直接删文件(正在写入的文件被删,journald 的索引会错乱)。
# 看一眼当前占用
journalctl --disk-usage
# 按体积清理,只保留最近 200M
journalctl --vacuum-size=200M
# 按时间清理,只保留最近 7 天
journalctl --vacuum-time=7d
# 按文件数清理,只保留最近 5 个文件
journalctl --vacuum-files=5清理完成后再跑一次 journalctl --disk-usage 确认生效。注意 --vacuum-size 是按日志的「实际解压后体积」估算,不一定精确等于磁盘占用,但足够用于日常管理。
还有个好习惯是定期验证日志文件完整性:
journalctl --verify它会逐个校验 journal 文件的校验和,输出 PASS 或指出哪个文件损坏。如果服务器经历过强制断电,这个命令能帮你确认日志是否还可信。
六、日常查日志,这几个用法效率翻倍
持久化做好了,下一步是会用。journalctl 的参数组合威力很大,掌握下面这些就够日常排障。
# 只看本次开机以来的日志
journalctl -b
# 看上一次开机的日志(排查重启前故障的关键)
journalctl -b -1
# 列出所有可用的开机记录
journalctl --list-boots
# 看指定服务的日志(最常用)
journalctl -u nginx.service
journalctl -u mysql.service --since "1 hour ago"
# 实时跟踪,等价于 tail -f
journalctl -u php-fpm -f
# 只看错误级别以上
journalctl -p err -b
# 按时间范围查
journalctl --since "2026-09-19 14:00" --until "2026-09-19 16:00"
# 只输出正文,方便塞进 grep / awk
journalctl -u nginx -o cat
# 按进程、按 PID、按用户过滤
journalctl _PID=1234
journalctl _COMM=sshd
journalctl _UID=33几个实用细节:
journalctl --list-boots里的-1表示上一次开机,早期版本才有,新版直接用-1偏移量即可。-p err的级别从 0 到 7:emerg、alert、crit、err、warning、notice、info、debug。用-p err能筛掉大量噪音,快速定位真正的问题。-o cat只打消息正文,不带时间戳和主机名等元数据,做管道处理时最省事;-o json则输出结构化 JSON,方便程序解析。- 不加任何参数直接
journalctl会从头翻,日志量大时体验很差。养成默认加-b的习惯,只看本次开机。
七、日志落盘后,下一步是集中化
单机持久化解决了「重启丢日志」,但如果你的站点跑在多台机器上(比如源站 + 跳板机 + 数据库机),排查一次故障要在三台机器间来回 SSH,效率依然很低。此时可以考虑两种进阶方案:
- 轻量方案:用 rsyslog 或 vector 把 journald 的日志转发到一台集中日志机,或者直接转发到云厂商的日志服务。配置量不大,适合 3~5 台机器的小集群。
- 重量方案:搭建 ELK / Loki 栈做检索和可视化。功能强但资源消耗大,个人站长如果只有一两台机器,往往不值得,因为 Loki + Grafana 的最低内存需求也要 1G 以上。
在决定上集中化之前,先把单机的 journald 持久化和容量上限做好,这是投入产出比最高的一步。很多人的「日志缺失」问题,其实一台机器都没配置对。
八、常见坑与排查
- 改了配置但重启后还是丢日志。 检查
/var/log/journal是否存在、journalctl --disk-usage指向的路径是什么。绝大多数情况是目录没建。 - 日志占满磁盘。 通常是只设了 SystemMaxUse 却没设 SystemKeepFree,或者压根没设任何上限(默认上限是磁盘的 10%,在大磁盘上就是几十 G)。补上这两个参数,再用
journalctl --vacuum-size清一次。 - 普通用户看不到日志。 要把用户加入
systemd-journal组:usermod -aG systemd-journal 用户名。这个组是读取日志的钥匙。 - 手动 rm 了 journal 文件。 别这么做。如果已经删了,重启
systemd-journald让它重建索引,再用journalctl --verify检查是否恢复正常。 - Time 显示不直观。 journalctl 默认可能显示本地时间也可能显示 UTC,取决于系统时区。用
timedatectl确认时区正确,排障时时间轴对齐非常重要。 - 以为 journalctl 能查 Nginx 访问日志。 不能。Nginx 的 access_log 是它自己写的文本文件,不经过 journald。只有以 systemd 服务形式启动、且输出到 stdout/stderr 的日志才会进 journald。
小结
journald 的持久化是一个「配一次、长期受益」的基础设施改造。核心就三件事:建目录 + Storage=persistent 让日志落盘,SystemMaxUse + SystemKeepFree 给日志设硬上限,用 journalctl --vacuum 做维护而不碰 logrotate。做完之后,你会拥有一个重启不丢、容量可控、能按服务/时间/级别快速过滤的日志系统——这在服务器半夜重启、服务莫名崩溃、或者排查一次偶发 502 时,往往就是能不能找到根因的关键差别。对于个人站长而言,这可能是所有运维配置里投入最小、回报最直接的一项。
常见问题
Q:持久化后日志会存多久?
A:取决于你设的上限,取「体积上限、文件数上限、时间上限」三者中最先触发的那个。示例配置里 SystemMaxUse=500M、MaxRetentionSec=1month,实际站点通常能存两周到一个多月。
Q:改成持久化会不会影响性能?
A:影响极小。journald 本身是异步写入并压缩的,相比它带来的排障价值,这点开销完全可以忽略。真正需要注意的是磁盘 IO 已经打满的机器(用 iostat 确认)。
Q:Storage=auto 和 persistent 选哪个?
A:想稳定保留日志就写 persistent,语义明确;auto 的行为依赖目录是否存在,容易在换机器、重装系统后出现「行为不一致」,不利于排查。生产服务器建议 persistent。