systemd 服务写入 /tmp 的文件在终端找不到
应用能读回自己写入 /tmp 的文件,服务器终端却找不到,可能是 systemd 的 PrivateTmp 隔离了临时目录。通过实际进程的目录视图定位文件,再把需要保留的结果迁移到专用状态目录。
应用把文件写到了 /tmp/export.txt,应用自己能读回来,在服务器终端执行 ls /tmp/export.txt 却提示文件不存在。反过来,终端放进 /tmp 的文件,服务也可能看不到。
如果应用由 systemd 启动,并启用了 PrivateTmp,两边看到的 /tmp 可能根本不是同一个目录。此时改文件权限并不能让它们互相可见。先确认服务实际看到的目录,再决定文件应该存在哪里。
下面针对 Linux 上由系统级 systemd 服务启动的进程。app.service、appuser、appgroup 和文件名都是示例,需要替换为实际值。容器内部、chroot 或应用自行创建的隔离环境也可能产生相似现象,不能只凭“找不到文件”就认定是 PrivateTmp。
确认隔离设置和实际写文件的进程
先查看服务的配置文件、覆盖配置及 systemd 当前加载的属性:
systemctl cat app.service
systemctl show app.service \
--property=PrivateTmp \
--property=MainPID \
--property=User \
--property=Group \
--property=DynamicUser
systemctl cat 显示磁盘上的 unit 和 drop-in 文件;systemctl show 显示服务管理器当前掌握的属性。刚修改配置但没有执行 daemon-reload 时,两者可能不一致。即使已经重新加载配置,旧进程也不会因此自动获得新的挂载视图,仍需结合进程实际状态判断。
PrivateTmp=yes 或 true 表示服务使用私有临时目录。支持 disconnected 的 systemd 版本还允许用独立的 tmpfs 提供临时目录;这种模式不能靠在主机 /tmp 下搜索文件来定位。以本机 man systemd.exec 支持的取值为准,不要给旧系统直接套用新选项。
还要找对服务。网站请求可能由 Apache 转交给 PHP-FPM,真正生成文件的是 PHP-FPM 工作进程。只检查前端 Web 服务的 PrivateTmp,不能说明后端进程看到的目录。MainPID 是服务主进程,不保证就是写文件的工作进程;工作进程若又创建了自己的隔离环境,应检查那个进程。
对于普通单进程服务,可先从主进程的视图列出临时目录:
service_pid=$(systemctl show app.service --property=MainPID --value)
if [ "$service_pid" -gt 0 ]; then
sudo ls -la -- "/proc/$service_pid/root/tmp/"
fi
Linux 的 /proc/PID/root 能呈现该进程看到的文件系统,包括它自己的挂载。这里是在读取目录,不是进入服务执行命令。若已确定写文件的是另一个工作进程,应将路径中的 PID 换成那个仍在运行的进程号。
MainPID=0、进程刚退出或重启,都可能让这个路径不可用;访问还受 /proc 与进程访问权限限制。遇到拒绝访问时,不能据此认定文件不存在,也不应为了查看目录而放宽整台机器的进程访问限制。
如果文件在服务的 /proc/PID/root/tmp/ 中,却不在终端的 /tmp/ 中,再结合已启用的隔离配置,才能解释“服务写入成功,终端却找不到”的现象。若两个位置都没有文件,应继续查应用实际输出路径、写入错误和是否已被清理。
私有临时目录不能用来长期保存结果
PrivateTmp 同时隔离 /tmp 和 /var/tmp。因此,把路径从 /tmp/export.txt 改成 /var/tmp/export.txt,通常并不能解决服务与外部进程共享文件的问题。
启用普通私有临时目录时,主机上可能看到 systemd-private-... 形式的目录。但这些目录名属于运行时安排,不适合硬编码进下载脚本、备份任务或其他服务。服务重启后路径可能改变;使用独立 tmpfs 的模式时,寻找这种主机目录的办法也不成立。
服务停止后,其私有临时目录中的临时文件会被清理。需要保留的导出结果,应在停止或重启服务前通过应用导出,或由有权限的管理员从已确认的进程视图复制到受控位置。不要先重启再寻找文件,也不要手动删除正在使用的 systemd-private-* 目录。
如果文件只供同一服务在一次处理过程中使用,继续放在私有 /tmp 是合理的。只有需要跨进程交换、供终端读取或保留到重启之后的结果,才需要另设目录。
把需要保留的文件移到专用状态目录
对于已有固定 User=appuser、Group=appgroup 的普通系统服务,可以使用 systemd 的 StateDirectory 管理专用目录。以下示例要求本机支持该设置,且服务未启用动态用户;/var/lib/myapp-exports 应是为这个应用新选的专用位置。
修改前保存原有服务覆盖配置和应用输出路径配置,并先取出仍要保留的临时文件。创建或编辑 /etc/systemd/system/app.service.d/exports.conf,父目录不存在时需由管理员创建,已有同名文件则先备份再合并:
[Service]
StateDirectory=myapp-exports
StateDirectoryMode=0750
这段配置不改变服务的运行用户。在上述固定用户前提下,systemd 启动服务时会创建 /var/lib/myapp-exports,并按 unit 中的 User 和 Group 设置所有者。0750 允许目录所有者读写访问、所属组读取和进入,其他用户没有访问权限;目录里的文件仍有各自的权限。
随后在应用自身的导出设置中,把输出文件改为 /var/lib/myapp-exports/export.txt。StateDirectory 只负责准备目录,不会自动把应用所有写向 /tmp 的路径重定向过去。配置项名称取决于应用,必须确认实际写入路径已经改变。
不要把 StateDirectory 指向一个已有的共享业务目录:如果目录所有者与 unit 中的用户或组不匹配,systemd 可能递归调整其中内容的所有权。对会自行降权的 Web 服务,也不能把 unit 的启动用户直接当成最终写文件的用户。此时应按应用的工作进程身份规划目录与权限,而不是照搬这个示例。
需要另一个服务读取结果时,还应为那个服务安排明确的读取权限。不要为了让文件可见而把目录设为所有人可写。ProtectSystem、InaccessiblePaths、容器挂载以及 SELinux 等额外限制,也需要分别核对。
重启后的检查和回退
确认备份和路径配置后,在允许服务中断的时间重新加载 unit 并重启目标服务:
sudo systemctl daemon-reload
sudo systemctl restart app.service
systemctl status app.service --no-pager
重启会中断正在处理的任务,也可能清掉原先的私有临时文件,需要保留的文件必须在重启前取出。daemon-reload 只重新加载服务定义,不会代替重启。
触发一次正常的、小规模导出,核对应用报告成功、专用目录中出现预期文件,以及实际读取方能读取文件。只看到服务状态为 active,不能证明导出路径和权限已经正确。也不要把测试时生成的结果误当成原来那份丢失的文件。
若改动后应用无法写入,保留新目录中的数据,恢复原应用路径配置,并恢复或移除本次新增的 exports.conf,不要删除其他 drop-in。再次重新加载并重启目标服务。恢复配置不会自动把已经导出的文件搬回旧位置,也无法恢复此前因服务停止而被清理的临时文件。
不建议把关闭 PrivateTmp 作为默认处理:它会改变服务与外部进程共享临时文件的边界。对于需要保存或交换的数据,明确目录、所有者和读取方,比依赖公共临时目录更容易长期维护。


