
本教程旨在解决 php 应用(如 laravel)通过 `shell_exec` 调用 `tar` 命令时遇到的 "read-only file system" 错误。当 `tar` 命令在命令行下正常工作,但在 php 环境中失败时,其常见原因是 `systemd` 中 php-fpm 服务的 `protectsystem=full` 配置项。文章将详细解释该问题产生的原因,并提供修改 `systemd` 服务单元文件以解除限制的解决方案,确保 php 应用能够成功执行文件系统操作。
在开发基于 PHP 的 Web 应用时,我们有时会遇到需要通过 PHP 脚本执行系统级命令的场景,例如使用 tar 命令进行文件或目录的备份与恢复。然而,一个常见的困境是,当尝试通过 shell_exec 或 exec 等函数在 PHP 应用中执行 tar 命令(特别是涉及解压和覆盖文件,如使用 --unlink-first 或 --recursive-unlink 选项)时,会收到“Cannot unlink: Read-only file system”之类的错误。
令人费解的是,同样的 tar 命令,如果直接在命令行终端中执行,即使切换到 php-fpm 进程所使用的用户(通常是 http、www-data 或 nginx 用户),也能正常工作。这种行为上的差异表明问题并非出在 tar 命令本身或文件系统权限的直接配置上,而是 PHP 运行环境的某种特定限制。本文将深入探讨这一问题的根源,并提供一套专业的诊断与解决方案。
要理解为何 PHP 环境下 tar 命令会遭遇“只读文件系统”错误,我们需要了解 Linux 系统中的 systemd 服务管理器及其安全强化功能。
systemd 是现代 Linux 发行版(如 Arch Linux, Ubuntu, CentOS 7+ 等)中广泛使用的初始化系统和服务管理器。它负责启动、停止和管理系统上的各种服务,包括 php-fpm。systemd 提供了一系列强大的配置选项,用于增强服务的安全性、隔离性及其对系统资源的访问控制。
立即学习“PHP免费学习笔记(深入)”;
ProtectSystem 是 systemd 服务单元文件中的一个指令,旨在通过限制服务对系统关键目录的写入权限来提高安全性。它的主要目的是防止受损的服务或应用程序意外或恶意地修改系统核心文件。
当 php-fpm 服务单元中配置了 ProtectSystem=full 时,任何通过 PHP 脚本执行的、需要修改或删除系统文件(例如,tar -xf 在解压时,如果目标目录中存在同名文件,需要先删除旧文件)的操作,都会因为尝试写入被 systemd 标记为只读的文件系统区域而失败,从而抛出“Cannot unlink: Read-only file system”错误。
而从命令行直接执行 tar 命令之所以能够成功,是因为此时 tar 进程不是在 php-fpm 服务单元的 systemd 沙箱环境下运行,它没有受到 ProtectSystem=full 的限制,因此具备正常的写入权限。
明确了问题根源后,解决方案也变得清晰:我们需要修改 php-fpm 的 systemd 服务单元配置,以解除 ProtectSystem=full 的限制。
首先,你需要找到 php-fpm 对应的 systemd 服务单元文件。这个文件通常位于以下路径之一:
文件命名格式通常是 php-fpmX.service,其中 X 代表 PHP 的版本号,例如 php-fpm7.service、php-fpm8.1.service 等。
你可以使用以下命令来查找确切的服务名称和文件路径:
systemctl status php-fpm # 或 systemctl status php-fpmX.service
在输出中查找 Loaded: 一行,它会显示服务单元文件的完整路径。
找到服务单元文件后,你需要编辑它,将 ProtectSystem=full 这一行注释掉或删除。
使用 sed 命令快速修改(推荐):
你可以使用 sed 命令来自动化这个过程。假设你的 php-fpm 服务文件是 /usr/lib/systemd/system/php-fpm7.service:
sudo sed -i 's:ProtectSystem=full:#ProtectSystem=full:' /usr/lib/systemd/system/php-fpm7.service
这条 sed 命令的作用是:在指定文件中查找 ProtectSystem=full 这行,并在其前面添加一个 # 符号,从而将其注释掉。
手动编辑文件:
如果你更倾向于手动编辑,可以使用 vi、nano 或其他文本编辑器打开服务单元文件:
sudo vi /usr/lib/systemd/system/php-fpm7.service # 替换为你的实际路径
在文件中找到 ProtectSystem=full 这一行,然后在其前面添加 # 符号,使其变为 #ProtectSystem=full。
修改服务单元文件后,systemd 不会自动识别这些更改。你需要执行以下命令来重载 systemd 配置并重启 php-fpm 服务,使更改生效:
sudo systemctl daemon-reload sudo systemctl restart php-fpm7.service # 替换为你的实际php-fpm服务名
完成这些步骤后,你的 PHP 应用应该就能够成功执行 tar 命令,而不再遇到“Read-only file system”错误了。
虽然上述解决方案能够有效解决问题,但禁用 ProtectSystem 具有重要的安全含义,必须予以充分考虑。
禁用 ProtectSystem=full 意味着 php-fpm 进程现在可以写入 /usr、/boot、/etc 等系统关键目录。如果你的 PHP 应用存在安全漏洞(例如,文件上传漏洞、命令注入漏洞),攻击者可能会利用这些漏洞来修改或删除系统文件,从而导致系统不稳定、数据丢失甚至完全的系统控制权。
在生产环境中,始终应遵循权限最小化原则。这意味着任何服务或应用程序都应该只拥有完成其功能所需的最低权限。如果你的 PHP 应用确实需要执行涉及修改系统目录的操作,请务必仔细评估其必要性,并尽可能采取其他安全措施。
专用 Bash 脚本与 sudoers: 如果 tar 操作是特定且受控的,可以考虑编写一个独立的 Bash 脚本来封装 tar 命令。然后,通过 sudoers 文件,仅允许 php-fpm 运行的用户(如 http)以非常受限的方式执行这个特定的脚本,并且只允许执行这些脚本,而不是任意命令。这种方式可以精细控制权限,降低风险。
例如,在 /etc/sudoers.d/php_tar_script 中添加:
http ALL=(root) NOPASSWD: /path/to/your/secure_tar_script.sh
然后在 PHP 中调用 sudo /path/to/your/secure_tar_script.sh。
容器化环境: 在 Docker、Kubernetes 等容器化环境中运行 PHP 应用可以提供更好的隔离性。你可以在容器内部拥有更宽松的文件系统权限,而不会影响宿主机系统。
只读操作: 如果 tar 命令仅用于创建备份(tar -cf),通常不会遇到此问题,因为这不涉及写入系统目录。问题主要出现在解压(tar -xf)且涉及删除现有文件时。
环境差异: 请注意,php-fpm 服务名称和 systemd 单元文件路径可能因 Linux 发行版(如 Debian/Ubuntu 可能使用 phpX.Y-fpm.service)和 PHP 版本而异。在执行修改前,请务必确认正确的服务名称和文件路径。
systemd 的 ProtectSystem=full 指令是导致 PHP 应用通过 shell_exec 调用 tar 命令时出现“Read-only file system”错误的主要原因。通过修改 php-fpm 的 systemd 服务单元文件,注释掉或删除 ProtectSystem=full 这一行,并重载 systemd 配置、重启 php-fpm 服务,可以有效解决此问题。
然而,在实施此解决方案时,务必充分理解其带来的安全风险。在生产环境中,强烈建议权衡功能性与安全性,并探索更安全、权限更受限的替代方案,以保护你的系统免受潜在的攻击。
以上就是PHP-FPM 环境下 tar 命令只读文件系统错误的诊断与修复的详细内容,更多请关注php中文网其它相关文章!
PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号