多數人看到 403 Forbidden,第一個念頭是網站壞掉了。事實剛好相反,伺服器運作得很正常,也聽懂了你的請求,只是依照規則決定不給。
這個「不給」的決定可能出在很多層:瀏覽器裡過期的 Cookie、VPN 出口 IP 被列入封鎖名單、網站資料夾少了一個首頁檔、權限數字差了一位、.htaccess 多寫了一行,或是 CDN 防火牆的某條規則。畫面長得一模一樣,訪客與網站管理者該走的排查路線卻完全不同,順序走錯,就會在不相干的地方反覆嘗試。
要走對路線,得先分清楚 403 這個數字在說什麼,以及它跟 401、404 的界線。

403 Forbidden 是什麼?
403 屬於 HTTP 狀態碼裡 4xx 客戶端錯誤這一類,意思是伺服器看得懂請求,但不照辦。IETF 的 RFC 9110 對它的定義是,伺服器理解這個請求,但拒絕履行;伺服器若願意公開理由,可以寫在回應內容裡,不過多數網站只丟出一個制式錯誤頁,什麼也不解釋。
它最常被搞混的對象是 401 與 404,三者的差別如下。
| 狀態碼 | 伺服器想表達的意思 | 不改條件重送同一個請求 |
|---|---|---|
| 401 | 缺少有效的驗證憑證,或帶來的憑證被拒絕,回應必須附上 WWW-Authenticate 標頭 | 補上正確憑證通常就能通過 |
| 403 | 請求看得懂,但不給,原因不一定跟登入有關 | 結果通常不會改變 |
| 404 | 找不到這個資源 | 資源出現之前結果都一樣 |
簡單來說,401 是「請先證明你是誰」,403 是「這裡不開放」。RFC 9110 也提到,如果請求已經帶了憑證,伺服器就是認定這組憑證不足以授權,客戶端不該自動用同一組憑證重送,可以改用不同的憑證再試;但請求被禁止也可能跟憑證毫無關係,所以登入成功之後仍然看到 403,並不矛盾。
有一種情況會讓判斷更混亂。伺服器如果不想向沒有權限的人承認某個資源存在,RFC 9110 允許它把本來該回 403 的請求改用 404 回應。因此看到 404 時,資源未必真的不存在;也因為這個緣故,同樣是「進不去」,有的網站回 403,有的回 404,兩者都可能是刻意設計。
另外,同樣寫著 Forbidden,不同環境的錯誤頁外觀可能截然不同。有的只有一行純文字的 Forbidden,有的是 Nginx 或 Apache 的預設頁,有的是帶有 Cloudflare 品牌的整頁封鎖畫面。外觀的差異不只是美醜問題,它會透露是哪一層做出拒絕的決定,這在後面的排查裡是很重要的線索。
最後,4xx 這一類代表伺服器認定問題出在這個請求,而不是伺服器自己出了狀況。伺服器真的當機、程式出錯時,回的是 5xx。所以看到 403,網站本身多半是活著的,要查的是「誰設了這條拒絕的規則」。
先確認是只有自己被擋還是整站都被擋
同一個 403,原因在訪客這一端,還是在網站那一端,修法完全不同。把「誰被擋」先分清楚,後面的排查才有正確的入口。
分流的結果只能指出往哪個方向找,不能直接當成結論,真正的原因還是要靠後面各節的檢查來確認。
換網路、換裝置與無痕視窗的測試結果
最直接的做法,是換條件再試一次,並且一次只換一個。
| 測試 | 結果 | 多半代表 |
|---|---|---|
| 手機關閉 Wi-Fi,改用行動網路開同一個網址 | 正常 | 原本那條網路的 IP 被擋,或 VPN 出口被擋 |
| 同一個網路下換一台裝置或另一個瀏覽器 | 正常 | 原本瀏覽器的 Cookie、快取或擴充功能有問題 |
| 任何網路、任何裝置都被擋 | 一樣 403 | 問題在網站端 |
無痕視窗是最快的排除法,因為它不帶原本的 Cookie,通常也不載入擴充功能。無痕視窗打得開,就可以把懷疑對象縮到原瀏覽器的資料與擴充功能;無痕視窗還是 403,再換網路測試。

