限問題排查指南:告別403 Forbidden與Permission denied)
說實話只要跑過一段時間Nginx沒人能躲過這個報錯組合瀏覽器里一個大白板寫著403 Forbidden翻到error.log看到一行open() /xxx/index.html failed (13: Permission denied)。尤其是當(dāng)你確認文件存在、路徑?jīng)]錯、配置也是從默認模板改的結(jié)果頁面死活打不開的時候那種憋屈感我太懂了。Nginx權(quán)限問題在排障里出鏡率極高但它本身并不復(fù)雜。這篇文章我會把底層邏輯講明白再按場景給可復(fù)現(xiàn)的修復(fù)步驟最后附上我平時排查的固定流程和踩坑清單爭取讓你下次碰到類似問題能在五分鐘內(nèi)定位。1. 先搞清楚Nginx權(quán)限體系的底層邏輯1.1 工作進程是誰在跑很多人把Nginx的權(quán)限問題想得太玄其實核心就一句話運行Nginx進程的用戶必須對網(wǎng)頁文件有合適的訪問權(quán)限。Nginx啟動時有個master主進程負責(zé)加載配置、管理worker進程。主進程通常以root啟動因為要監(jiān)聽80/443這種特權(quán)端口但它干完活就會把worker進程降權(quán)。真正處理請求、讀文件、寫日志的是那些worker進程。worker進程跑在哪個用戶下看配置文件里的user指令。常見發(fā)行版默認值不一樣Debian/Ubuntu系常用www-dataCentOS/RHEL系常用nginx一些編譯安裝的版本可能直接用nobody。所以排查權(quán)限問題的第一步永遠是確認worker進程的實際身份。光看配置文件的user指令是不夠的因為某些發(fā)行版會在編譯時覆蓋默認值或者配置里有include片段二次賦值。最可靠的辦法是直接看進程ps -eo pid,user,comm | grep nginx輸出里以nginx: worker結(jié)尾的進程屬于哪個用戶處理請求時就用哪個身份這是鐵律。順便提一句雖然user指令可以寫成user root;來“一勞永逸”但這是嚴重的生產(chǎn)事故隱患后文會專門解釋為什么不能這么干。1.2 目錄和文件權(quán)限到底卡在哪一層Linux的權(quán)限模型很簡單文件或目錄有owner、group、others三組身份每組有r讀、w寫、x執(zhí)行三種權(quán)限。但對Nginx這類web服務(wù)來說判斷標(biāo)準(zhǔn)要從“當(dāng)前登錄用戶”換成“worker進程的用戶身份”。這里有個新手最容易栽跟頭的地方目錄的權(quán)限含義和文件不一樣。文件讀需要r寫需要w執(zhí)行/運行需要x。目錄r只代表“能列出目錄里有哪些名字”w代表“能在目錄里創(chuàng)建/刪除文件”x才代表“能穿過這個目錄”。對Nginx而言訪問/data/www/site/index.html需要/data、/data/www、/data/www/site每一級目錄都包含“其他用戶可執(zhí)行”others的x的權(quán)限。哪怕只有一級目錄缺了xNginx就會上報permission denied。但注意目錄缺r并不會直接阻止訪問已知文件名的文件這就是為什么有些“目錄沒讀權(quán)限”的站點直接輸入完整路徑能打開但瀏覽目錄列表會403。考慮到權(quán)限配置標(biāo)準(zhǔn)安全的做法是目錄給755rwxr-xr-x文件給644rw-r--r--owner設(shè)為部署用戶或站點管理員。這樣owner全權(quán)控制其他所有人包括Nginx worker能讀能執(zhí)行但不能寫。1.3 SELinux這個隱藏攔路虎在CentOS/RHEL及衍生發(fā)行版上大量Nginx權(quán)限問題還有一個隱形元兇SELinux。它的存在會讓“普通的Linux權(quán)限”看起來完全正常但訪問依然被拒。SELinux給每個進程和文件都打了安全標(biāo)簽。默認策略里Nginx進程的域是httpd_t而網(wǎng)頁文件通常需要標(biāo)記為httpd_sys_content_t。如果你的站點文件放在自定義路徑比如/data/www而不是發(fā)行版默認的/usr/share/nginx/htmlSELinux默認不帶httpd_sys_content_t標(biāo)簽Nginx就會在系統(tǒng)調(diào)用層被攔截。最典型的現(xiàn)象是ll看文件權(quán)限一切正常nginx用戶也有權(quán)限但日志照樣報Permission denied。這時候必須檢查SELinuxgetenforce輸出Enforcing就說明SELinux在生效。再用ls -Z看文件標(biāo)簽ls -Zd /data/www如果標(biāo)簽是default_t而不是httpd_sys_content_t就可以實錘了。修SELinux的正確姿勢不是直接setenforce 0關(guān)掉雖然很多人這么干而是按后文方案給文件打標(biāo)簽。后面我在解決方案里會展開講。2. 認識兩類最典型的權(quán)限報錯2.1 403 Forbidden的兩種截然不同的成因我見過太多人一看403就急著調(diào)目錄權(quán)限結(jié)果越調(diào)越亂。其實403 Forbidden在Nginx里有兩種完全不同的來源必須靠error.log區(qū)分。第一種是系統(tǒng)調(diào)用層錯誤。Nginx嘗試用worker進程身份去open()文件內(nèi)核返回EACCESerrno 13。這種錯誤在error.log里的特征是包含Permission denied字樣并且會帶上失敗的操作路徑。比如[error] 12345#0: *678 open() /usr/share/nginx/html/index.html failed (13: Permission denied)這種才是真正的“權(quán)限”問題需要按第3章的方案去修目錄、owner、SELinux標(biāo)簽。第二種是Nginx配置層的拒絕。文件本身讀得到但Nginx的配置邏輯決定不給你看。典型場景[error] 12345#0: *789 directory index of /data/www/site/ is forbidden這種情況通常是目錄里沒有index.html/index.php等索引文件而配置里又沒開autoindex on;。簡單說Nginx進得了目錄、讀得到文件列表但不知道默認該顯示哪個文件。這不太算權(quán)限問題更像配置問題。還有一種配置層拒絕是location塊里顯式寫了deny all;或做了IP封禁。區(qū)分這兩種403的方法很簡單去error.log看關(guān)鍵字。有“permission denied”的走系統(tǒng)權(quán)限排查只有“directory index is forbidden”的檢查索引文件和index指令。2.2 error.log里第13號錯誤代表什么系統(tǒng)調(diào)用錯誤后面跟的數(shù)字是從errno全局變量來的。13: Permission denied對應(yīng)EACCES意思是“權(quán)限不足操作不被允許”。并不是只有文件權(quán)限會導(dǎo)致EACCES像Unix socket連接、寫日志、創(chuàng)建臨時文件都可能觸發(fā)。常見的幾個errno在Nginx日志里出現(xiàn)的頻率也高我整理了一下errno含義Nginx日志里的典型表現(xiàn)13EACCES權(quán)限不足open() failed (13: Permission denied)2ENOENT文件/目錄不存在open() failed (2: No such file or directory)21EISDIR路徑是目錄open() /path failed (21: Is a directory)30EROFS文件系統(tǒng)只讀open() failed (30: Read-only file system)111ECONNREFUSED連接被拒connect() failed (111: Connection refused)排查時不要只盯著13不放有時候2號和13號是連鎖反應(yīng)比如Nginx想訪問某目錄下的默認索引文件但目錄本身對worker進程沒有x權(quán)限內(nèi)核可能返回的不是13而是2當(dāng)作不存在。所以“文件明明存在但報No such file or directory”時也要回頭看權(quán)限。3. 分場景破解權(quán)限問題3.1 靜態(tài)文件與站點目錄的權(quán)限修復(fù)最典型也最好修的場景靜態(tài)站或前端項目部署到服務(wù)器上結(jié)果訪問直接403。我習(xí)慣按這個順序處理第一步確認worker進程用戶。ps -eo user,comm | grep nginx假設(shè)返回nginx那所有判斷都以nginx用戶能否讀文件為基準(zhǔn)。第二步檢查目錄鏈路權(quán)限。namei -l /data/www/site/index.html這條命令會列出從根目錄到目標(biāo)文件的每一層權(quán)限一眼就能看出哪級目錄缺x或r。比如說f: /data/www/site/index.html drwxr-xr-x root root / drwxr-xr-x root root data drwxr-xr-x root root www drwx------ root root site -rw-r--r-- root root index.html問題立刻就清楚了site目錄權(quán)限是700只有root能進nginx用戶被拒之門外。修復(fù)方式是把目錄改成755chmod 755 /data/www/site如果不想用迭代的chmod命令可以一條命令遞推設(shè)置目錄和文件權(quán)限find /data/www/site -type d -exec chmod 755 {} \; find /data/www/site -type f -exec chmod 644 {} \;第三步把owner調(diào)整為更適合協(xié)作的模式。如果部署用戶是deploy但它要把文件放在/data/www下給nginx讀有幾種選擇直接把文件owner改成nginxchown -R nginx:nginx /data/www/site更推薦的做法owner保持deploygroup設(shè)成nginx目錄權(quán)限755文件權(quán)限640以上再把nginx用戶加入deploy組或文件所在組。這樣開發(fā)部署方便Nginx也能讀權(quán)責(zé)邊界清晰。實操里我一般不用nginx作owner因為后續(xù)程序要更新文件時會很別扭。把項目交給專用的部署賬戶然后把站點目錄的group設(shè)成nginx更順手。第四步檢查SELinux標(biāo)簽。如果你在CentOS系上文件權(quán)限改完還是403立刻檢查標(biāo)簽chcon -R -t httpd_sys_content_t /data/www/site但chcon是臨時生效重啟或執(zhí)行restorecon后會恢復(fù)。正規(guī)做法是用semanage fcontext寫持久化規(guī)則semanage fcontext -a -t httpd_sys_content_t /data/www/site(/.*)? restorecon -Rv /data/www/site如果后續(xù)還會部署新文件到該目錄這個持久化規(guī)則會自動給新文件打正確的標(biāo)簽一勞永逸。3.2 PHP-FPM的Unix Socket權(quán)限沖突PHP類站點比如各種PHP框架、WordPress跑Nginx時靜態(tài)文件可能一切正常但一旦訪問PHP文件報錯就變成了[error] connect() to unix:/run/php-fpm/php-fpm.sock failed (13: Permission denied)這種和網(wǎng)頁文件的Permission denied完全不是一回事。Nginx在這里是要把自己當(dāng)成客戶端去連接一個Unix domain socket而連接socket的權(quán)限由socket文件的權(quán)限和SELinux布爾值決定。先看socket文件本身ls -l /run/php-fpm/php-fpm.sock常見輸出是srw-rw---- 1 root php-fpm 0 某日某時 php-fpm.socksocket的owner是rootowner是php-fpm組。而Nginx worker進程是nginx用戶它不屬于php-fpm組。srw-rw----表示除了root和php-fpm組成員其他人都無法讀也無法寫nginx自然連不上。解決辦法有四種按推薦度排序方案A改PHP-FPM的socket owner/group為nginx。在PHP-FPM的pool.d/www.conf里找listen.owner nginx listen.group nginx listen.mode 0660改完重啟PHP-FPMsocket的ower/group就變成nginx了Nginx就能連。這種最省心。方案B把nginx用戶加進php-fpm組。usermod -a -G php-fpm nginx需要讓Nginx重新加載用戶組信息通常得重啟服務(wù)。效果也是讓nginx用戶獲得socket訪問權(quán)。方案C把socket權(quán)限放寬到0666。修改listen.mode 0666。能用但不建議因為這是把所有本地用戶都敞開寫入socket的權(quán)限等于把后門打開給同機器的其他進程。CenterOS系上還有第五個隱藏坑即使socket權(quán)限看起來全對可能還是連不上因為SELinux會攔。這時要檢查并打開布爾開關(guān)setsebool -P httpd_can_network_connect 1httpd_can_network_connect控制Nginx是否能發(fā)起網(wǎng)絡(luò)連接包括訪問socket。在CentOS 7/8上這個值默認往往是off。順帶一提如果你用Nginx反代到本機某端口遇到connection refused或13錯誤很多時候也是這個布爾值的鍋。我自己踩過最深的坑是改了socket權(quán)限之后忘了檢查SELinux結(jié)果還報13錯。后來把兩個維度并成一套檢查流程ls -l看Unix權(quán)限getsebool httpd_can_network_connect看SELinux布爾量一氣呵成。3.3 反向代理與緩存目錄的權(quán)限邊界Nginx做反向代理時目錄權(quán)限問題主要在緩存目錄和臨時目錄。如果配置了proxy_cache而緩存目錄沒有正確權(quán)限會出現(xiàn)[error] mkdir() /var/cache/nginx/proxy_temp failed (13: Permission denied)這里要理解Nginx對臨時目錄和緩存目錄的權(quán)限要求worker進程必須對它們有完全的讀寫執(zhí)行權(quán)限。因為Nginx在代理大文件、響應(yīng)體時會先把內(nèi)容寫入臨時文件快滿或超閾值時再移動到目標(biāo)位置。默認臨時目錄配置在http塊里proxy_temp_path /var/cache/nginx/proxy_temp; proxy_cache_path /var/cache/nginx/proxy_cache levels1:2 keys_zonemy_cache:10m max_size10g inactive60m use_temp_pathon;把這兩類目錄的owner統(tǒng)一給nginx是正確的做法mkdir -p /var/cache/nginx/proxy_temp /var/cache/nginx/proxy_cache chown -R nginx:nginx /var/cache/nginx chmod -R 700 /var/cache/nginx權(quán)限給700就夠反正是Nginx自己的私有目錄越少對外開放越好。如果你用Nginx反代到上游比如負載均衡到多個后端節(jié)點還要留意上游返回內(nèi)容很大時Nginx寫臨時文件會迅速占滿磁盤。這雖然不算權(quán)限問題但經(jīng)常和權(quán)限問題一起出現(xiàn)排查時我建議順手看下df -h和目錄所屬空間是否夠用。3.4 日志文件寫不進去怎么辦Nginx自身有兩個日志相關(guān)的大坑一是access.log/error.log本身寫不了導(dǎo)致啟動失敗二是站點的業(yè)務(wù)日志目錄權(quán)限不對導(dǎo)致應(yīng)用報錯。Nginx日志寫不了的場景error.log里大概率會直接出現(xiàn)[emerg] open() /var/log/nginx/error.log failed (13: Permission denied)這種往往是/var/log/nginx目錄的owner或者父目錄權(quán)限被動過worker進程和master進程在啟動階段沒權(quán)限創(chuàng)建或追加日志文件。解決辦法mkdir -p /var/log/nginx chown -R nginx:nginx /var/log/nginx用nginx -t驗證配置沒問題后再啟動服務(wù)。要是還不行注意看一下/var/log自身是不是權(quán)限異常某些加固腳本會把所有日志目錄都改成700 root:root那Nginx是真的寫不進去。業(yè)務(wù)日志目錄的坑沒法用Nginx配置解決。比如PHP應(yīng)用要寫/data/www/site/runtime日志目錄而Nginx worker用戶的上下文是nginx應(yīng)用進程PHP-FPM也是nginx用戶如果該目錄owner是別的用戶且不可寫應(yīng)用就會報錯。這類問題排查時要區(qū)分“給Nginx的權(quán)限”和“給應(yīng)用運行用戶的權(quán)限”別全部套用Nginx邏輯。我經(jīng)歷過一次所有改動都正確但仍寫不進去的情況最后發(fā)現(xiàn)是/data/www/site/runtime被chattr i加了不可變標(biāo)志。這個冷門問題值得留意排查時可執(zhí)行l(wèi)sattr /data/www/site/runtime如果看到----i--------移除標(biāo)志后用chattr -i就行。4. 一套可落地的排查流程4.1 從進程用戶到目錄鏈路的五步檢查爛熟于心的排查順序能幫你節(jié)省大量時間。我自己總結(jié)的五步法基本覆蓋了90%的Nginx權(quán)限問題第一步最小化確認worker身份。執(zhí)行ps -eo user,comm | grep nginx第二步確認報錯路徑與文件。從error.log里拿到具體路徑不要憑感覺猜目錄。日志里的路徑可能有前綴比如/usr/share/nginx/html和實際站點目錄不是一回事務(wù)必以日志為準(zhǔn)。第三步逐級查看目錄鏈路權(quán)限。用namei -l或手動ls -ld每個目錄確認每一級都有“其他人可執(zhí)行”權(quán)限。第四步確認文件權(quán)限。文件至少要644至少不能對worker用戶封鎖讀權(quán)限。第五步查SELinux狀態(tài)和標(biāo)簽。特別在CentOS/RHEL系上這一步不能省。這五步執(zhí)行下來基本能定位“是普通Linux權(quán)限還是SELinux標(biāo)簽”的問題。如果五步全對還是報Permission denied我才會考慮是不是有chattr加固、只讀掛載或其他文件系統(tǒng)層因素。4.2 模擬用戶訪問定位權(quán)限盲區(qū)有一個特別高效的驗證思路切換到worker用戶身份真實模擬一次Nginx訪問。先確認worker用戶比如是nginx然后執(zhí)行sudo -u nginx cat /data/www/site/index.html如果cat讀得出來說明普通Linux權(quán)限沒問題。如果提示permission denied那就可以直接定位到具體是哪個目錄的哪個環(huán)節(jié)缺權(quán)限sudo -u nginx ls /data/www/site/對比兩條命令的結(jié)果就能判斷到底卡在目錄穿越x權(quán)限還是文件讀取r權(quán)限上。PHP-FPM socket場景也能模擬sudo -u nginx curl --unix-socket /run/php-fpm/php-fpm.sock http://localhost/如果curl訪問socket返回權(quán)限錯誤說明socket的owner/group或SELinux布爾量有問題如果curl返回HTTP響應(yīng)那Nginx配置或PHP進程本身可能是主要嫌疑。這個模擬法的好處是能區(qū)分“文件系統(tǒng)的狀態(tài)”和“Nginx的配置行為”。很多時候文件權(quán)限沒問題、SELinux也正常但Nginx依然403原因就跑到Nginx配置層去了。通過模擬命令可以直接把配置層問題從權(quán)限問題中摘出來。5. 常見問題速查與避坑筆記5.1 高頻問題對照表我把這幾年在群里、社區(qū)里見過的高頻Nginx權(quán)限問題濃縮成一張速查表實戰(zhàn)時可對照著用報錯特征可能原因首選修復(fù)open() failed (13: Permission denied)文件在站點目錄目錄鏈路缺x或文件缺rSELinux標(biāo)簽不對namei -l定位缺權(quán)限層chmod 755/644SELinux下用restorecon -Rvdirectory index of /xxx is forbidden目錄下無index文件且未開autoindex放index文件或加autoindex on;慎用connect() to unix:/xxx.sock failed (13: Permission denied)socket權(quán)限不對調(diào)listen.owner/group或在PHP-FPM配置中指定nginx用戶mkdir() xxx failed (13: Permission denied)緩存目錄/臨時目錄owner不對chown -R nginx:nginx緩存目錄access.log/error.log open() failed (13)日志目錄owner不對或日志目錄被鎖chown -R nignx:nginx /var/log/nginx查lsattr反代到本機端口報13或111SELinux布爾量關(guān)閉setsebool -P httpd_can_network_connect 1文件存在但報2號No such file某級目錄權(quán)限缺x內(nèi)核“看不見”補目錄的x權(quán)限光看表不一定覆蓋所有場景但排障的時候先對照一遍大概率能省去你從零摸索的時間。5.2 幾個容易忽略的細節(jié)這一節(jié)分享幾個我在實際操作中容易踩到的細節(jié)常規(guī)文檔基本不提但影響實打?qū)崱<毠?jié)一Nginx對目錄的權(quán)限要求比想象中嚴格。目錄權(quán)限至少要711才能讓Nginx正常穿越750也能穿越owner可讀寫執(zhí)行、group可讀執(zhí)行、others沒有權(quán)限。這意味著如果你把站點目錄放在某用戶home目錄下比如/home/deploy/www而home目錄權(quán)限是700Nginx就永遠進不去。很多人把站點放在home下文件權(quán)限全對就是403原因就在home目錄沒有“其他人可執(zhí)行”。這種場景要么把目錄挪到/var/www或/data/www要么調(diào)整home目錄權(quán)限一般不建議。細節(jié)二不要輕易用user root;。我看到過幾次有人為了省事直接把Nginx worker設(shè)成root這樣的后果是任何漏洞都直接以root權(quán)限執(zhí)行。一旦PHP或其他模塊被攻擊影響是災(zāi)難性的。正規(guī)做法永遠是給worker一個低權(quán)限賬號并把站點文件通過合理的方式group、ACL、sudo部署交給這個賬號訪問。細節(jié)三改完權(quán)限記得測試重載。修改配置文件或權(quán)限后先跑nginx -t確認語法沒問題再用nginx -s reload平滑重載。很多時候權(quán)限問題不是沒修好而是改了配置沒生效或者語法報錯導(dǎo)致根本沒加載。細節(jié)四ACL權(quán)限也可能參與。當(dāng)ls普通權(quán)限看著沒問題但Nginx還是13錯時檢查ACLgetfacl /data/www/site如果ACL里有mask或者某條對特定用戶拒絕的記錄會直接影響Nginx的訪問判斷。用setfacl -R -m u:nginx:rX /data/www/site可以追加ACL授權(quán)這個方案特別適合那些不方便改owner的共享目錄場景。細節(jié)五日志輪轉(zhuǎn)也會制造權(quán)限問題。logrotate輪轉(zhuǎn)日志時如果新創(chuàng)建的日志文件owner是root而Nginx接下來要寫入就會遇到13錯。排查時如果看到日志報錯時間點集中在輪轉(zhuǎn)前后大概率就是輪轉(zhuǎn)腳本創(chuàng)建的文件權(quán)限不對。解決方式是在logrotate配置里加create 640 nginx adm這類參數(shù)確保輪轉(zhuǎn)后文件owner正確。結(jié)尾說點個人體會處理Nginx權(quán)限問題這幾年我最深的體會是這問題根本不算難難的是養(yǎng)成系統(tǒng)性排查習(xí)慣。很多人一看到403就直接把手頭文件chmod 777改完了還不行就開始懷疑配置、懷疑防火墻、懷疑網(wǎng)絡(luò)繞一大圈回來才知道是目錄上一級少了個x權(quán)限。與其這樣不如按前面那五步來確認worker身份、看日志拿路徑、查目錄鏈路、查文件權(quán)限、查SELinux。每一步都用一個命令驗證基本上兩分鐘就能定位。另外一個讓我吃過虧的細節(jié)是修改完權(quán)限后務(wù)必要重新加載Nginx和PHP-FPM。Nginx的root權(quán)限配置改了要重載PHP-FPM的listen.owner改了要重啟SELinux布爾量改了最好也重啟一下相關(guān)服務(wù)。很多人改完不重啟就在那兒反復(fù)測浪費時間不說還容易誤判。如果你按照這個流程排完之后發(fā)現(xiàn)權(quán)限完全正常但問題依舊最后再分享一個小技巧去看error.log的完整上下文別只盯著最后一行。有時候權(quán)限問題不是根因而是連鎖反應(yīng)。比如后端PHP進程崩潰了Nginx連不上socket日志先報connect失敗后報permission denied你把全部精力花在處理socket權(quán)限上結(jié)果后端一啟動就全好了。前后文結(jié)合著看才能避免白忙一場。