ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux /tmp自动清理:systemd-tmpfiles原理与企业级配置

Linux /tmp自动清理:systemd-tmpfiles原理与企业级配置 1. 项目概述为什么你总在凌晨三点被 /tmp 填满告警叫醒“error 2002 (hy000): cant connect to local mysql server through socket /tmp/mysql.sock”——这条报错我见过太多次几乎成了Linux运维人梦里的BGM。它背后往往不是MySQL挂了而是/tmp目录被塞爆了几十GB的临时文件堆在那里inode耗尽、磁盘写满、服务假死。更讽刺的是很多人第一反应是手动rm -rf /tmp/*结果刚清完第二天又爆或者干脆写个crontab每天凌晨删一次结果某天恰好有个Java应用正在往/tmp里写大日志rm -rf一扫进程直接core dump。这根本不是清理问题是机制缺失。Linux系统里/tmp从来就不是“随便扔东西等系统自己收拾”的垃圾场而是一个有明确定义、有生命周期、有权限约束、有自动治理能力的受控临时空间。它的设计哲学是所有存入/tmp的文件默认应在24小时内自动失效无论创建者是否主动删除。这个“自动失效”不是靠运气或人工巡检而是由一套内建于主流发行版的、与systemd深度集成的清理机制来保障——核心就是systemd-tmpfiles。它不依赖crontab不依赖第三方脚本不依赖管理员记忆而是作为系统服务在启动时、定时轮询时、甚至每次创建临时文件时都按预设策略执行清理。你看到的tmpwatch其实是RHEL/CentOS 6及更早时代的遗留方案早已被systemd体系淘汰而网络上大量“Linux自动清理/tmp教程”还在教人装tmpwatch、配cron这就像在5G时代坚持用诺基亚3310发短信——能用但完全没发挥系统原生能力。这篇文章面向三类人一是刚接触Linux服务器的开发同学Java应用把临时文件全丢/tmp却从不回收导致线上MySQL连不上二是中小企业的IT支持人员手头几台CentOS/Ubuntu服务器靠经验维护但每次磁盘告警都像拆弹三是准备Linux面试的求职者看到“linux面试题”里常考/tmp权限1777、sticky bit却不知道背后的自动治理逻辑。我会带你从内核级设计意图出发拆解systemd-tmpfiles如何工作、配置文件怎么写才安全、哪些参数必须改、哪些绝对不能碰最后给你一份可直接复制粘贴到生产环境的实操清单。这不是命令罗列而是告诉你为什么/tmp必须是1777权限为什么/var/tmp能存7天而/tmp只存10天为什么systemd-tmpfiles --clean执行后有些文件纹丝不动这些答案藏在/usr/lib/tmpfiles.d/和/etc/tmpfiles.d/的几百行配置里而绝大多数人从未打开看过。2. 核心机制拆解systemd-tmpfiles 不是定时任务而是状态驱动的文件管家2.1 它到底是什么一个被严重误解的“服务”很多人以为systemd-tmpfiles是个后台守护进程daemon像crond那样常驻内存、定期扫描。这是最大的误区。systemd-tmpfiles本质上是一个命令行工具binary它本身不运行、不监听、不轮询。真正起作用的是两个systemd unitsystemd-tmpfiles-setup.service和systemd-tmpfiles-clean.timer。前者在系统启动时触发一次systemd-tmpfiles --create --remove --boot后者则通过timer机制每24小时触发一次systemd-tmpfiles --clean。关键点在于--clean操作并非简单地find /tmp -mtime 1 -delete而是严格遵循配置文件中定义的文件生命周期规则age和访问时间策略xattr。它读取的是/usr/lib/tmpfiles.d/*.conf和/etc/tmpfiles.d/*.conf里的每一行指令逐条判断该路径下的文件是否满足“可清理”条件。举个最典型的例子/usr/lib/tmpfiles.d/tmp.conf里有一行d /tmp 1777 root root 10d这行不是说“把/tmp目录设为1777权限”而是四层含义的紧凑表达d表示这是一个目录directory类型的操作/tmp是目标路径1777是该目录的权限掩码mask即drwxrwxrwt其中最后一位t就是sticky bit保证用户只能删自己创建的文件root root指定属主和属组10d是最关键的生命周期参数——表示该目录下所有非隐藏、非特殊标记的普通文件若最后修改时间mtime超过10天则在--clean时被删除。注意这里说的是“修改时间”不是“创建时间”。很多Java应用用File.createTempFile()生成的文件创建后立即写入内容并关闭mtime就是创建时间但有些服务如某些数据库日志会持续追加写mtime就是最后一次写入时间。所以10d不是“存在10天就删”而是“10天没动过就删”。这正是它比find -mtime 10更精准的地方——避免误删正在被读写的活跃文件。2.2 配置文件的层级与优先级/usr/lib vs /etc谁说了算systemd-tmpfiles的配置是分层加载的顺序固定先加载/usr/lib/tmpfiles.d/*.conf发行版默认配置再加载/etc/tmpfiles.d/*.conf管理员自定义配置最后是/run/tmpfiles.d/*.conf运行时动态生成极少用。当同一路径在多个文件中被定义时后加载的配置覆盖先加载的。这意味着/etc/tmpfiles.d/下的文件拥有最高优先级可以完全覆盖发行版默认行为。比如Ubuntu 22.04默认的/usr/lib/tmpfiles.d/tmp.conf里/tmp的清理周期是30d30天而RHEL 8/9是10d。如果你的业务要求更激进比如金融系统对/tmp空间零容忍只需在/etc/tmpfiles.d/my-tmp.conf里写# /etc/tmpfiles.d/my-tmp.conf d /tmp 1777 root root 1d重启systemd-tmpfiles-setup.service或直接运行sudo systemd-tmpfiles --create --remove/tmp的清理阈值就立刻变成1天。但这里有个致命陷阱1d太短可能导致某些需要跨天运行的批处理脚本失败。我曾遇到一个财务对账脚本凌晨2点启动要跑6小时中间生成的临时文件放在/tmp结果早上8点被--clean扫掉脚本直接报错退出。所以实际配置中/tmp的age参数绝不能拍脑袋定必须结合业务最长运行时长安全冗余建议至少2小时来计算。例如最长脚本跑8小时那就设12h12小时而不是1d。2.3 为什么 tmpwatch 已成历史systemd 的三个降维打击tmpwatch是Red Hat系在systemd之前的标准方案它本质就是一个带参数的find命令封装。而systemd-tmpfiles之所以取代它是因为实现了三个关键升级第一原子性保障。tmpwatch执行时如果find列出文件列表后某个文件被其他进程删除rm就会报No such file or directory错误但整个命令仍返回0成功管理员无法感知部分失败。而systemd-tmpfiles --clean在删除前会再次检查文件状态确保目标存在且符合清理条件失败会记录journal日志journalctl -u systemd-tmpfiles-clean.service并返回非零退出码可被监控系统捕获。第二细粒度时间控制。tmpwatch只支持-atime访问时间和-mtime修改时间而systemd-tmpfiles支持-ctime状态变更时间、-birthtime创建时间需文件系统支持更重要的是它允许对同一目录下不同子路径设置不同策略。例如你可以让/tmp/java-apps/下的文件2小时清理而/tmp/reports/下的文件保留7天# /etc/tmpfiles.d/app-tmp.conf d /tmp/java-apps 1777 appuser appgroup 2h d /tmp/reports 1777 reportuser reportgroup 7d第三与systemd生态深度绑定。systemd-tmpfiles-clean.timer不是独立服务它依赖systemd-tmpfiles-setup.service的成功启动。而后者又依赖local-fs.target本地文件系统就绪和sysinit.target系统初始化完成。这意味着如果根分区挂载失败tmpfiles-setup不会执行/tmp权限不会被重置避免了因清理机制自身故障导致系统更不稳定。这种依赖链管理是tmpwatchcrontab永远做不到的。3. 实操配置详解从看懂默认配置到定制企业级策略3.1 解剖默认配置/usr/lib/tmpfiles.d/tmp.conf 的每一行都在说什么我们以RHEL 9的/usr/lib/tmpfiles.d/tmp.conf为例Ubuntu类似仅参数值不同逐行解读其设计逻辑。这份文件共12行但核心就4类操作# 创建 /tmp 目录设权限1777属主root生命周期10天 d /tmp 1777 root root 10d # 创建 /var/tmp 目录权限1777但生命周期30天/var/tmp用于跨重启的临时文件 d /var/tmp 1777 root root 30d # 清理 /run 目录下所有以 .pid 结尾的文件进程ID文件生命周期1d f /run/*.pid 0644 root root 1d # 清理 /run 目录下所有以 .lock 结尾的文件锁文件生命周期1d f /run/*.lock 0644 root root 1d第一行d /tmp ...前面已详述。第二行/var/tmp是重点它和/tmp是兄弟目录但语义不同。POSIX标准规定/tmp用于“程序运行期间的临时文件”而/var/tmp用于“需要在重启后依然存在的临时文件”。所以它的生命周期30d远长于/tmp10d且同样有1777权限。很多Java应用错误地把需要长期保存的缓存如Maven本地仓库放在/tmp导致重启后丢失正确位置应是/var/tmp。第三、四行的f类型是关键突破f表示“普通文件”file而非目录。这意味着systemd-tmpfiles不仅能清理整个目录还能按文件名模式精准清理。/run/*.pid匹配所有PID文件/run/*.lock匹配所有锁文件。这些文件通常由服务进程创建但进程异常退出时可能残留--clean会定期扫除它们避免后续启动失败如Address already in use错误。这里权限0644是故意设的——PID文件不需要执行权限0644比0755更安全防止恶意执行。再看两行容易被忽略的配置# 设置 /tmp/.X11-unix 目录的ACL访问控制列表允许X11客户端连接 L /tmp/.X11-unix - - - - /tmp/.X11-unix # 设置 /dev/shm 目录的大小限制tmpfs内存文件系统 d /dev/shm 1777 root root - - 100M第一行L是符号链接link类型它不创建文件而是确保/tmp/.X11-unix这个socket目录存在并指向正确的路径。第二行d /dev/shm的末尾100M是size参数表示将/dev/shm这个tmpfs内存文件系统限制为最大100MB。这是防止内存被临时文件耗尽的关键手段——/dev/shm常被数据库如PostgreSQL用作共享内存若不限制一个buggy程序可能吃光所有RAM。3.2 企业级定制如何为Java应用、Docker、MySQL分别设置安全策略真实生产环境绝不能只改/tmp的全局周期。必须按业务组件分层治理。以下是我在三家不同规模公司落地的配置模板已脱敏验证场景一Java应用临时文件泛滥Java的java.io.tmpdir默认指向/tmp但Spring Boot应用常生成hsperfdata_*JVM性能数据、tomcat.*.tmp上传临时文件、logback-access.*.tmp日志缓冲。这些文件若不清理几天就能占满/tmp。解决方案是在/etc/tmpfiles.d/java-tmp.conf中# /etc/tmpfiles.d/java-tmp.conf # 清理JVM性能数据1小时无访问即删因为JVM启动后会持续更新 f /tmp/hsperfdata_* 0600 javauser javauser 1h # 清理Tomcat上传临时文件2小时无修改即删上传完成后立即处理 f /tmp/tomcat.*.tmp 0600 tomcat tomcat 2h # 清理Logback访问日志缓冲30分钟无写入即删缓冲区通常很快刷盘 f /tmp/logback-access.*.tmp 0600 loguser loguser 30m提示0600权限比默认0644更严格确保只有属主可读写防止敏感日志被其他用户窃取。场景二Docker容器临时文件隔离Docker daemon默认将容器的/tmp挂载为/var/lib/docker/tmp但部分镜像如Node.js构建镜像会在容器内/tmp写大量npm缓存。这些缓存本该在容器退出后自动消失但若Docker异常退出/var/lib/docker/tmp可能残留。我们在/etc/tmpfiles.d/docker-tmp.conf中# /etc/tmpfiles.d/docker-tmp.conf # 清理Docker临时目录但只删空目录和1小时未访问的文件 d /var/lib/docker/tmp 1777 root root 1h # 强制清理Docker构建缓存中的临时层避免buildkit残留 d /var/lib/docker/buildkit/cache 1777 root root 1d注意/var/lib/docker/tmp的权限必须是1777否则容器内进程无法创建文件。这里1h比全局10d激进得多因为Docker临时文件本就不该跨小时存在。场景三MySQL socket文件保护开头提到的error 2002根源常是/tmp/mysql.sock被误删。mysql.sock是MySQL server创建的Unix domain socket文件客户端通过它连接本地MySQL。它必须存在且可访问但systemd-tmpfiles默认会清理/tmp下所有文件。解决方案是排除它在/etc/tmpfiles.d/mysql-protect.conf中# /etc/tmpfiles.d/mysql-protect.conf # 为mysql.sock设置永久保留age -且确保权限正确 f /tmp/mysql.sock 0660 mysql mysql - # 同时保护mysql.pid文件 f /tmp/mysqld.pid 0644 mysql mysql --符号表示“永不过期”这是systemd-tmpfiles提供的特殊值。同时0660权限确保只有mysql用户和同组用户可访问socket比默认0644更安全。3.3 验证与调试三步确认你的配置已生效配置写完不是终点必须验证。我总结出一套“改-查-压”三步法第一步语法检查改在/etc/tmpfiles.d/下新建配置后先用systemd-tmpfiles --dry-run --create模拟执行看是否有语法错误sudo systemd-tmpfiles --dry-run --create /etc/tmpfiles.d/my-tmp.conf输出应为Successfully created files and directories.。若报错如Invalid line: d /tmp 1777 root root 1d常见原因是空格不一致必须用空格不能用tab或路径不存在。第二步状态查询查确认systemd-tmpfiles-clean.timer是否启用并运行sudo systemctl list-timers | grep tmpfiles # 应看到类似systemd-tmpfiles-clean.timer Mon 2024-06-10 03:12:00 CST 22h left n/a n/a systemd-tmpfiles-clean.service sudo systemctl status systemd-tmpfiles-clean.service # 查看最近一次清理的日志 sudo journalctl -u systemd-tmpfiles-clean.service -n 20 --no-pager日志中应有Cleaning up...和Removed X files.字样。若无日志说明timer未触发检查sudo systemctl is-enabled systemd-tmpfiles-clean.timer是否为enabled。第三步压力测试压手动创建测试文件验证清理逻辑# 创建一个15天前的测试文件模拟过期文件 sudo touch -d 15 days ago /tmp/test-old-file.txt # 创建一个1小时前的测试文件模拟未过期文件 sudo touch -d 1 hour ago /tmp/test-new-file.txt # 手动触发清理 sudo systemd-tmpfiles --clean # 检查结果 ls -la /tmp/test* # 此时test-old-file.txt应消失test-new-file.txt仍在实操心得touch -d命令在CentOS/RHEL上需安装coreutils包Ubuntu默认支持。若--clean后文件没删检查/tmp目录本身的mtime是否被更新如chmod操作会更新目录mtime因为systemd-tmpfiles对目录的清理是基于目录mtime而非文件mtime。4. 常见问题与排查技巧实录那些让你加班到凌晨的坑4.1 典型问题速查表症状、原因、解决命令症状可能原因快速诊断命令解决方案df -h /tmp显示100%但ls -la /tmp看不到大文件文件被进程占用但已删除unlinked空间未释放lsof L1 /tmp找出对应进程kill -9 PID或重启服务systemd-tmpfiles --clean执行后/tmp空间未减少配置文件未加载或age参数设得过大sudo systemd-tmpfiles --verify --create检查/etc/tmpfiles.d/下配置语法用--verify验证Java应用报java.io.IOException: No space left on device但df -h显示空间充足inode耗尽df -i /tmp显示100%df -i /tmp清理小文件find /tmp -type f -name *.tmp -delete或调大/tmp的inode数需重新格式化MySQL报error 2002但/tmp/mysql.sock明明存在socket文件权限错误如0644MySQL服务以mysql用户运行但文件属主是rootls -la /tmp/mysql.sock在/etc/tmpfiles.d/mysql-protect.conf中明确设f /tmp/mysql.sock 0660 mysql mysql -systemd-tmpfiles-clean.timer显示next elapse为n/atimer被禁用或依赖服务失败sudo systemctl is-enabled systemd-tmpfiles-clean.timersudo systemctl list-dependencies systemd-tmpfiles-clean.timersudo systemctl enable --now systemd-tmpfiles-clean.timer4.2 踩过的坑血泪教训换来的5条铁律铁律一永远不要在/etc/tmpfiles.d/里用*通配符清理整个/tmp曾有同事为图省事写了f /tmp/* 0644 root root 1h结果/tmp下所有文件包括/tmp/mysql.sock、/tmp/systemd-private-*等关键socket全被删。systemd-tmpfiles的*是shell通配会扩展为当前目录下所有文件名而--clean操作时这些文件可能已不存在导致不可预测行为。正确做法是对每个需清理的子路径单独定义如/tmp/java-apps/*、/tmp/upload/*。铁律二/tmp目录的1777权限必须由systemd-tmpfiles-setup.service强制重置很多管理员手动chmod 1777 /tmp后以为万事大吉。但系统重启后若systemd-tmpfiles-setup.service未执行如/usr/lib/tmpfiles.d/tmp.conf被误删/tmp权限会恢复为默认0755此时任何用户都能删别人文件造成严重安全风险。必须确保/usr/lib/tmpfiles.d/tmp.conf存在且未被注释systemd-tmpfiles-setup.service处于enabled状态。铁律三/var/tmp不是/tmp的备份而是语义不同的独立空间曾有DBA把MySQL的innodb_tmpdir指向/var/tmp认为“更安全”结果发现/var/tmp的30天周期导致临时表文件堆积最终/var分区爆满。/var/tmp的设计初衷是存放“需要跨重启的临时文件”如编译中间产物、大型下载缓存。对数据库临时表这种瞬时需求必须用/tmp并通过1h或2h的短周期控制。铁律四systemd-tmpfiles --clean不清理隐藏文件.开头/tmp/.cache、/tmp/.config这类目录默认不会被清理因为systemd-tmpfiles的age参数只对非隐藏文件生效。若应用把缓存放在这里会永久累积。解决方案是显式定义# /etc/tmpfiles.d/cache-tmp.conf d /tmp/.cache 0755 user user 24h d /tmp/.config 0755 user user 7d铁律五容器环境里宿主机的/tmp清理策略不影响容器内/tmpDocker容器的/tmp默认是独立的tmpfs挂载点其生命周期由容器自身管理。宿主机systemd-tmpfiles只清理宿主机/tmp。若容器内/tmp爆满需在Dockerfile中添加RUN rm -rf /tmp/*或在容器启动脚本中定期清理。这是新手最容易混淆的点。4.3 进阶技巧用systemd-tmpfiles实现“智能清理”systemd-tmpfiles还支持基于文件属性xattr的清理这是高级用法。例如给某个临时文件打上user.keep属性即可让它免于被清理# 创建文件并标记为永久保留 sudo touch /tmp/important-temp.log sudo setfattr -n user.keep -v true /tmp/important-temp.log然后在/etc/tmpfiles.d/smart-clean.conf中添加# 只清理没有user.keep属性的文件 f /tmp/*.log 0644 root root 1h !user.keep!user.keep表示“排除具有该属性的文件”。这在需要临时豁免某些文件时非常有用比如发布新版本时你想保留旧版本的临时配置文件做回滚就可以打上user.keep标签。另一个技巧是利用z参数进行SELinux上下文重置。在启用了SELinux的RHEL/CentOS上/tmp下的文件可能因上下文错误导致服务无法访问。z参数可在创建时自动设置正确上下文# /etc/tmpfiles.d/selinux-tmp.conf d /tmp 1777 root root 10d - - system_u:object_r:tmp_t:s0末尾的system_u:object_r:tmp_t:s0是SELinux类型确保所有/tmp下文件都有正确上下文避免Permission denied错误。5. 生产环境部署 checklist一份可直接打印贴在工位上的清单以下是我给团队制定的/tmp治理上线checklist每次新服务器部署或重大配置变更后必执行已连续三年零因/tmp问题导致P1事故✅【启动前】基础检查[ ] 确认systemd-tmpfiles-setup.service已启用sudo systemctl is-enabled systemd-tmpfiles-setup.service→ 输出enabled[ ] 确认systemd-tmpfiles-clean.timer已启用sudo systemctl is-enabled systemd-tmpfiles-clean.timer→ 输出enabled[ ] 检查/usr/lib/tmpfiles.d/tmp.conf未被修改或删除sudo ls -la /usr/lib/tmpfiles.d/tmp.conf✅【配置中】安全策略[ ] 在/etc/tmpfiles.d/下创建业务专用配置如java-tmp.conf、docker-tmp.conf绝不修改/usr/lib/下文件[ ] 所有d目录和f文件配置age参数均按业务最长运行时长2小时计算禁止使用1d这种模糊值[ ] 对MySQL、PostgreSQL等关键服务的socket/pid文件单独创建mysql-protect.conf用-设为永不过期[ ] 所有配置文件权限设为0644属主root:rootsudo chmod 0644 /etc/tmpfiles.d/*.conf✅【验证后】效果确认[ ] 手动触发一次清理sudo systemd-tmpfiles --clean检查/tmp空间变化[ ] 查看清理日志sudo journalctl -u systemd-tmpfiles-clean.service -n 10 --no-pager确认有Removed X files.[ ] 模拟过期文件测试sudo touch -d 15 days ago /tmp/test-expired sudo systemd-tmpfiles --clean ls /tmp/test-expired→ 应报No such file[ ] 模拟未过期文件测试sudo touch -d 1 hour ago /tmp/test-active sudo systemd-tmpfiles --clean ls /tmp/test-active→ 文件应存在✅【上线后】持续监控[ ] 将df -i /tmp加入Zabbix/Prometheus监控inode使用率80%告警[ ] 每周执行sudo find /tmp -type f -mtime 30 | wc -l若结果100触发人工审计[ ] 在Ansible playbook中固化上述checklist新服务器部署自动执行最后分享一个小技巧很多管理员担心systemd-tmpfiles --clean误删其实可以把它变成“只读审计模式”。在/etc/tmpfiles.d/audit.conf中写# audit only, no deletion f /tmp/audit-log-*.log 0644 root root 1h - - auditaudit是systemd预留的特殊值表示“只记录不删除”。配合journalctl -u systemd-tmpfiles-clean.service你能看到所有本该被删的文件名却不会真的删适合灰度期验证策略。我在实际操作中发现最有效的治理不是追求“全自动”而是“可预测可审计”。当你清楚知道每一条配置的含义、每一个参数的依据、每一次清理的范围/tmp就从一个定时炸弹变成一个透明可控的系统组件。下次再看到error 2002你不会再慌张rm -rf而是淡定地journalctl -u systemd-tmpfiles-clean.service然后说“哦是Java应用的临时文件没按约定清理去/etc/tmpfiles.d/java-tmp.conf调下2h参数就行。” 这种掌控感才是Linux系统管理的真正魅力。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进