如果你本人就是網站的擁有者,自己被擋而別人都能正常瀏覽,同樣先當作 IP 層的問題。很可能是你自己的 IP 被安全外掛、伺服器防火牆或 CDN 規則誤判,這比網站設定壞掉的機率高得多。前面提到 RFC 9110 的說法,請求被禁止未必跟憑證有關,所以「我明明已經登入了」並不能排除這個可能。
錯誤頁外觀透露的拒絕來源
錯誤頁的長相,是判斷誰回了這個 403 最容易取得的線索。Cloudflare 的文件把它分得很清楚,沒有 Cloudflare 品牌的 403,是來源網站的伺服器直接回的,通常跟伺服器上的權限規則有關;帶有 Cloudflare 品牌的頁面,則是 Cloudflare 的安全功能回的。
頁面上有 Ray ID,代表這個請求經過 Cloudflare;如果還寫著 Error 1020 這類編號,就是 Cloudflare 的安全功能擋下的。Cloudflare 的 Error 1020 文件說明,1020 代表網站擁有者設定的 Cloudflare 安全功能擋下了請求,並以 HTTP 403 回應;被 WAF 自訂規則或受管規則擋下的請求則不會顯示 1020,而是出現 WAF 的封鎖頁。至於 Cloudflare 以外的純文字頁,像是只有「403 Forbidden」加上伺服器軟體名稱的畫面,通常出自 Apache 或 Nginx 本身。

