PHP 開發者應該理解的 Linux 入門權限指南
PHP 開發者應該理解的 Linux 入門權限指南
如果你曾經將 PHP 應用部署到 Linux 服務器并遇到 Permission denied 錯誤(通常出現在最糟糕的時候),你并不孤單。在理解 Linux 權限之前,它們確實會讓人感到困惑。本文將幫助你理解 PHP 代碼實際運行時涉及的用戶、文件組以及那些 rwx 字母的真正含義。
原文鏈接-PHP 開發者應該理解的 Linux 入門權限指南
10秒速覽
Linux 中的每個文件或目錄都有:
- 一個所有者(用戶)
- 一個所屬組
- 三組權限:所有者權限、組權限和其他用戶權限
權限分為三種:
r= 讀取w= 寫入x= 執行(對文件)或"可進入"(對目錄)
當 PHP 在服務器上運行時,你的代碼通常以 web 服務器用戶的身份執行(例如 Debian/Ubuntu 上的 www-data,CentOS/Red Hat 上的 apache,或者某些設置中的 nginx)——或者作為你配置的 PHP-FPM 池用戶運行。大多數"在本地可以但在生產環境不行"的權限問題都可以歸結為"到底是哪個用戶在運行這段代碼?"
專業解讀權限
運行 ls -l 查看詳情:
$ ls -l
-rw-r--r-- 1 deploy www-data 4238 Sep 17 10:12 index.php
drwxr-xr-x 2 deploy www-data 4096 Sep 17 10:12 public
drwxrwxr-x 4 deploy www-data 4096 Sep 17 10:12 storage
-rw-r--r-- 的解析:
- 第一個字符:文件類型(
-表示普通文件,d表示目錄) - 接下來 3 個:所有者權限(
rw-) - 再 3 個:組權限(
r--) - 最后 3 個:其他用戶權限(
r--)
所以 index.php 對所有人可讀,但只有所有者可寫。
你還會看到所有者(deploy)和組(www-data)。如果 PHP-FPM 以 www-data 運行,通常最簡單的修復方法是給組設置正確的權限。
八進制 vs 符號模式(以及何時使用)
你可以使用以下兩種方式設置權限:
- 符號表示法:
chmod g+w storage(給組添加寫權限) - 八進制表示法:
chmod 775 storage(所有者 rwx=7,組 rwx=7,其他用戶 r-x=5)
常用的八進制組合:
644用于文件:所有者可讀寫,其他用戶只讀755用于目錄:所有者完全權限,其他用戶可讀可執行664/775當組也需要寫權限時(共享部署)
避免使用 777——這是安全風險,幾乎從不需要。
文件 vs 目錄:微妙但重要的區別
文件
r:可以讀取內容w:可以修改內容x:可以執行(作為程序或腳本運行)
目錄
r:可以列出目錄中的文件名w:可以在目錄中創建/刪除/重命名條目x:可以進入(遍歷)該目錄
這就是為什么可寫上傳目錄同時需要 w 和 x 權限:
# 對于 PHP 需要寫入的上傳目錄:
chmod 775 uploads
# 或者如果只需要所有者寫入:
chmod 755 uploads # (但要確保所有權與 FPM 用戶匹配)
所有權:chown 和 chgrp
如果 web 服務器用戶需要寫入某個目錄(例如 storage、cache、uploads),需要調整所有權或組:
# 將 web 服務器組設為所有者組
sudo chgrp -R www-data storage
# 允許組寫入(和進入)目錄
sudo chmod -R g+rwX storage
大寫的 X 只對目錄(和已有的可執行文件)設置執行權限,這比通配符 +x 更安全。
如果你希望 web 服務器用戶擁有文件:
sudo chown -R www-data:www-data storage
這在單用戶服務器上很常見。在多用戶部署中,建議保持代碼庫歸人類用戶所有(例如 deploy),同時使用共享組(例如 www-data)和組寫權限。
umask:誰設置默認權限?
創建新文件時,它們會從一個"基礎"值開始(通常是文件 666,目錄 777),然后減去 umask 指定的權限。
umask為022會得到644文件和755目錄umask為002會得到664文件和775目錄(組可寫)
你可以在服務配置中(例如 PHP-FPM 的 systemd 單元文件)或 PHP 內部(進程范圍內)設置 umask。
在 PHP 中(進程生命周期內):
<?php
// 減少共享組部署的摩擦
umask(0002); // 新文件 664,目錄 775
使用時要小心——這會影響調用后創建的文件。
PHP 的視角:我是誰?
你的腳本以 FPM 池用戶的身份運行。快速診斷:
<?php
echo '運行用戶: ' . get_current_user() . PHP_EOL;
echo '有效 UID: ' . posix_geteuid() . PHP_EOL; // 如果啟用了 posix 擴展
以及經典的權限檢查:
<?php
$path = __DIR__ . '/storage';
if (!is_dir($path)) mkdir($path, 0775, true);
if (!is_writable($path)) {
error_log("$path 對 PHP 不可寫");
}
記住:mkdir 中的 0775 會被當前的 umask 掩碼。如果你傳入 0777 但 umask 是 0022,最終會得到 0755。
實際場景(及合理默認值)
框架緩存和日志(Laravel、Symfony 等)
這些目錄必須對 PHP 用戶可寫:
storage/bootstrap/cache/var/cache/var/log/
設置組為 web 用戶并使組可寫:
sudo chgrp -R www-data storage bootstrap/cache
sudo chmod -R g+rwX storage bootstrap/cache
如果有多個部署者/CI 代理寫入這些目錄,還要確保 umask 0002 以允許組寫。
WordPress 和 CMS 上傳
上傳文件通常位于 wp-content/uploads(或類似目錄)。保持代碼只讀;只讓上傳目錄可寫。
sudo chgrp -R www-data wp-content/uploads
sudo chmod -R 775 wp-content/uploads
核心和插件文件應該是 644(文件)和 755(目錄)。如果由于權限過緊導致 WordPress 無法自動更新,考慮使用 SSH/CLI 部署,而不是放寬所有權限。
生產環境中的 Composer
最好在構建步驟中運行 Composer,而不是在生產環境。如果必須在生產環境運行:
# 以非 root 用戶運行:
composer install --no-dev --optimize-autoloader
確保工作目錄對該用戶可寫;不需要讓 web 服務器用戶擁有整個代碼庫。
使用 CI 的共享部署
在開發者和 web 服務器之間使用共享組(例如 deploy):
sudo usermod -aG deploy www-data
sudo chgrp -R deploy /var/www/myapp
sudo chmod -R g+rwX /var/www/myapp
# 使新文件繼承目錄的組:
sudo find /var/www/myapp -type d -exec chmod g+s {} \;
最后一行在目錄上設置了 setgid 位(稍后會詳細介紹)。
特殊位:setuid、setgid、sticky
你偶爾會在 ls -l 中看到額外的字母:
- setuid(所有者的
s):以文件所有者的權限執行文件(主要用于系統二進制文件)。PHP 應用中很少使用。 - setgid(組的
s):在目錄上:新文件繼承目錄的組——非常適合共享部署組。 - sticky 位(其他用戶的
t):在像/tmp這樣的目錄上:用戶只能刪除自己的文件,即使目錄對組/其他用戶可寫。
命令:
# 在目錄上設置 setgid(繼承組)
chmod g+s storage
# 設置 sticky 位(常見于共享臨時目錄)
chmod +t /var/www/myapp/tmp
當基本權限不夠時:ACL
傳統的 rwx 權限不夠精細。ACL(訪問控制列表)允許你為特定用戶或組授予訪問權限,而無需更改所有者:
# 允許 www-data 遞歸讀寫
sudo setfacl -R -m u:www-data:rwX storage
# 為 storage 中的新項目設置默認值
sudo setfacl -R -d -m u:www-data:rwX storage
ACL 非常適合需要一致寫訪問權限的 CI 管道或多個應用進程。
SELinux/AppArmor(簡要說明)
如果你已經反復檢查了權限,但仍然遇到 Permission denied,可能是 SELinux 或 AppArmor 等強制訪問控制阻止了文件訪問。在啟用了 SELinux 的系統上,目錄的上下文很重要(例如,可寫 web 目錄的 httpd_sys_rw_content_t)。不要禁用 SELinux——而是設置正確的上下文:
# 示例(支持 SELinux 的發行版)
sudo chcon -R -t httpd_sys_rw_content_t /var/www/myapp/storage
需要避免的安全陷阱
- 不要
chmod -R 777。這會授予所有人寫權限,包括不受信任的系統用戶或服務。 - 不要以 root 身份運行 web 服務器。應該使用受限用戶(
www-data、apache、nginx)。 - 在生產環境中保持代碼只讀。只有數據目錄(上傳/緩存/日志)應該是可寫的。
- 將構建與運行時分開。在 CI 中構建工件,部署工件,并保持運行時環境嚴格。
實用的、可重復的配置方案
以下是許多 PHP 應用的合理基準配置:
APP_DIR=/var/www/myapp
# 1) 所有權:人類用戶所有,web 組
sudo chown -R deploy:www-data $APP_DIR
# 2) 嚴格的代碼權限(文件 644,目錄 755)
find $APP_DIR -type f -exec chmod 0644 {} \;
find $APP_DIR -type d -exec chmod 0755 {} \;
# 3) 可寫的運行時目錄(組可寫 + setgid)
sudo chgrp -R www-data $APP_DIR/storage $APP_DIR/bootstrap/cache
sudo chmod -R 2775 $APP_DIR/storage $APP_DIR/bootstrap/cache # 2 = setgid
# 4) 確保新文件繼承組并組可寫
sudo find $APP_DIR/storage $APP_DIR/bootstrap/cache -type d -exec chmod g+s {} \;
# 5) (可選)如果存在多個寫入者,使用 ACL
sudo setfacl -R -m u:www-data:rwX $APP_DIR/storage $APP_DIR/bootstrap/cache
sudo setfacl -R -d -m u:www-data:rwX $APP_DIR/storage $APP_DIR/bootstrap/cache
這樣可以在不開放過多權限的情況下,保持代碼安全且運行時目錄可寫。
故障排除:解讀"Permission denied"三部曲
-
我是誰(在運行時)?
- 檢查 PHP-FPM 池用戶,并用簡單腳本驗證(
get_current_user())。
- 檢查 PHP-FPM 池用戶,并用簡單腳本驗證(
-
目標路徑的權限/所有者是什么?
- 使用
ls -l和namei -l /完整/路徑/到/文件(非常適合查看路徑上每個目錄的權限)。
- 使用
-
是否有更高級的控制(SELinux/AppArmor)?
- 在 SELinux 上,
sudo ausearch -m avc -ts recent或sudo sealert -a /var/log/audit/audit.log可以顯示拒絕記錄。
- 在 SELinux 上,
別忘了查看確切的 PHP 錯誤。例如:
Warning: fopen(/var/www/myapp/storage/app.log): failed to open stream: Permission denied
這通常意味著目錄缺少 PHP 用戶/組的 w/x 權限——即使文件本身的權限看起來沒問題。
快速參考:"我應該設置什么?"
- 應用代碼:文件
644,目錄755,所有者deploy,組www-data - 可寫數據(緩存/日志/上傳):目錄
775(通常2775設置 setgid),組www-data - 避免:任何地方的
777 - 團隊/CI 共享寫入:在目錄上設置 setgid +
umask 0002,或使用 ACL
結論
Linux 權限并不神秘——它們只是關于誰可以做什么的規則。一旦你將這些規則與 PHP 的運行方式(你的 FPM/web 用戶)對應起來,大多數問題都會迎刃而解。保持代碼只讀,只讓數據目錄可寫,使用組(和 setgid)進行協作,在需要更精細控制時使用 ACL。只要稍加注意,你就能避免 Permission denied 的困擾,而不必使用 777 這樣的危險命令。

浙公網安備 33010602011771號