Nginx 與 Apache
這個分類下的問題與解答。點標題可以展開答案,也可以點右上角進到單題頁面。
更新virtualmin至版本7.1以上會提示
【已棄用的 Apache mod_php模塊已在您的系統上啟用,但沒有被任何虛擬服務器使用。禁用它將阻止將來為任何虛擬服務器選擇此 PHP 執行模式。】
如果您禁用他,重啟apache或nginx失敗,通常是【/etc/httpd/conf/httpd.conf】設定檔中的php_admin_value跟php_value沒有清除。
找出設定檔中的【php_admin_value engine Off】將其註解掉。
兩者的架構差異
| Nginx | Apache | |
|---|---|---|
| 連線處理 | 事件驅動、非同步 | 以行程或執行緒處理(依 MPM) |
| 高並發表現 | 記憶體用量穩定 | 視 MPM 設定而定 |
| 靜態檔案 | 效率高 | 正常 |
| 目錄層級設定 | 不支援 | 支援 .htaccess |
| 模組載入 | 多需編譯時決定 | 可動態載入 |
| 設定語法 | 簡潔、階層式 | 指令式、較冗長 |
最實際的差別是 .htaccess。 Apache 允許在各目錄放置設定檔,不需重載服務即可生效;Nginx 沒有這個機制,所有設定都要寫在主設定中並重載。
.htaccess 的取捨
優點
- 不需要主機管理權限就能調整轉址與存取控制
- 共享主機環境下,使用者可自行設定
- 許多開源套件預設附帶 .htaccess
缺點
- 每次請求都要逐層讀取目錄中的 .htaccess,有效能成本
- 設定分散,不易追蹤
- 寫錯可能造成整站 500 錯誤
如果有主機的完整控制權,Apache 也建議把規則寫進 vhost 設定並關閉 AllowOverride,可減少檔案系統的查找。
常見的三種部署方式
一、純 Nginx + PHP-FPM
目前常見的組合。Nginx 處理連線與靜態檔案,PHP 交給 FPM 處理。
- 優點——資源效率好、設定集中
- 注意——原本依賴 .htaccess 的套件需改寫規則
二、純 Apache + mod_php 或 PHP-FPM
相容性最好,套件的預設設定通常可直接使用。
- 建議搭配 event MPM 與 PHP-FPM,而非傳統的 prefork + mod_php
三、Nginx 反向代理 Apache
Nginx 在前端處理連線與靜態檔案,動態請求轉給後端的 Apache。
- 優點——保留 .htaccess 相容性,同時取得前端的連線處理效率
- 注意——需正確傳遞真實來源位址,否則後端記錄到的都是代理的位址
反向代理時的來源位址
# Nginx 端 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $host;
後端 Apache 需啟用 mod_remoteip 並設定信任的代理來源,否則存取紀錄與應用程式取得的來源位址會全部是代理伺服器。
怎麼選
- 新建、且有完整控制權——Nginx + PHP-FPM
- 既有系統依賴 .htaccess,且不想改寫——Apache,或 Nginx 反代 Apache
- 共享主機——通常沒得選,依主機商提供的環境
- 需要動態載入特定模組——Apache 較有彈性
就一般企業網站的流量規模而言,兩者的效能差異通常不是瓶頸——真正的瓶頸多半在應用層與資料庫。效能問題的排查順序見網站慢的常見原因。
設定檔的位置與結構
Nginx
/etc/nginx/nginx.conf # 主設定 /etc/nginx/conf.d/*.conf # 常見的分割位置 /etc/nginx/sites-available/ # Debian 系 /etc/nginx/sites-enabled/ # 以符號連結啟用
Apache
/etc/apache2/apache2.conf # Debian 系主設定 /etc/httpd/conf/httpd.conf # RHEL 系主設定 /etc/apache2/sites-available/ # 站台設定 /etc/apache2/mods-available/ # 模組設定
路徑因發行版而異,可用 nginx -t 或 apachectl -S 確認實際載入的設定檔。
修改設定的標準流程
- 備份原設定檔
- 修改
- 測試語法——Nginx 用
nginx -t,Apache 用apachectl configtest - 重載而非重啟——
systemctl reload nginx或apachectl graceful,可避免中斷既有連線 - 驗證實際效果
第三步不要省略。 語法錯誤時直接重啟會導致服務無法啟動,網站整個下線。
本文的設定範例以常見的環境為例,實際的路徑、模組名稱與指令可能因作業系統、版本與安裝方式而異。套用前請先在測試環境驗證,並確實備份原設定檔。
虛擬主機的基本結構
Nginx
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com;
root /var/www/example/public;
index index.php index.html;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
access_log /var/log/nginx/example.access.log;
error_log /var/log/nginx/example.error.log;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
注意:較新版本的 Nginx 已改用獨立的 http2 on; 指令,舊寫法是在 listen 後面加 http2 參數。
Apache
<VirtualHost *:443>
ServerName example.com
DocumentRoot /var/www/example/public
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
<Directory /var/www/example/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.error.log
CustomLog ${APACHE_LOG_DIR}/example.access.log combined
</VirtualHost>
http 轉 https
Nginx
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
用 return 而不是 rewrite——效率較好,語意也更清楚。
Apache
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
Redirect permanent / https://example.com/
</VirtualHost>
或用 mod_rewrite:
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]
統一 www 與非 www
加上協定,同一頁面可能有四種網址形式,必須擇一為主。
Nginx:獨立的 server 區塊處理
server {
listen 443 ssl;
server_name www.example.com;
# 憑證需涵蓋 www
return 301 https://example.com$request_uri;
}
轉址用的 server 區塊也需要憑證,否則使用者在轉址前就會看到憑證警告。
Apache
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]
正規化的完整說明見網址正規化與重複內容怎麼處理。
轉址的四個常見錯誤
一、用 302 而非 301
永久搬遷應該用 301。302 表示暫時,搜尋引擎不會完整傳遞評價。
二、產生轉址鏈
例如 http://www → https://www → https://(無 www),經過兩次跳轉。
應該一次到位:http://www 直接轉到 https://(無 www)。
三、轉址迴圈
最常見於反向代理或 CDN 環境——前端已經是 https,但回源時是 http,後端又判斷要轉 https,形成循環。
解法:判斷 X-Forwarded-Proto 而非直接判斷 HTTPS:
# Apache 於代理環境下
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
四、全部轉到首頁
舊網址大量轉向首頁,會被視為軟性 404。應盡量對應到相近的頁面。
舊網址對應的批次轉址
改版時常需要大量的一對一對應。
Nginx:使用 map
map $request_uri $redirect_target {
default "";
/old-page.html /new-page;
/products/123.php /products/abc;
}
server {
if ($redirect_target != "") {
return 301 $redirect_target;
}
}
map 比大量的 if 或 location 效率高得多,數千筆對應也不成問題。
Apache:使用 RewriteMap 或 Redirect
Redirect 301 /old-page.html /new-page
數量龐大時建議使用 RewriteMap 搭配外部檔案。
改版轉址的完整規劃見網站改版或更換網域,SEO 排名怎麼保住。
try_files 與 rewrite 的選擇
Nginx 中處理前端控制器(單一入口)時,優先使用 try_files:
location / {
try_files $uri $uri/ /index.php?$query_string;
}
它會依序嘗試實體檔案、目錄,最後才交給 index.php。比用 rewrite 判斷檔案是否存在更直接,也避免了 if 的常見陷阱。
Nginx 的 if 在 location 內的行為有已知的限制,官方文件也建議盡量避免——能用 try_files 或 map 解決的就不要用 if。
本文的設定範例以常見的環境為例,實際的路徑、模組名稱與指令可能因作業系統、版本與安裝方式而異。套用前請先在測試環境驗證,並確實備份原設定檔。
先確認瓶頸在哪
調整伺服器設定之前,先確認問題不是出在應用層。如果純文字頁面的回應時間就已經偏高,那是後端或資料庫的問題,前端調整幫助有限。
排查順序見網站慢的常見原因與優先處理順序。
一、啟用壓縮
Nginx
gzip on;
gzip_vary on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_proxied any;
gzip_types
text/plain text/css text/xml
application/javascript application/json
application/xml image/svg+xml;
不要壓縮已壓縮的格式——JPG、PNG、WebP、MP4、ZIP 再壓縮沒有效果,只是浪費 CPU。
關於 Brotli
壓縮率通常優於 gzip,但需要額外的模組。建議兩者並存,讓瀏覽器依支援情況協商。
Apache
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css text/plain
AddOutputFilterByType DEFLATE application/javascript application/json
AddOutputFilterByType DEFLATE image/svg+xml
</IfModule>
二、靜態檔案的快取標頭
Nginx
location ~* \.(css|js|jpg|jpeg|png|gif|webp|avif|svg|woff2?)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
搭配檔名雜湊使用時可以設更長。若檔名不變而內容會更新,就不能設太長——這也是為什麼建議更新資源時改檔名。
快取層級的完整說明見快取是什麼。
Apache
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/webp "access plus 30 days"
ExpiresByType text/css "access plus 30 days"
ExpiresByType application/javascript "access plus 30 days"
</IfModule>
三、HTTP/2 與 HTTP/3
HTTP/2 需要加密連線才能啟用,帶來多工與標頭壓縮的效益。
listen 443 ssl; http2 on;
HTTP/3 基於 QUIC,需要額外的模組或較新的版本支援,並開放 UDP 443:
listen 443 quic reuseport; add_header Alt-Svc 'h3=":443"; ma=86400';
啟用前確認防火牆已開放 UDP 443,否則客戶端會嘗試後回退,反而多一次往返。
四、連線與工作行程
Nginx
worker_processes auto; worker_connections 2048; keepalive_timeout 65; keepalive_requests 1000; sendfile on; tcp_nopush on; tcp_nodelay on;
worker_processes auto 會依 CPU 核心數自動設定,多數情況不需要手動調整。
Apache 的 MPM 選擇
建議使用 event MPM 搭配 PHP-FPM,而非傳統的 prefork + mod_php。
<IfModule mpm_event_module>
StartServers 2
MinSpareThreads 25
MaxSpareThreads 75
ThreadsPerChild 25
MaxRequestWorkers 150
MaxConnectionsPerChild 0
</IfModule>
MaxRequestWorkers 要依實際記憶體評估——設太高會在流量尖峰時耗盡記憶體,反而更糟。
五、PHP-FPM 的行程池
pm = dynamic pm.max_children = 20 pm.start_servers = 4 pm.min_spare_servers = 2 pm.max_spare_servers = 8 pm.max_requests = 500
怎麼估 max_children
可用記憶體 ÷ 單一 PHP 行程的平均用量
例如可用 2GB、每個行程約 60MB,則約 30 上下。寧可保守,因為超出記憶體會觸發系統的行程終止機制,比排隊等待更糟。
pm.max_requests 設定行程處理一定次數後重啟,可緩解記憶體洩漏累積。
六、上傳與逾時的相關設定
這幾個值需要在多處保持一致,否則會出現難以理解的錯誤。
| 項目 | Nginx | PHP |
|---|---|---|
| 上傳大小 | client_max_body_size | upload_max_filesizepost_max_size |
| 執行時間 | fastcgi_read_timeout | max_execution_time |
只改 PHP 沒有改 Nginx,上傳大檔會得到 413 錯誤;只改 Nginx 沒改 PHP,則會被 PHP 端擋下。
相關錯誤的判讀見Nginx 與 Apache 的疑難排解。
七、反向代理的快取
對變動不頻繁的頁面,可在 Nginx 層做快取:
fastcgi_cache_path /var/cache/nginx levels=1:2
keys_zone=SITE:100m inactive=60m;
location ~ \.php$ {
fastcgi_cache SITE;
fastcgi_cache_valid 200 10m;
fastcgi_cache_bypass $cookie_session;
fastcgi_no_cache $cookie_session;
}
務必排除登入狀態與個人化頁面——否則可能把 A 使用者的頁面快取後回應給 B,這是嚴重的資料外洩事故。
驗收時必測:用兩個帳號分別登入,確認各自看到正確的資料。
調整後要驗證
nginx -t或apachectl configtest- reload 而非 restart
- 用瀏覽器開發者工具確認回應標頭
- 觀察一段時間的記憶體與回應時間
- 用實際的行動網路測試感受
本文的設定範例以常見的環境為例,實際的路徑、模組名稱與指令可能因作業系統、版本與安裝方式而異。套用前請先在測試環境驗證,並確實備份原設定檔。
先做的三件事
- 隱藏版本資訊
- 關閉目錄瀏覽
- 限制上傳目錄的執行權限
這三項成本極低,卻能擋掉相當比例的自動化探測與攻擊。
一、隱藏版本與伺服器資訊
Nginx
server_tokens off;
Apache
ServerTokens Prod ServerSignature Off
PHP
expose_php = Off
攻擊的第一步通常是探測版本,再去找對應的已知漏洞。隱藏不能防止攻擊,但能減少被自動化工具鎖定的機會。
二、關閉目錄瀏覽
Nginx
autoindex off;
Nginx 預設就是關閉的,但若曾為了除錯開啟,記得關回去。
Apache
Options -Indexes
三、限制上傳目錄執行程式
這是最重要的一項。 能阻止上傳的惡意檔案被當成程式執行。
Nginx
location ^~ /uploads/ {
location ~ \.php$ { deny all; }
}
Apache
<Directory /var/www/example/public/uploads>
php_admin_flag engine off
<FilesMatch "\.(php|phar|phtml)$">
Require all denied
</FilesMatch>
</Directory>
表單上傳的相關防護見表單的防灌水與資安。
四、阻擋敏感檔案
# Nginx
location ~ /\.(?!well-known) { deny all; }
location ~* \.(env|ini|log|bak|sql|sh|conf)$ { deny all; }
location ~ composer\.(json|lock)$ { deny all; }
注意排除 .well-known,否則憑證的自動續期驗證會失敗。
Apache 對應的寫法:
<FilesMatch "^\.|\.(env|ini|log|bak|sql|conf)$">
Require all denied
</FilesMatch>
五、安全性標頭
add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
Nginx 的 add_header 有一個陷阱
子區塊中若出現任何 add_header,會覆蓋掉父區塊的所有 add_header。
也就是說:在 server 層設了標頭,但某個 location 中又加了別的 add_header,那個 location 就會失去 server 層的標頭。
處理方式:把共用的標頭抽成獨立檔案,在每個需要的區塊 include 一次。
另外always 參數很重要——沒有加的話,錯誤回應(例如 404、500)不會帶上這些標頭。
六、內容安全政策
效果最強,但也最容易設錯——設太嚴會讓地圖、影片、分析工具、社群外掛全部失效。
建議的導入方式
# 第一階段:僅回報,不阻擋
add_header Content-Security-Policy-Report-Only
"default-src 'self'; report-uri /csp-report" always;
收集一段時間,確認所有正常來源都已納入後,再改為實際執行。
語法上的注意事項
- 指令名稱後面接空格,不是冒號
- 各指令之間用分號區隔
- 特定關鍵字需要單引號——如
'self'、'none'、'unsafe-inline' - 網域來源不需要引號
更多語法細節可參考我們既有的NGINX 檔頭相關設定。
七、強制加密的宣告
add_header Strict-Transport-Security
"max-age=31536000; includeSubDomains" always;
導入前務必確認:
- 所有子網域都有有效憑證(若加了 includeSubDomains)
- 自動續期確實運作
- 一旦生效,在有效期內無法回退到 http
建議先設較短的 max-age 測試,例如 300 秒,確認一切正常再延長。
完整說明見加密之外的傳輸安全設定。
八、加密協定與套件
ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_stapling on; ssl_stapling_verify on;
建議用線上的伺服器加密檢測工具驗證,並每年複測——評判標準會隨新弱點的發現而調整。
九、限制請求頻率
對登入等敏感路徑加上頻率限制,可有效阻擋自動化嘗試:
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
location = /admin/login.php {
limit_req zone=login burst=5 nodelay;
}
Apache 可使用 mod_ratelimit 或第三方模組。
十、限制管理路徑的來源
location ^~ /admin/ {
allow 203.0.113.0/24;
deny all;
}
在反向代理或 CDN 後方時要注意——直接用 $remote_addr 會取到代理的位址。需先正確設定 real_ip 相關指令。
套用後的檢查
- 語法測試通過
- 用開發者工具確認回應標頭都有出現
- 確認錯誤頁面也帶有安全性標頭(驗證 always 有生效)
- 確認嵌入的地圖、影片、分析工具仍正常
- 確認憑證續期的驗證路徑未被阻擋
整體的防護原則見主機層與存取控制的防護設定。
本文的設定範例以常見的環境為例,實際的路徑、模組名稱與指令可能因作業系統、版本與安裝方式而異。套用前請先在測試環境驗證,並確實備份原設定檔。
先看日誌,不要猜
多數問題的原因會直接寫在日誌裡。常見位置:
# Nginx /var/log/nginx/error.log /var/log/nginx/access.log # Apache /var/log/apache2/error.log # Debian 系 /var/log/httpd/error_log # RHEL 系 # PHP-FPM /var/log/php8.3-fpm.log
即時觀察:
tail -f /var/log/nginx/error.log
vhost 中若有自訂的日誌路徑,要看那一份,不是預設的。
502 Bad Gateway
代表 Nginx 無法與後端(通常是 PHP-FPM)溝通。
依序檢查
- PHP-FPM 有在跑嗎——
systemctl status php8.3-fpm - socket 路徑對嗎——設定中的
fastcgi_pass與 FPM 的listen是否一致 - 權限對嗎——FPM 的
listen.owner、listen.group需與 Nginx 執行身分相容 - 行程池是否耗盡——FPM 日誌若出現 max_children 相關警告,就是不夠用
- PHP 是否崩潰——查看 FPM 日誌中的異常結束訊息
版本升級後最常見的原因是 socket 路徑改變——例如從 php8.2-fpm.sock 變成 php8.3-fpm.sock,但 Nginx 設定沒有同步更新。
504 Gateway Timeout
後端有回應但太慢,超過等待時間。
fastcgi_read_timeout 120s; proxy_read_timeout 120s;
但拉長逾時只是治標。 應該找出為什麼那麼慢:
- 資料庫查詢沒有索引
- 迴圈中重複查詢
- 呼叫外部 API 而對方無回應
- 批次作業放在網頁請求中執行
批次或匯出類的長時間作業,應該改為背景排程處理,而不是靠拉長逾時。
413 Request Entity Too Large
上傳的檔案超過限制。需要多處一起調整:
# Nginx client_max_body_size 64m; # PHP upload_max_filesize = 64M post_max_size = 72M
post_max_size 要大於 upload_max_filesize,因為除了檔案本身還有其他表單欄位。
Apache 端若有 LimitRequestBody 也需一併調整。
403 Forbidden
常見原因:
- 檔案權限或擁有者不正確
- index 檔案不存在且目錄瀏覽關閉——這其實是正常的
- SELinux 或 AppArmor 的限制——RHEL 系特別常見
- 設定中的 deny 規則誤擋
SELinux 的檢查
getenforce ls -Z /var/www/example
若情境標籤不正確,可用 restorecon -Rv 修正。不建議直接關閉 SELinux。
404 但檔案確實存在
- root 或 alias 路徑設錯——注意 alias 與 root 的行為差異
- location 的比對順序——精確比對與前綴比對的優先順序
- try_files 的順序
- 大小寫——Linux 的檔案系統區分大小寫
用 nginx -T 可以輸出完整的實際設定(含所有 include),確認實際生效的內容。
設定改了沒生效
- 有 reload 嗎
- 改的是實際載入的檔案嗎——用
nginx -T或apachectl -S確認 - sites-enabled 的符號連結建立了嗎
- 有沒有被後面的設定覆蓋——Nginx 的 add_header 繼承規則尤其容易踩到
- 瀏覽器快取——用無痕視窗或 curl 確認
curl -I https://example.com
多個 server 區塊時比對錯誤
Nginx 的 server_name 比對順序:精確名稱 → 星號開頭的萬用 → 星號結尾的萬用 → 正規表示式 → default_server。
若請求落到了非預期的 server 區塊,通常是 server_name 沒有涵蓋該網域,因而落到預設的區塊。
建議明確指定一個 default_server 處理未匹配的請求:
server {
listen 443 ssl default_server;
server_name _;
return 444;
}
日誌的判讀技巧
找出最常見的錯誤
awk '{print $9}' access.log | sort | uniq -c | sort -rn | head
找出 404 最多的路徑
awk '$9==404 {print $7}' access.log | sort | uniq -c | sort -rn | head -20
找出請求量異常的來源
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20
最後一項對判斷異常流量很有用——若單一來源的請求量遠高於其他,可能是掃描或攻擊。
入侵徵兆的判斷見怎麼知道網站被入侵了。
日誌的維護
- 確認 logrotate 有正常運作——日誌長期不輪替會佔滿磁碟
- 靜態檔案的存取可關閉記錄——減少日誌量
- 保留期間要考量個資——存取紀錄含連線位址,屬於個人資料的範疇
磁碟被日誌佔滿是實際會發生的事故,而且症狀是網站突然無法運作。建議把磁碟用量納入監控。
排查的通用順序
- 確認範圍——全站還是特定路徑?所有人還是部分?
- 看錯誤日誌——原因通常直接寫在裡面
- 用 curl 排除瀏覽器因素
- 確認服務狀態——Nginx、PHP-FPM、資料庫
- 確認磁碟與記憶體——
df -h、free -m - 比對最近的變更——設定、部署、系統更新
第六項最有效。 突然出現的問題,幾乎都能對應到某個具體的變更。
本文的設定範例以常見的環境為例,實際的路徑、模組名稱與指令可能因作業系統、版本與安裝方式而異。套用前請先在測試環境驗證,並確實備份原設定檔。
準備好讓網站 開始幫你帶生意了嗎?
不論是要做新網站、救舊網站,還是只想先聊聊方向——先諮詢,不用先付錢,我們照實給你建議。