另一種容易被忽略的情況,是 Cloudflare 也可能回一個「沒有樣式」的 403。這種回應發生在請求剛進入 Cloudflare 基礎設施的極早期階段,例如客戶端送出的 Host 與 SNI 不一致,而且不會被記錄下來。無論看到哪一種頁面,上面的編號都先保留下來,網站管理者要靠它回查是哪一條規則擋下了請求。
訪客端造成 403 的情況與處理方式
只是想打開某個頁面的訪客,遇到 403 時能自己檢查的地方集中在網址、瀏覽器資料、網路出口與帳號權限四處,每一處都有判斷方法,也有該停手、改請站方處理的界線。
網站要不要對某個 IP、某個地區或某個帳號開放,是網站管理者的決定。訪客能做的是確認自己這一端有沒有問題,確認之後聯絡管理者,而不是想辦法越過它。
網址指向不開放的目錄或後台路徑
不少 403 的原因,是網址指向的地方本來就不開放。網址結尾只剩資料夾名稱、沒有檔名時,伺服器會去找該資料夾的首頁檔,找不到而且又不允許列出檔案清單,就會回 403。舊書籤、被截斷的連結、手動刪掉網址後半段,都容易走到這一步。對訪客來說,這是一個正常的拒絕,不代表網站出錯。
遇到這種情況,先檢查網址有沒有多打、少打或被截斷。從搜尋結果或舊書籤進來的連結如果失效,改從網站首頁重新點進去找,是最快的做法。後台路徑也一樣,一般訪客本來就沒有權限進入,看到 403 是網站在保護自己。
瀏覽器裡的 Cookie、快取和擴充功能
網站會用 Cookie 記錄登入與驗證狀態。Cookie 過期或內容損毀時,伺服器可能把請求當成沒有權限,因此在某些情況下,清掉這些資料有機會讓 403 消失。這不是必然的結果,是否有效要看實際的成因。
處理的順序是先用無痕視窗測試。無痕視窗正常,再只清除這個網站的 Cookie 與快取,不必整台瀏覽器清空,因為出問題的資料只屬於這個網站。清完之後需要重新登入,這是正常現象。如果清完仍然 403,再把最近安裝的擴充功能一個個停用測試,廣告攔截與隱私類的擴充功能是最常見的嫌疑對象。
反過來,無痕視窗和一般視窗都是 403,問題就不在瀏覽器,不必再花時間清資料或停用擴充功能。
VPN 與共用 IP 被封鎖,地區規則也會擋人
網站可以依訪客的 IP、所屬的網路業者(ASN)或所在國家來決定放行或封鎖。Cloudflare 的 IP Access rules 文件就說明,網站擁有者能用這三種條件對流量設定允許、封鎖或挑戰,常用來處理可疑的來源。
這也解釋了為什麼 VPN 與共用網路容易被牽連。VPN 的出口 IP 通常很多人同時使用,只要其中一個人做了可疑的事,整個 IP 就可能被列入封鎖。公司、學校或公共 Wi-Fi 的共用出口也是同樣的道理。地區限制則是網站依自己的服務範圍設定的規則,看到 403 並不表示網站壞了,而是它刻意不對這個地區開放。
訪客能做的事很單純,就是關掉 VPN,用自己平常的網路再試一次,確認被擋的是哪一個出口。如果關掉 VPN 後仍然 403,或是你確定自己沒有做任何異常動作,就把狀況回報給網站管理者。規則由站方決定,要不要放行也只有站方能確認。
登入帳號本身沒有該頁面的權限
已登入仍被擋,而且換網路、換瀏覽器都一樣,就可能是帳號層級的問題。有些頁面只開放給特定角色,例如會員制網站的付費內容、公司內部系統的管理頁面。這時登入成功只代表伺服器認得你,不代表你有權限進入這一頁。
這對應到前面提到的 RFC 語意,伺服器認定這組憑證不足以授權,才回了 403,所以手上若有另一個本來就有權限的帳號,改用它再試是合理的做法。
如果沒有這樣的帳號,就只能找管理者處理。把你的帳號名稱、想開啟的頁面網址,以及你需要這個權限的用途一併告訴對方,他才能判斷該調整你的角色,還是這個頁面本來就不對外開放。
聯絡網站管理者時要附上的資料
訪客端的排查走到最後,就是請站方處理。要讓對方一次就查得到原因,聯絡時請盡量附上下面這些資料:
- 出問題的完整網址,包含 https 開頭的整串文字。
- 發生的時間,並註明時區,管理者才能對照伺服器日誌。
- 錯誤頁的截圖,頁面上如果有 Ray ID 或錯誤編號(例如 1020),一定要拍進去。
- 當時使用的網路環境,是家用網路、公司網路還是行動網路,有沒有開 VPN。
其中 Ray ID 與錯誤編號最有價值。Cloudflare 的 Error 1020 文件指出,不是網站擁有者的人遇到 1020 時,應該把錯誤訊息的截圖交給網站擁有者;擁有者再用 Ray ID 或訪客的 IP,到 Security Events 記錄裡搜尋,就能找出是哪個安全功能與哪條規則擋下了請求。
沒有這些資料,管理者只能憑猜測排查。附得越完整,對方越不需要回頭反覆追問,問題也處理得越快。
網站端排查先從伺服器錯誤日誌開始
網站端所有原因共用的第一步,是不要憑感覺猜,直接看伺服器的錯誤日誌。日誌通常會寫出伺服器拒絕的理由,光是那一行紀錄,就能省下大半的排查時間。
日誌的位置依主機環境而異。自己管理的 Linux 主機,Apache 與 Nginx 的錯誤日誌常放在 /var/log 底下的對應資料夾;共享主機多半在控制面板裡提供日誌下載或檢視的功能。找不到時,先查主機商的說明文件。
以下是幾則常見的訊息,以及它們指向後面哪一節。
| 日誌訊息的特徵 | 指向的原因 | 對應章節 |
|---|---|---|
| Apache 寫出 AH01276,內容是 No matching DirectoryIndex 加上 server-generated directory index forbidden | 資料夾沒有首頁檔,目錄瀏覽又關閉 | 缺少首頁檔而目錄瀏覽又關閉 |
| Nginx 寫出 directory index of 某個路徑 is forbidden | 同上,是 Nginx 的版本 | 缺少首頁檔而目錄瀏覽又關閉 |
| 日誌裡出現 Permission denied 這類字樣 | 伺服器程序沒有讀取檔案或進入目錄的權限 | 檔案與目錄權限設錯 |
Red Hat 的知識庫記錄過這種情況。請求的是資料夾、找不到符合 DirectoryIndex 的檔案,而 Options 又不允許自動產生目錄清單時,Apache 會回應 403 Forbidden,並在錯誤日誌寫下類似下面的訊息,路徑依實際被請求的資料夾而定:
AH01276: Cannot serve directory /var/www/html/images/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directiveCode language: plaintext (plaintext)
Nginx 遇到類似情況,則會寫出下面這種格式的訊息,路徑是被請求的資料夾:
directory index of "/var/www/html/images/" is forbiddenCode language: plaintext (plaintext)
網站前面如果有 CDN 或防火牆服務,日誌裡可能完全找不到那一筆請求,這一點需要特別留意。這代表請求還沒到達你的伺服器,就在前面那一層被擋下,要往 WAF 與 CDN 那一節查。
檔案與目錄權限設錯,網站就讀不到內容
權限是網站端最常見的一類成因。伺服器程序是以某個系統使用者的身分在執行,如果檔案或目錄對這個使用者來說不可讀、不可進入,它就只能回 403。
診斷時建議照固定的順序,先確認目錄是否有執行權限,再確認檔案與目錄的數值是否合理,最後才檢查擁有者與 SELinux 標籤。三個環節都沒有問題,權限這條線才算排除。
目錄需要執行權限,上層目錄也一樣
Linux 的權限分成擁有者、群組與其他人三組,每一組都有讀、寫、執行三種存取權,數值分別是 4、2、1。WordPress 開發者手冊的說明是,每一組把數值加總就是該組的權限,所以 755 代表擁有者是 rwx,群組與其他人是 r-x。
| 符號 | 數值 | 對檔案的意義 | 對目錄的意義 |
|---|---|---|---|
| r | 4 | 可以讀取內容 | 可以列出裡面有哪些檔案 |
| w | 2 | 可以修改內容 | 可以新增或刪除裡面的檔案 |
| x | 1 | 可以當程式執行 | 可以進入並穿過這個目錄 |
目錄上的 x 最容易被漏掉。檔案只要能讀就能被伺服器送出去,但目錄沒有執行權限,伺服器連進去都做不到,裡面的檔案再怎麼設成可讀也沒有用。這就是為什麼檔案常設 644,目錄卻常設 755 或 750,都保留了執行權限。

另一個常見的誤區,是只對最底層的資料夾下 chmod。Linux 解析路徑時,路徑上每一層目錄都要讓程序有搜尋(也就是執行)權限,缺任何一層都會失敗。網站根目錄以上的每一層上層目錄,都必須讓伺服器使用者通過。要一次看清楚整條路徑上的權限,可以用 namei 指令:
namei -l /var/www/html/wp-content/uploads/photo.jpgCode language: Bash (bash)
輸出會逐層列出每一段路徑的擁有者與權限,哪一層缺了 x,一眼就看得出來。
把目錄設成 755、檔案設成 644
如果你能用 SSH 連到主機,可以用 find 搭配 chmod,一次把目錄設成 755、檔案設成 644。WordPress 開發者手冊也以目錄 755、檔案 644 當作一般的預設值,至於採用 suexec 的共享主機,手冊建議的是目錄 755 或 750、檔案 644 或 640。
find /var/www/html -type d -exec chmod 755 {} \;
find /var/www/html -type f -exec chmod 644 {} \;Code language: Bash (bash)
執行之前,先確認 /var/www/html 是你網站實際的根目錄路徑,這裡只是通用示範。手冊也提醒,權限設錯可能讓網站離線,所以改動前先備份,改完立刻到瀏覽器實測。
不要為了讓 403 消失就用 chmod -R 777。同一份手冊明確指出,任何目錄都不該設成 777,連上傳目錄也一樣;在 suexec 型的共享主機上,PHP 是以檔案擁有者的身分執行,目錄設 755 就已經寫得進去。777 等於讓同一台主機上的所有使用者都能修改你的檔案,是用安全換來暫時的方便。
共享主機如果沒有 SSH,可以改用 FTP 軟體或主機控制面板的檔案管理員。在檔案或資料夾上按右鍵,選「檔案權限」之類的選項,直接輸入 755 或 644 的數值即可。
權限正確時再查檔案擁有者和 SELinux 標籤
權限數值都對、仍然是 403 時,最後有兩個嫌疑。第一個是檔案的擁有者。WordPress 開發者手冊指出,在共享主機上,檔案不該歸伺服器程序本身(例如 www、apache、nobody)所有,而應該屬於網站帳號。搬站或用不同使用者上傳過檔案後,擁有者錯亂是常見的情況。
第二個是 SELinux,主要出現在 RHEL 系列的主機。Red Hat Enterprise Linux 8 的文件指出,SELinux 的政策規則是在傳統權限(DAC)規則之後才檢查,傳統權限先拒絕的話,根本輪不到 SELinux 規則。反過來說,權限數值都對卻仍然被拒絕,SELinux 標籤不對就是值得懷疑的方向。同一份文件也提到,標籤出錯的常見原因是服務用了非標準的目錄;網站內容放在 /var/www/html 底下時,標籤通常是 httpd_sys_content_t。
標籤出問題時,該用標籤工具處理,不是改 chmod。Red Hat 文件的做法是先用 semanage fcontext 登錄標籤規則,再用 restorecon 套用:
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/myweb(/.*)?"
sudo restorecon -R -v /srv/mywebCode language: Bash (bash)
另外要留意,Red Hat 的文件說明,用 chcon 做的變更在檔案系統重新標記或執行 restorecon 之後就不會保留,要持久就得用 semanage fcontext 加 restorecon。
設定檔裡的存取規則誤擋正常訪客
檔案權限都沒問題,下一個嫌疑是伺服器自己的存取規則。Apache 有 .htaccess 與主設定檔,Nginx 則是在 location 區塊裡設 allow 與 deny。
規則誤擋有一個共通的模式,寫規則的人想擋的是惡意流量,但條件寫得太寬,或者放在錯誤的層級與順序,連正常訪客、自己的辦公室、監控工具都被一起擋掉。改任何規則之前先備份,一次只動一處,改完立刻測試,才不會在出問題時分不清是哪一次修改造成的。
Apache 的 Require 規則和舊式 Order 寫法
Apache 2.4 用 Require 指令控制誰能進來。Require all denied 全部拒絕,Require ip 只放行特定網段,Require not ip 則是把某些 IP 排除。其中任何一條寫過頭,都會把正常訪客擋在外面,最常見的是辦公室的固定 IP 或 CDN 的出口 IP 被漏掉。
<Directory "/var/www/html/admin">
Require ip 203.0.113.0/24
</Directory>Code language: Apache (apache)
這段設定只放行 203.0.113.0/24 這個網段,其他來源全部會得到 403。如果要放行所有人、只排除特定 IP,要用 RequireAll 把兩個條件包在一起:
<RequireAll>
Require all granted
Require not ip 203.0.113.25
</RequireAll>Code language: Apache (apache)
網站如果在 CDN 後面,Apache 看到的來源 IP 會是 CDN 的 IP,不是訪客的真實 IP。依 IP 設定的規則要先還原訪客 IP 才有意義,Apache 上常見的做法是啟用 mod_remoteip。
老網站的設定檔裡,可能還留著 Order、Allow、Deny 這組舊式寫法。Apache HTTP Server 的文件指出,這些指令由 mod_access_compat 提供,已經棄用、未來版本會移除,應該避免使用,也不要照著推薦這些指令的過時教學去寫。
RewriteRule 的 F 旗標與防盜連規則
mod_rewrite 的 F 旗標會讓符合條件的請求直接回 403。Apache HTTP Server 的文件把它列為可依任意條件拒絕存取的方式。最典型的應用就是防盜連,也就是比對請求的 Referer,不是來自自己網站的圖片請求就拒絕。
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?example\.com/ [NC]
RewriteRule \.(jpe?g|png|gif|webp)$ - [F,NC]Code language: Apache (apache)
第一個條件放行沒有 Referer 的請求,第二個條件放行來自 example.com 的請求,剩下的圖片請求就會被 F 旗標擋下。
這類規則最容易寫得不完整。如果第二個條件只寫了 https 與不帶 www 的版本,那麼從 http 或 www 開頭的網址載入圖片,就會被當成外部來源而擋掉。症狀很有特色,網頁本身正常,圖片卻全部 403。規則裡的網域要涵蓋 www 與非 www、http 與 https 這幾種組合,另外若圖片放在 CDN 子網域,也要一併列入。
Nginx 的 allow、deny 規則順序和繼承
Nginx 用 ngx_http_access_module 的 allow 與 deny 限制特定來源。Nginx 的文件說明,規則會依序檢查,直到第一個符合的規則為止,所以順序錯了就會誤擋。
location /admin/ {
allow 203.0.113.0/24;
allow 2001:db8::/32;
deny all;
}Code language: Nginx (nginx)
這段設定先放行兩個網段,最後用 deny all 拒絕其他所有來源,是正確的寫法。如果把順序反過來,變成 deny all 在前、allow 在後,每個請求都會先符合 deny all,後面的 allow 永遠輪不到,結果是所有人都被拒絕。
另一個常見的原因是繼承。Nginx 的文件指出,allow 與 deny 只有在當前層級沒有定義任何 allow 或 deny 時,才會從上一層繼承。也就是說,只要在某個 location 裡寫了一條自己的 allow 或 deny,上一層的規則就不再適用。「明明上層有 allow,這裡還是 403」多半就是這個原因。

暫時移開 .htaccess 確認問題來源
Apache 站最快的二分法,是先把 .htaccess 移開,看 403 還在不在。這個檔案通常是隱藏檔,FTP 軟體需要開啟顯示隱藏檔的選項才看得到。
cp .htaccess .htaccess.bak
mv .htaccess .htaccess.offCode language: Bash (bash)
改名之後重新整理頁面。403 消失,問題就在這個檔案裡,這時把規則逐段加回去,每加一段就測一次,很快就能找出元兇;403 還在,就代表問題不在 .htaccess,繼續往權限或主設定查。測完之後記得把檔名改回來。
.htaccess 能不能生效,取決於 Apache 主設定裡的 AllowOverride。Apache HTTP Server 的文件指出,.htaccess 是按目錄設定的分散式設定檔,指令適用於所在目錄與所有子目錄,能放哪些指令由 AllowOverride 決定,預設值是 None,此時 .htaccess 會被完全忽略。文件也提供了測試方法,在 .htaccess 裡放一個沒有意義的字詞後重新載入頁面,如果沒有出現 500 錯誤,幾乎可以確定 AllowOverride None 生效、檔案被忽略了。在正式站做這個測試,記得馬上把那個字詞刪掉。
另外,Apache 會往上逐層尋找上層目錄的 .htaccess,把各層的指令合在一起套用,所以問題規則也可能不在目前這個資料夾,而是上層的某個檔案。
缺少首頁檔而目錄瀏覽又關閉
這是新手最常遇到的網站端 403。網址對應的是資料夾,資料夾裡沒有伺服器指定的首頁檔,自動列出檔案清單的目錄瀏覽也沒有開,伺服器就拒絕了。這類請求只在網址以斜線結尾、也就是指向資料夾時,才會走到首頁檔的判斷邏輯。搬站或換主機之後,網站根目錄與首頁檔的位置對不上,是這類 403 最典型的情境。
處理時有一個原則,設定檔裡的首頁檔名稱要跟實際存在的檔案對得上,而不是反射性地把目錄瀏覽打開。目錄瀏覽一旦開啟,資料夾裡所有檔案的清單就對外公開,可能連備份檔、設定檔都被看光。
Apache 的 DirectoryIndex 與 Indexes
Apache 找首頁檔靠 DirectoryIndex,預設值是 index.html,可以列出多個檔名,依序尋找。用 PHP 的網站要確認 index.php 在清單裡,否則即使檔案存在,Apache 也不會把它當成首頁。
DirectoryIndex index.php index.htmlCode language: Apache (apache)
找不到首頁檔時,要不要自動產生檔案清單,是由 Options 的 Indexes 決定。Apache 的文件說明,自動產生目錄清單需要用 Options +Indexes 啟用;沒有啟用的話,缺首頁檔就只剩拒絕這條路,結果就是前面日誌那一節看到的 AH01276 訊息加上 403。
Options -IndexesCode language: Apache (apache)
這一行代表明確關閉目錄清單,是多數正式網站該有的預設狀態。遇到這個 403 時,正確的修法是補上首頁檔,或把 DirectoryIndex 的清單改成實際存在的檔名,而不是把 -Indexes 改成 +Indexes。
Nginx 的 index、autoindex 與 root
Nginx 的對應設定是 index、autoindex 與 root。根據 Nginx 的文件,index 預設是 index.html,依清單順序檢查檔案,且只處理以斜線結尾的請求;autoindex 預設是 off,通常是 index 找不到首頁檔時,請求才會交給它產生目錄清單。兩者都沒有結果,就是 403。
server {
listen 80;
server_name example.com;
root /var/www/html;
index index.php index.html;
autoindex off;
}Code language: Nginx (nginx)
PHP 網站要把 index.php 放進 index 清單。root 指到錯誤的路徑也會造成類似的問題,搬站後路徑沒有跟著更新是常見原因。另外,Nginx 用 index 找到首頁檔後會做一次內部轉址,請求可能改由另一個 location 處理;首頁檔明明存在,卻被轉進一個設了存取限制的 location,同樣會看到 403。
改完設定之後,先檢查語法,沒有錯誤再重新載入:
sudo nginx -t
sudo systemctl reload nginxCode language: Bash (bash)
nginx -t 會在套用之前先檢查語法,錯誤當場就看得到。這一步省不得,因為 Nginx 的文件說明,重新載入時如果新設定套用失敗,主程序會退回舊設定繼續運作,指令看似執行完畢,新設定其實根本沒有生效。要是改用重新啟動而不是重新載入,設定有錯時服務可能直接起不來,整個站台跟著中斷,後果比原本的 403 嚴重得多。
WAF、CDN 與主機防火牆擋下的請求
伺服器設定都乾淨,日誌裡卻找不到那一筆請求,或者訪客看到的是帶品牌的封鎖頁,就是伺服器前面那一層擋的。
站長自己設定的規則,最常誤傷的對象有四種:自己、監控工具、搜尋引擎爬蟲,以及 API 呼叫。這四種流量的 IP 或特徵常跟「可疑流量」重疊,一不小心就被一起擋掉。
Cloudflare 的 403 分成兩種來源
Cloudflare 的 Error 403 文件把來源分成兩類。第一類是來源伺服器直接回的 403,多半跟 .htaccess 的權限規則、ModSecurity 規則或 IP 拒絕規則有關。文件也特別提醒,要確認 Cloudflare 的 IP 範圍沒有被封鎖,否則經過 Cloudflare 轉送的請求會被來源主機一起擋下。這類 403 沒有 Cloudflare 品牌,要回到來源主機查。
第二類是 Cloudflare 自己回的 403,原因包括 WAF 規則(受管規則或自訂規則)的挑戰或封鎖動作、預設為 Medium 的 Security Level、DDoS 防護、Browser Integrity Check,以及多數 1xxx 開頭的錯誤碼。這類 403 要進 Cloudflare 的後台查,改的是 Cloudflare 的設定,不是來源主機。
兩類原因各有各的修法,分錯了就會白做工。在來源主機上翻遍設定檔,查不出 Cloudflare 規則造成的 403;反過來,進 Cloudflare 後台調整規則,也解決不了來源主機自己回的 403。如果被擋的是監控工具,Cloudflare 的文件建議用帶 Skip 動作的自訂規則,讓它略過誤擋的安全功能;不過免費方案的 Bot Fight Mode 無法用 Skip 規則略過。
用 Ray ID 在安全事件記錄找出命中規則
站長端最實用的一招,是請訪客提供錯誤頁上的 Ray ID,或是他們的 IP。Cloudflare 的 Error 1020 文件說明,擁有者到 Security 底下 Analytics 的 Events 分頁,用 Ray ID 或客戶端 IP 搜尋,事件會顯示是哪個安全功能與哪條規則擋的,再評估原因並更新該功能或規則的設定。
找到命中的規則之後,調整的對象是那一條規則,不是把整個安全功能關掉。關掉整個功能雖然能讓 403 立刻消失,但也等於撤掉了原本的防護。針對規則擋下的請求,也可以自訂錯誤頁,讓被擋到的人看得到聯絡方式,之後再遇到誤擋,網站就能更快收到完整的回報。
ModSecurity、fail2ban 和安全外掛造成的 IP 封鎖
來源主機上也有幾道會封鎖 IP 的機制,常見的有三種:ModSecurity 規則、fail2ban,以及網站上的安全外掛。誤擋最常發生在短時間內多次登入失敗,或請求過於頻繁的情況,這些行為在規則眼裡跟攻擊很像。
三者的特徵不太一樣。ModSecurity 屬於請求內容的規則比對,被擋的請求多半有特定的字串或參數;fail2ban 是看日誌,統計到太多失敗就暫時封鎖該 IP;安全外掛則是從網站程式層面擋人,通常跟登入或後台路徑有關。
網站在 CDN 後面時,要特別注意訪客真實 IP 的還原。Cloudflare 的 IP Access rules 文件提到,為了避免 fail2ban 誤封 Cloudflare 的 IP,造成部分訪客出現錯誤,需要在來源伺服器的日誌中還原原始訪客 IP。沒有還原的話,fail2ban 看到的永遠是 CDN 的 IP,一旦封掉,經由那個 IP 轉送的訪客就會一起連線出錯。
解除封鎖也要回到擋下請求的那個工具裡處理,ModSecurity 調整規則、fail2ban 解封 IP、安全外掛則在外掛的設定頁操作,在別處改設定不會有效果。
搜尋引擎爬蟲被擋,網址會逐漸移出索引
站長最該在意的後果,是搜尋引擎爬蟲被誤擋。Google 說明檢索器如何處理 HTTP 狀態碼的文件指出,Google 不使用回傳 4xx 的網址內容;先前已收錄的網址之後回 4xx,Google 的系統會逐漸停止使用它,並把已收錄且回 4xx 的網址自索引移除。
除了 429 以外,所有 4xx 狀態碼都以同樣的方式處理,檢索器會通知下游系統網頁內容不存在,這個網址被檢索的頻率也會逐漸降低。Search Console 針對 4xx 到 5xx 的狀態碼,以及失敗的轉址,都會產生錯誤訊息。換句話說,WAF 或安全外掛的規則一旦把 Googlebot 一起擋掉,網站在搜尋結果裡的曝光會慢慢流失。
所以調整 WAF 或封鎖規則之後,除了確認自己的 IP 沒被擋,也要確認搜尋引擎爬蟲沒有被連帶擋掉。修復之後,爬蟲需要時間重新檢索,索引不會立刻恢復。
WordPress 站台的 403 另有專屬成因
WordPress 站除了前面講的通用成因,還有幾個特有的 403 來源。
最常見的是安全外掛。這類外掛會依 IP、登入失敗次數設定封鎖規則,誤判時,站長本人或正常訪客就會被擋在外面。後台頁面出現 403 時,還要先分辨這是設定出錯,還是網站已經被人動過手腳。兩種情況的處理方式完全不同,先別急著改設定。
另一個高頻的原因,是 .htaccess 被外掛改壞。WordPress 靠這個檔案處理漂亮永久連結;WordPress 開發者手冊介紹 .htaccess 的頁面也說明,頁面上列出的預設內容可以用來還原損壞的檔案,例如被行為異常的外掛弄壞的時候。單站台預設的區塊內容如下:
# BEGIN WordPress
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
# END WordPressCode language: Apache (apache)
動手之前,先備份現有的 .htaccess,再把這段內容貼回去,其他外掛寫進去的規則可以之後再一條條補回來測試。
檔案權限方面,wp-config.php 是特別要注意的檔案。WordPress 開發者手冊建議把它的權限收緊,在 suexec 型的主機上設成 440 或 400,避免同一台主機上的其他使用者讀取。不過權限收得太緊,伺服器自己也讀不到,同樣會造成錯誤,改完要馬上實測。
修復後的驗證與日後預防
修復之後,不能只看瀏覽器畫面就算完成,因為瀏覽器可能顯示的是快取內容。更可靠的做法,是用 curl -I 直接看伺服器回的狀態碼:
curl -I https://example.com/path/Code language: Bash (bash)
輸出的第一行就是狀態碼,看到 HTTP/2 200 代表已經恢復正常,仍然是 HTTP/2 403 就表示問題還沒解決。網站在 Cloudflare 後面時,回應標頭裡會帶有 cf-ray,可以確認請求確實經過了它。有些規則對 HEAD 與 GET 的反應不同,所以除了 curl,也要用無痕視窗在瀏覽器裡實際打開一次。
驗證還要在不開 VPN 的外部網路再做一次,因為你自己的網路、自己的 IP,可能剛好是被放行的那一個,在內部看起來正常,不代表訪客也正常。日誌裡也不該再出現同一則拒絕訊息;如果修改之後伺服器出現新的錯誤,Apache 的文件建議查 httpd 的錯誤日誌,裡面通常會指出是哪一條指令不被允許。

日後預防靠的是習慣。改 .htaccess 或伺服器設定之前先備份,一次只改一處,改完立刻測,出了問題才知道要退回哪一步。調整 WAF 與封鎖規則之前,先確認自己的 IP、監控工具與搜尋引擎爬蟲不在被擋的名單裡。
403 Forbidden 是伺服器講得最清楚的一種拒絕,它知道你要什麼,也明確表示不給。既然是有人設下的規則,通常就會留下痕跡,從錯誤頁上的編號和伺服器日誌往回找,多半能找到那條規則,也就知道該改的是哪一個地方。
