跳转到帖子
在手机APP中查看

一个更好的浏览方法。了解更多

PHP论坛人

主屏幕上的全屏APP,带有推送通知、徽章等。

在iOS和iPadOS上安装此APP
  1. 在Safari中轻敲分享图标
  2. 滚动菜单并轻敲添加到主屏幕
  3. 轻敲右上角的添加按钮。
在安卓上安装此APP
  1. 轻敲浏览器右上角的三个点菜单 (⋮) 。
  2. 轻敲添加到主屏幕安装APP
  3. 轻敲安装进行确认。

Vhost 域名.com.conf 優化、Invision Community 偽靜態 規則

精选回复

---------------
1. 前置確認
---------------

在修改虛擬主機設定前,請先確認以下條件:


確認 Nginx 版本(應為 nginx/1.30 或以上版本)
nginx -v



確認 PHP-FPM socket 存在
ls -la /var/run/php/php8.3-fpm.sock



確認網站根目錄存在
ls -la /var/www/域名.com/



確認 TLS 憑證已由 acme.sh 安裝(應可看到 域名.com.fullchain.pem、域名.com.key)
ls -la /etc/nginx/ssl/域名.com.*





確認 ACME 驗證目錄存在(應有 .well-known 目錄)
ls -la /var/www/acme-challenge/





若 PHP-FPM socket 不存在,請先確認服務狀態
systemctl status php8.3-fpm --no-pager -l





為何 ACME 驗證目錄要獨立於網站根目錄?

本教學將 ACME HTTP-01 驗證目錄設定為 /var/www/acme-challenge,

而非放在論壇根目錄(如 /var/www/域名.com/.well-known/)之下。理由如下:


論壇程式升級、還原備份、或調整檔案權限時,常會整批覆寫或清空網站根目錄;

若驗證目錄與網站根目錄耦合,可能被意外刪除,導致憑證續簽失敗。


將憑證驗證路徑與應用程式檔案完全切開,符合「關注點分離(separation of concerns)」原則:

憑證生命週期管理屬於基礎設施層,不應與應用程式部署流程互相影響。


即使日後更換論壇程式、搬遷站台目錄、或執行整站還原,ACME 驗證機制仍可獨立正常運作,不需額外調整。













----------------------------
2. 編輯虛擬主機設定檔
----------------------------

編輯
vi /etc/nginx/sites-available/域名.com.conf



將原有內容修改為以下完整版本




# ============================================================
# /etc/nginx/sites-available/域名.com.conf
# Debian 13 + Nginx 1.30(或以上) + PHP 8.3 + Invision Community 5.x
# TLS 憑證由 acme.sh + Google Trust Services 管理
# ============================================================

# HTTP:僅處理 ACME 驗證,其餘強制跳轉 HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name 域名.com;

    # ACME HTTP-01 驗證路徑(acme.sh 續簽必要,請勿移除)
    # 驗證目錄獨立於網站根目錄之外(/var/www/acme-challenge),
    # 避免論壇程式更新、還原備份或權限調整時被誤刪,
    # 降低 Web Root 與憑證驗證目錄的耦合(常見設計原則)。
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/acme-challenge;
        default_type text/plain;
        try_files $uri =404;
    }

    # HTTP 強制跳轉 HTTPS(301 永久重新導向)
    # 使用 $host 而非 $server_name,避免多域名設定時錯誤重導向
    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS Server:主要服務設定
server {
    # listen 指令:是否使用 reuseport 建議視壓力測試結果決定
    # reuseport 讓每個 worker process 各自持有一份 listen socket,由核心層依連線雜湊直接分配給對應 worker,
    # 減少 worker 之間搶奪同一 socket 的鎖競爭(lock contention)。
    # 但在 2 vCPU(通常僅 2 個 worker process)且論壇流量規模不大的環境下,鎖競爭本來就極輕微,reuseport 帶來的效益不明顯;
    # 若日後遷移到多核心、高並發環境,才比較容易量化其效益。
    # 因此本教學預設不啟用,建議先以基本語法上線,
    # 待實際壓力測試(如 wrk、ab)確認有效益後,再自行增加並修改。
    # listen 443 ssl reuseport;
    # listen [::]:443 ssl reuseport;

    listen 443 ssl;
    listen [::]:443 ssl;

    http2 on;
    server_name 域名.com;

    # 隱藏 Nginx 版本號(安全強化)
    # 防止攻擊者透過 Server 標頭得知 Nginx 版本,降低版本指紋洩露風險
    # 注意:server_tokens 亦需在 nginx.conf 的 http {} 區塊中設定
    # 此處設定可覆蓋主設定檔中的值,確保本虛擬主機強制隱藏版本
    server_tokens off;

    # TLS 憑證設定(由 acme.sh --install-cert 安裝)
    ssl_certificate     /etc/nginx/ssl/域名.com.fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/域名.com.key;

    # TLS 協定版本(僅允許 TLS 1.2 與 TLS 1.3)
    ssl_protocols TLSv1.2 TLSv1.3;

    # 橢圓曲線(ECC 金鑰交換優先順序)
    # X25519:效能最佳,現代瀏覽器(Chrome、Firefox、Safari)均優先選用
    # secp384r1(P-384):NIST 高安全等級曲線,備援用途
    # prime256v1(P-256):相容性最廣,適用於較舊的用戶端
    # 2GB RAM VPS 完全足以支援此三曲線組合,不造成額外記憶體壓力
    ssl_ecdh_curve X25519:secp384r1:prime256v1;

    # TLS 1.3 Early Data(0-RTT):關閉
    # 0-RTT 存在重放攻擊(Replay Attack)風險
    # 論壇網站含有登入與 POST 請求,生產環境必須關閉
    ssl_early_data off;

    # 加密套件
    # TLS 1.3 套件由 OpenSSL 自動管理,無需手動指定
    # TLS 1.2 使用 ECDHE + AEAD 安全套件
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;

    # 關閉伺服器端套件優先,讓現代用戶端自行選擇最佳套件
    ssl_prefer_server_ciphers off;

    # SSL Session 快取(減少 TLS 握手開銷,提升效能)
    # shared:SSL:10m 可快取約 80,000 個 session(2 GB RAM 完全沒問題)
    # ssl_session_timeout 1d:session 有效期 1 天
    # ssl_session_tickets off:禁用 Session Ticket,提升前向保密性(Forward Secrecy)避免 ticket key 外洩導致歷史流量被解密
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # TLS 記錄緩衝區大小
    # 預設 16k 對高延遲連線較耗記憶體;
    # 設為 8k 可降低 TTFB(Time To First Byte),減少記憶體消耗,適合以 HTML 回應為主的論壇環境。
    # 若有大量大型靜態檔案傳輸(附件下載、高解析圖片)可評估調回 16k。
    ssl_buffer_size 8k;

    # OCSP Stapling:已停用
    # Google Trust Services 與 Let's Encrypt 均已於 2025 年停止支援 OCSP(Must-Staple 自 2025 年 5 月起失效,OCSP 回應服務亦陸續關閉),
    # 現代憑證驗證已改以 CRL(Certificate Revocation List)為主。
    # 若仍啟用 ssl_stapling,Nginx 啟動時會出現 [warn] "ssl_stapling" ignored, no OCSP responder URL in the certificate 警告。
    ssl_stapling        off;
    ssl_stapling_verify off;

    # 若日後重新啟用 ssl_stapling(改用其他支援 OCSP 的 CA),請將以下三行取消註解:
    # ssl_trusted_certificate /etc/nginx/ssl/域名.com.fullchain.pem;
    # resolver 1.1.1.1 1.0.0.1 8.8.8.8 valid=300s ipv6=off;
    # resolver_timeout 5s;

    # 連線保持時間
    # 預設 75s 在高併發論壇環境下可能佔用過多 worker connection
    # 設為 30s 可加速連線釋放,降低並發下的資源浪費
    keepalive_timeout 30s;

    # 單個 keep-alive 連線允許的最大請求數
    # 預設 1000,對論壇環境已足夠;可依實際流量調整
    keepalive_requests 1000;

    # 連線層級逾時設定(防範 Slowloris 類型慢速連線攻擊)
    # Slowloris 攻擊以極低速率傳送請求標頭或請求主體,刻意讓連線長時間佔用但不完成,逐步耗盡 worker 可用連線數。
    # 在小型VPS上,worker_connections 數量有限,此類攻擊的衝擊會比大型叢集更明顯,因此務必設定逾時上限。
    client_body_timeout   30s;
    client_header_timeout 30s;
    send_timeout           30s;

    # 連線逾時後直接送出 TCP RST(而非優雅關閉)
    # 可立即釋放對應的 socket 與記憶體資源,避免殭屍連線(half-closed connection)
    # 長時間占用 worker 資源,是 Slowloris 類攻擊的重要防線之一。
    reset_timedout_connection on;

    # 允許的最大請求體大小(論壇附件上傳必要)
    # IPS論壇預設附件大小限制請與此值保持一致
    # 若允許更大的附件,需同步調高 PHP upload_max_filesize 與 post_max_size
    client_max_body_size 200m;

    # 日誌設定
    # 路徑須與 Fail2ban jail.local 中的 logpath 完全一致,否則 Fail2ban 無法讀取日誌進行封鎖。
    # Nginx 官方源預設的 log_format 名稱為 combined;
    # 若 nginx.conf 中沒有自訂 main 格式,請使用 combined。
    access_log /var/log/nginx/域名.com.access.log combined;
    error_log  /var/log/nginx/域名.com.error.log warn;

    # 安全性相關 HTTP 標頭
    # 注意:X-XSS-Protection 已淘汰(現代瀏覽器忽略),不加入

    # HSTS:強制瀏覽器以 HTTPS 存取
    # 初始設為 86400(1 天)以便測試,確認 HTTPS 完全正常後
    # 逐步調高至 2592000(30 天)→ 31536000(1 年)
    # 警告:加入 preload 並提交至 HSTS preload list 後,移除需要數月時間,操作失誤將導致網站完全無法以 HTTP 存取
    # 請在完全確認 HTTPS 正常且無回退需求後,再加入 includeSubDomains; preload
    add_header Strict-Transport-Security "max-age=86400" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Permissions-Policy:限制瀏覽器功能存取(依需求調整)
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

    # Content-Security-Policy:請依實際站台需求調整後再啟用
    # IPS論壇使用較多 inline script,啟用前請仔細測試,避免造成功能異常
    # 以下為停用狀態,供日後參考:
    # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self';" always;

    # 網站根目錄(依實際情況調整)
    root  /var/www/域名.com;
    index index.html index.htm index.php default.html default.htm default.php;

    # 主要路由(IPS論壇)
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # favicon.ico / robots.txt:不記錄(減少無意義日誌)
    # 搜尋引擎爬蟲、瀏覽器自動請求此兩個路徑極為頻繁,不記錄可避免污染存取日誌,方便日後分析真實流量。
    location = /favicon.ico {
        access_log    off;
        log_not_found off;
    }

    location = /robots.txt {
        access_log    off;
        log_not_found off;
    }

    # IPS論壇 uploads 附件目錄安全防護
    # uploads 目錄禁止列出檔案
    location /uploads/ {
        autoindex off;
    }

    # uploads 目錄禁止執行 PHP 類型檔案
    location ~* ^/uploads/.*\.(ph(p[0-9]*)?|phtml|phar)$ {
        return 403;
        access_log off;
        log_not_found off;
    }

    # PHP-FPM(FastCGI)
    location ~ \.php$ {
        try_files $uri =404;

        include snippets/fastcgi-php.conf;

        # 請確認 socket 路徑與實際 PHP-FPM 版本一致
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;

        # 隱藏 PHP 版本資訊,降低版本指紋風險
        fastcgi_hide_header X-Powered-By;

        # 自訂錯誤頁面支援:若 PHP-FPM 回傳 4xx/5xx 狀態碼,交由 Nginx 的 error_page 指令處理,方便套用自訂錯誤頁
        # 若尚未設定 error_page,此指令不影響現有行為
        # fastcgi_intercept_errors on;

        # open_basedir:限制 PHP 可存取的目錄
        # 防止 PHP 讀取 /var/www/域名.com/ 與 /tmp/ 以外的檔案
        # 強制開啟 open_basedir 可能造成:第三方外掛、圖片處理異常。
        # 建議先不啟用,確認所有插件正常後再評估開啟。
        # fastcgi_param PHP_ADMIN_VALUE "open_basedir=/var/www/域名.com/:/tmp/:/var/lib/php/sessions/";

        # 用戶端真實 IP 傳遞說明
        # snippets/fastcgi-php.conf(Nginx 官方套件內建)已經會正確設定
        # fastcgi_param REMOTE_ADDR $remote_addr; 這一行,因此「沒有」前置 CDN 或反向代理(如 Cloudflare、HAProxy)時,不需要在此自行覆寫 REMOTE_ADDR。
        # 若日後加入 Cloudflare / CDN / HAProxy 等反向代理層,
        # 才需要額外處理 REMOTE_ADDR(建議改用 ngx_http_realip_module 搭配 set_real_ip_from 與 real_ip_header,而非在此手動覆寫),
        # 否則 PHP 端取得的 REMOTE_ADDR 會是代理伺服器的 IP,而非真實訪客 IP。
        # X-Forwarded-For 標頭目前無前置代理層時意義有限(其值等同於用戶端直連 IP),但保留此設定不會造成負面影響,且可在日後加入代理層時減少改動範圍,故予以保留。
        fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;

        # FastCGI 超時設定(適合論壇附件上傳等耗時請求)
        fastcgi_connect_timeout 60s;
        fastcgi_send_timeout    300s;
        fastcgi_read_timeout    300s;

        # 禁止將 FastCGI 回應暫存至磁碟
        # 設為 0 表示完全在記憶體中處理,避免磁碟 I/O 並防止敏感資料寫入暫存檔
        fastcgi_max_temp_file_size   0;
    }

    # Gzip 壓縮(降低傳輸量,提升載入速度)
    # 注意:gzip_types 中不應包含 image/jpeg、image/png 等已壓縮格式
    # 對已壓縮的二進位檔案再次壓縮不僅無益,反而浪費 CPU
    gzip on;
    gzip_comp_level   5;
    gzip_min_length   256;
    gzip_vary         on;
    gzip_proxied      any;
    gzip_types
        text/plain
        text/css
        text/javascript
        application/javascript
        application/json
        application/xml
        application/rss+xml
        application/atom+xml
        text/xml
        image/svg+xml
        font/woff
        font/woff2
        application/font-woff
        application/font-woff2
        application/x-font-ttf
        image/x-icon;

    # 靜態資源快取
    # 重要:此 location 區塊內含有 add_header,根據 Nginx 的 add_header 繼承規則:
    # 子層只要有任何 add_header 指令,父層(server {})的所有 add_header 均不繼承。
    # 因此必須在此區塊內「完整重新宣告」所有安全性標頭,否則靜態資源回應將遺漏 HSTS 等重要標頭。
    # 日後若新增或修改安全標頭,務必同步更新此區塊。
    # expires 30d + immutable:
    # IPS論壇 對 CSS/JS 等資源檔名已內建版本號(雜湊或查詢字串),
    # 檔案內容變更時檔名也會隨之改變,因此可放心將快取時間延長至 30 天,
    # immutable 可讓瀏覽器在有效期內完全不發送驗證請求,進一步降低伺服器負載。
    location ~* \.(jpg|jpeg|png|bmp|gif|ico|webp|svg|woff|woff2|ttf|css|js)$ {
        expires    30d;
        add_header Cache-Control                 "public, max-age=2592000, immutable" always;
        add_header Strict-Transport-Security     "max-age=86400" always;
        add_header X-Frame-Options               "SAMEORIGIN"    always;
        add_header X-Content-Type-Options        "nosniff"       always;
        add_header Referrer-Policy               "strict-origin-when-cross-origin" always;
        add_header Permissions-Policy            "geolocation=(), microphone=(), camera=()" always;
        access_log off;

        # 靜態資源不存在(機器人探測不存在圖片)不記錄至 error log,
        # 避免大量 404 污染日誌,影響真實錯誤的排查效率。
        log_not_found off;
    }

    # 禁止存取隱藏檔案與目錄(排除 .well-known,避免誤攔截 ACME 驗證)
    # 攔截所有以「.」開頭的路徑,包含:
    # .htaccess、.git、.svn、.env、.DS_Store 等隱藏項目
    # 此規則亦防止 .git 目錄被直接存取造成原始碼洩露
    #
    # (?!well-known) 為負向前瞻(negative lookahead),
    # 排除 /.well-known/ 路徑不被本規則攔截。
    # 雖然 HTTP server {} 區塊中的 ^~ /.well-known/acme-challenge/
    # 在前綴匹配優先權上已足以正確處理 ACME 驗證,
    # 但本規則屬於同一個 HTTPS server {} 區塊內的 regex location,
    # 排除寫法可讓規則本身更具可維護性,避免日後其他人調整規則順序、
    # 或在 HTTPS 區塊新增 .well-known 相關路徑時,被本規則誤擋。
    location ~ /\.(?!well-known) {
        deny all;
        access_log    off;
        log_not_found off;
    }

    # 禁止存取危險副檔名
    # 防止意外洩露設定檔、資料庫備份、私鑰、編輯器暫存檔、壓縮封存檔等敏感檔案。
    # 在原有清單基礎上,再擴充以下幾類常見但容易被忽略的副檔名:
    # sqlite / sqlite3:SQLite 資料庫檔案,常見於開發測試環境誤留於正式環境
    # save / tmp:部分編輯器或腳本產生的暫存 / 備份檔
    # zip / tar / gz / tgz / 7z / rar:常見的整站打包備份檔,
    #   一旦被外部存取下載,等同於洩露完整網站原始碼與資料庫
    location ~* \.(env|sql|sqlite3?|bak|old|orig|save|log|ini|conf|sh|key|pem|swp|dist|tmp|zip|tar|gz|tgz|7z|rar)$ {
        deny all;
        access_log    off;
        log_not_found off;
    }

    # IPS論壇 偽靜態 (獨立檔案,便於維護)
    include /etc/nginx/rewrite/ips.conf;
}






儲存檔案並離開vi編輯器
按 Esc,輸入 :wq,按 Enter












----------------------------------------------------------
3. 重點修正說明
----------------------------------------------------------



----------------------------------------------
listen 443 ssl reuseport; 改為可選項
----------------------------------------------

reuseport 讓每個 Nginx worker process 各自持有一份 listen socket,

由核心層(kernel)依連線雜湊直接分配給對應 worker,減少 worker 之間搶奪同一個 socket 的鎖競爭(lock contention)。


在 2 vCPU 環境下,Nginx 預設 worker 數量通常等於 CPU 核心數(即 2 個 worker),鎖競爭本身就非常輕微,

reuseport 帶來的效益在此規模下難以量化,因此本教學調整為預設不啟用,改採:

listen 443 ssl;
listen [::]:443 ssl;
http2 on;


若經壓力測試確認有收益,可自行修改為:
listen 443 ssl reuseport;
listen [::]:443 ssl reuseport;
http2 on;



待站台流量成長、或遷移到多核心主機後,可透過 wrk、ab 等工具進行壓力測試,

確認 reuseport 確實能降低延遲或提升吞吐量後,再自行啟用。






--------------------------
http2 on; 語法
--------------------------

Nginx 1.25.1 起,http2 改為獨立指令,不可再寫成 listen 443 ssl http2; 的舊語法。

需特別補充:listen 443 ssl http2; 這種舊式合併寫法目前雖然仍可向下相容(不會直接導致啟動失敗),

但已被官方標記為 deprecated(已棄用)。


執行 nginx -t 時會出現如下警告:
nginx: [warn] the "listen ... http2" directive is deprecated, use the "http2" directive instead


新建設定檔不建議再使用此舊語法,以避免日後 Nginx 版本升級時出現棄用警告,

甚至在未來某個主版本中被完全移除而導致設定失效。

本教學已全面採用獨立的 http2 on; 指令,設定檔已是正確且面向未來的寫法。


小提醒:若你未來打算啟用 HTTP/3(QUIC),語法會再加上一行 listen 443 quic reuseport;,

且不需要額外的 http3 on; 指令(HTTP/3 由偵測到 quic 監聽自動啟用)。

HTTP/3 涉及 UDP 連接埠、防火牆規則與 Alt-Svc 標頭等額外設定,超出本教學範圍,故暫不納入,待日後視需求另行撰寫。






-----------------------------------------------------------
REMOTE_ADDR / X-Forwarded-For 傳遞邏輯
-----------------------------------------------------------

snippets/fastcgi-php.conf(Nginx 官方套件內建檔案)本身已包含 fastcgi_param REMOTE_ADDR $remote_addr;,

因此在沒有 Cloudflare、CDN 或 HAProxy 等反向代理層的情況下:


不需要自行覆寫 REMOTE_ADDR,snippets/fastcgi-php.conf 已經會正確傳遞,重複宣告除了讓設定檔變長外沒有實質效益。

HTTP_X_FORWARDED_FOR 可以保留,也可以整段拿掉;保留的理由是日後若加入反向代理層時改動範圍較小,

拿掉的理由是目前完全沒有使用價值。本教學選擇保留並加註說明,方便日後擴充。


若日後確實加入 Cloudflare、CDN 或 HAProxy,正確做法是使用 ngx_http_realip_module(set_real_ip_from + real_ip_header),

讓 Nginx 在連線層級就還原真實用戶端IP,而不是僅在 FastCGI 參數層級手動覆寫,

否則存取日誌($remote_addr)仍會記錄成代理伺服器IP,無法正確配合 Fail2ban 等工具運作。







-----------------------------
危險副檔名清單擴充
-----------------------------

在原清單基礎上,加入以下幾類常見但容易遺漏的副檔名:

location ~* \.(env|sql|sqlite3?|bak|old|orig|save|log|ini|conf|sh|key|pem|swp|dist|tmp|zip|tar|gz|tgz|7z|rar)$ {
    deny all;
    access_log    off;
    log_not_found off;
}


新增項目說明:

sqlite / sqlite3:SQLite 資料庫檔案,部分外掛或快取機制可能採用,一旦外洩等同於直接洩漏資料。

old / save / tmp:常見的人工備份或編輯器暫存命名習慣(如 config.php.old),與 .orig、.bak 風險類似。

zip / tar / gz / tgz / 7z / rar:常見整站或資料庫打包備份檔的副檔名。

管理者經常為求方便,直接將備份檔暫放於 Web Root 下載後忘記刪除,一旦被掃描到即等同於完整原始碼與資料庫外洩,風險等級極高。



---------------------------------
ACME 驗證目錄獨立性
---------------------------------

如第 1 章節已補充說明:ACME 驗證目錄應獨立於網站根目錄(例如 /var/www/acme-challenge),避免論壇程式更新或權限調整時誤刪,

並降低 Web Root 與憑證驗證目錄的耦合。這是生產環境文件常見的設計思維,本教學自始即採用此架構。



--------------------------------------------
連線層級逾時設定(防範 Slowloris)
--------------------------------------------

client_body_timeout   30s;
client_header_timeout 30s;
send_timeout           30s;


可降低 Slowloris 類型慢速連線攻擊耗盡 worker 資源的風險。

這類攻擊以極低速率傳輸請求標頭或主體,刻意延長連線存活時間但不完成請求,

在小型 VPS(worker 數量與可用連線數有限)上影響更為顯著,因此設定逾時上限十分重要。



-----------------------------------------
reset_timedout_connection on;
-----------------------------------------

讓超時連線直接送出 TCP RST(而非進行優雅關閉流程),使作業系統立即釋放對應的 socket 與記憶體資源,

避免殭屍連線(half-closed connection)長時間占用 worker 資源。

此設定與上述逾時參數搭配使用,可進一步強化對慢速連線攻擊的防禦力。



-----------------------------
OCSP Stapling 停用
-----------------------------

Google Trust Services 與 Let's Encrypt 已於 2025 年陸續終止 OCSP 相關服務(Let's Encrypt 的 OCSP Must-Staple 自 2025 年 5 月 7 日起停止簽發),

改以 CRL(Certificate Revocation List)為憑證撤銷查詢主要機制。


若設定檔仍保留 ssl_stapling on;,Nginx 啟動時會出現 [warn] "ssl_stapling" ignored, no OCSP responder URL in the certificate 之類的警告,

且無實際效益。本教學已將其關閉,待日後若更換為仍支援 OCSP 的 CA,再依註解指示復原。





-----------------------------------------
location ~ /\. 排除 .well-known
-----------------------------------------

原先規則 location ~ /\. { deny all; } 會攔截所有以 . 開頭的路徑。

雖然本設定的 ACME 驗證寫在 HTTP(80 port)server 區塊,且使用 ^~ 前綴匹配優先權高於本規則所在的 regex location,理論上不會被攔截;

但採用負向前瞻 (?!well-known) 寫法後,可讓規則本身具備自我說明性(self-documenting),

未來即便調整 location 順序、或將 ACME 驗證路徑搬移至 HTTPS 區塊,也不會誤傷驗證流程,屬於防禦性寫法(defensive configuration)。



-----------------------------------
靜態資源快取延長至 30 天
-----------------------------------

IPS論壇對 CSS/JS 等靜態資源檔名通常已內建版本號或雜湊值,內容更新時檔名也會隨之改變(cache busting),

因此可放心將 expires 由7天延長為30天,並加上 max-age=2592000(30 天的秒數)與 immutable,

讓瀏覽器在有效期內完全略過驗證請求(包含 conditional request),進一步降低伺服器負載。





-------------------------------------------------
fastcgi_intercept_errors on;(保留為註解)
-------------------------------------------------

此指令讓 Nginx 攔截 PHP-FPM 回傳的 4xx/5xx 狀態碼,轉交由 Nginx 的 error_page 指令統一處理。

目前設定尚未定義 error_page,因此本項修正不會改變現有行為,但已預先就位;

待日後規劃自訂 404 / 50x 錯誤頁面時,只需在 server {} 區塊加入:

error_page 404     /404.html;
error_page 500 502 503 504 /50x.html;

即可立即生效,無需再修改 PHP location 區塊。







----------------------------------------------------------------
4. 建立IPS論壇的偽靜態規則
----------------------------------------------------------------

IPS論壇使用友善 URL(Pretty URL / SEO URL),需要 Nginx 的 rewrite 規則配合。


建立目錄
mkdir -p /etc/nginx/rewrite



建立IPS論壇的偽靜態設定
vi /etc/nginx/rewrite/ips.conf




貼上以下內容




# ============================================================
# /etc/nginx/rewrite/ips.conf
# Invision Community 5.x 偽靜態(URL rewrite)規則
# 由 /etc/nginx/sites-available/域名.com.conf 透過 include 載入
# ============================================================

# 這個段落,已在主設定 /etc/nginx/sites-available/域名.com.conf 中宣告:
# 一般頁面路由(IPS 主要 rewrite)
# location / {
#     try_files $uri $uri/ /index.php?$query_string;
#  }


# IPS 分頁路由(/page/ 路徑下含 .php 後綴的友善 URL)
# 此規則處理如 /page/2.html 等帶有副檔名的分頁 URL
location ~* ^(/page/).*\.php$ {
    try_files $uri $uri/ /index.php?$query_string;
}

# IPS REST API 路由
# 若請求的實體檔案不存在,重新導向至 /api/index.php 處理
location /api/ {
    try_files $uri $uri/ /api/index.php?$query_string;
}





儲存檔案並離開vi編輯器
按 Esc,輸入 :wq,按 Enter










---------------------------------
5. 測試語法並重新載入Nginx
---------------------------------

每次修改 Nginx 設定後,務必先測試語法,確認無誤再重新載入,避免設定錯誤導致服務中斷。

nginx -t && systemctl reload nginx






為何要用 && 串接?

若設定有語法錯誤,systemctl reload nginx 會失敗並維持原有 worker 繼續服務,

通常不會中斷既有連線——這是因為 Nginx 的 reload 機制是先以新設定嘗試啟動新的 master 流程,

驗證失敗時會直接放棄該次 reload,舊有的 worker process 不受影響,仍持續處理現有與新進的連線。


但仍養成先執行 nginx -t && systemctl reload nginx 的習慣,理由並非「避免服務中斷」,而是:

避免部署流程因設定錯誤而產生不必要的風險與排查時間。

若不慎連續多次 reload 失敗的設定,可能在日誌中產生大量錯誤訊息,干擾後續問題排查。

養成先驗證再套用的習慣,是維運紀律的一部分,也能與 CI/CD 自動化部署流程的概念一致(先 lint、再 apply)。







----------------------------
6. 部署後服務狀態確認
----------------------------

重新載入後,立即確認兩個服務均正常運行:


確認 Nginx 服務狀態
systemctl status nginx --no-pager -l



確認 PHP-FPM 服務狀態
systemctl status php8.3-fpm --no-pager -l



預期輸出應包含 Active: active (running)




若顯示 failed 或 inactive,請使用以下指令查看詳細日誌:


查看 Nginx 最近 50 筆 systemd 日誌(比直接看 error.log 更完整,可看到啟動失敗原因)
journalctl -u nginx -n 50 --no-pager



查看 PHP-FPM 最近 50 筆 systemd 日誌
journalctl -u php8.3-fpm -n 50 --no-pager




為何使用 journalctl 而非只看 /var/log/nginx/error.log?

Nginx 的 error.log 只記錄服務啟動成功後的應用層錯誤,

若 Nginx 在啟動階段就失敗(如 socket 未找到、憑證路徑錯誤),error.log 不會有任何記錄。

journalctl 直接讀取 systemd journal,可看到完整的啟動流程與錯誤原因。




------------------------------------------------------------------------
7. 驗收檢查
------------------------------------------------------------------------

完成設定後,請逐項確認以下項目。以下指令中的 域名.com 請替換為實際的域名。


---------------------
重要前置提醒
---------------------

本章節大量使用 dig 指令,但Debian 13 預設未安裝 dig。

若直接執行會出現 dig: command not found,請先安裝:

apt update && apt install -y bind9-dnsutils





Debian 13 將 dig、nslookup、host 等工具集中於 bind9-dnsutils 套件中;dnsutils 為相容性 metapackage,

安裝任一個皆可(建議直接安裝 bind9-dnsutils,套件名稱在新版 Debian 中已逐步取代舊名 dnsutils)。






---------------------------
基本功能驗證
---------------------------


1. 驗證 HTTP 是否正確跳轉至 HTTPS(應回傳 301)

curl -I http://域名.com/




2. 驗證 HTTPS 是否正常回應(應回傳 200)

curl -I https://域名.com/




3. 驗證 TLS 憑證資訊(確認 Subject、Issuer、有效期)

openssl s_client -connect 域名.com:443 \
    -servername 域名.com </dev/null 2>/dev/null \
    | openssl x509 -noout -subject -issuer -dates







4. 驗證 HTTP/2 是否已啟用(應看到 HTTP/2 200)

curl -I --http2 https://域名.com/ 2>&1 | grep -i "HTTP/"


此指令常見「無效果」原因:

若輸出結果仍顯示 HTTP/1.1,並不代表 Nginx 端 http2 on; 沒有生效,

很可能是執行 curl 的用戶端版本過舊或編譯時未啟用 HTTP/2 支援(缺少 nghttp2 函式庫),

導致 curl 在 ALPN 協商階段就不會宣告支援 h2,從而永遠 fallback 回 HTTP/1.1 這與伺服器設定完全無關。

請先執行 curl --version | grep -i http2 確認用戶端是否列出 HTTP2 功能







-------------------------
安全標頭驗證
-------------------------

檢查回應標頭是否包含預期的安全標頭

curl -sI https://域名.com/ | grep -iE \
    "(strict-transport|x-frame|x-content-type|referrer-policy|permissions-policy)"




預期輸出應包含

strict-transport-security: max-age=86400

x-frame-options: SAMEORIGIN

x-content-type-options: nosniff

referrer-policy: strict-origin-when-cross-origin

permissions-policy: geolocation=(), microphone=(), camera=()





---------------------------------------
完整安全標頭與 Server 標頭檢查
---------------------------------------

除了上方逐項比對外,建議再執行一次更完整的標頭檢查,並特別留意 Server 標頭:

curl -sI https://域名.com/ \
    | grep -Ei "strict-transport|content-type|frame|referrer|permissions|server"



特別提醒:Server 標頭不應看到類似 Server: nginx/1.30.x 這種包含版本號的輸出,

正確結果應僅顯示:Server: nginx


若仍顯示完整版本號,請回頭確認 server_tokens off; 是否已正確寫在本虛擬主機設定(或全域 http {} 區塊)中,

並確認已執行 nginx -t && systemctl reload nginx 套用設定。






------------------------------
靜態資源安全標頭驗證
------------------------------

驗證靜態資源的安全標頭是否已在靜態資源 location 中重新宣告(Nginx add_header 繼承問題)。

將 /path/to/style.css 替換為論壇實際存在的 CSS 路徑:


curl -sI https://域名.com/path/to/style.css \
    | grep -iE "(strict-transport|cache-control|x-content-type)"



預期輸出應同時包含 cache-control 與 strict-transport-security


此指令常見「無效果」原因:

若沒有看到任何輸出,請先確認填入的路徑在論壇上是否真實存在且副檔名落在 location ~* \.(jpg|jpeg|png|...|css|js)$ 的清單內。

IPS論壇的 CSS/JS 路徑通常帶有雜湊或版本查詢字串(如 /applications/core/interface/css.css?v=xxxx),

建議先用瀏覽器開發者工具的 Network 面板找出一個實際存在的靜態資源完整路徑再測試,

否則 curl 對不存在的路徑會回傳 404,自然不會有 Cache-Control 等標頭。





------------------------
危險路徑封鎖驗證
------------------------

驗證 .env 等危險副檔名是否被封鎖(應回傳 403)

curl -I https://域名.com/.env

curl -I https://域名.com/config.bak

curl -I https://域名.com/index.php.orig

curl -I https://域名.com/config.php.dist

curl -I https://域名.com/backup.tar.gz




驗證隱藏目錄是否被封鎖(應回傳 403)

curl -I https://域名.com/.git/config






驗證 .sql 備份檔是否被封鎖(應回傳 403)

curl -I https://域名.com/backup.sql






補充:

以上測試的路徑本身不需要真實存在 —— 因為攔截規則是在 Nginx 比對到 location 後直接 deny all;,

屬於存取層級的拒絡,會在 PHP 或檔案系統層之前就被擋下並回傳 403,

所以即使檔案不存在也會得到 403 而非 404。

如果測試後看到的是 404 而非 403,請檢查是否因為設定檔尚未 reload,

或者該規則被排序在另一條更早比對到、且行為不同的 location 之前。





-----------------------------------
ACME 驗證路徑未被誤擋驗證
-----------------------------------

確認排除 .well-known 後,ACME 驗證路徑仍可正常存取(建立測試檔後應回傳 200)


mkdir -p /var/www/acme-challenge/.well-known/acme-challenge


echo "acme-test-ok" > /var/www/acme-challenge/.well-known/acme-challenge/test-token



應回傳 200 OK
curl -I http://域名.com/.well-known/acme-challenge/test-token




測試完成後刪除
rm /var/www/acme-challenge/.well-known/acme-challenge/test-token




此指令常見「無效果」原因:

若回傳 403 或連線逾時,最常見的兩個被忽略的原因是:

1. 目錄權限問題:/var/www/acme-challenge 目錄或其上層目錄權限過嚴(例如以 root 建立且權限為 700),

導致 Nginx worker process(預設使用者 www-data)無法讀取,回傳 403。

可用 namei -om /var/www/acme-challenge/.well-known/acme-challenge/test-token 逐層檢查每一層目錄的權限與擁有者。


2. 未先重新載入 Nginx:若你是在第一次建立 vhost 後才測試,請先確認已執行過 nginx -t && systemctl reload nginx,

否則新增的 location ^~ /.well-known/acme-challenge/ 規則尚未生效。


若是連線逾時或完全無回應(而非 403/404),

則需往前回到「DNS 解析驗證」與「雲端安全群組與本機防火牆」章節排查,而非繼續懷疑 Nginx 設定本身。







--------------------------------------
DNS 解析驗證(部署前置確認)
--------------------------------------

部署前建議先確認網域是否已正確解析至本機 VPS,

避免後續 ACME 驗證或服務測試因 DNS 尚未生效而誤判為設定錯誤:


getent ahosts 域名.com


dig +short 域名.com




如前文提醒,dig 指令需先安裝 bind9-dnsutils(apt install -y bind9-dnsutils)才能使用,

這是本指令在全新 Debian 13 環境下最常見的「無效果」(command not found)原因。





getent ahosts 與 dig 的差異:

getent ahosts 透過系統的 NSS(Name Service Switch)機制查詢,

反映的是本機作業系統實際會採用的解析結果,可能受 /etc/hosts、/etc/nsswitch.conf、本機 DNS 快取等因素影響。

若懷疑應用程式(包含 acme.sh)連線目標與預期不符,應優先以此指令確認。


dig 直接對指定(或系統預設)DNS 伺服器發出查詢,繞過本機 NSS 與 /etc/hosts 設定,

反映的是權威 DNS 伺服器的真實回應,較適合用於確認 DNS 紀錄本身是否已正確設定、是否已完成全球生效(propagation)。



兩者結果不一致時,通常代表本機 /etc/hosts 有額外覆寫,或本機 DNS 快取尚未更新,應優先排查這兩個方向。






------------------------
PHP 是否正常運行
------------------------


建立測試檔案(確認後必須立即刪除!)
echo "<?php phpinfo();" > /var/www/域名.com/info.php



使用 curl 確認 PHP 回應(勿在瀏覽器開啟,避免外洩伺服器資訊)
curl -sI https://域名.com/info.php | head -5



確認後立即刪除(此步驟不可省略)
rm /var/www/域名.com/info.php



二次確認已刪除
ls /var/www/域名.com/info.php 2>&1



提醒:info.php 會完整揭露 PHP 版本、編譯選項、環境變數等敏感資訊。

在生產環境中,測試完成後必須立即刪除,不得留存。




------------------------------------
uploads 目錄 PHP 執行封鎖驗證
------------------------------------

建立測試用 PHP 檔案於 uploads 目錄
echo "<?php echo 'blocked'; ?>" > /var/www/域名.com/uploads/test.php



應回傳 403 Forbidden
curl -I https://域名.com/uploads/test.php



同時測試 phtml 副檔名是否也被封鎖(應回傳 403)

echo "<?php echo 'blocked'; ?>" > /var/www/域名.com/uploads/test.phtml


curl -I https://域名.com/uploads/test.phtml



驗證完成後立即清除測試檔案

rm /var/www/域名.com/uploads/test.php

rm /var/www/域名.com/uploads/test.phtml




------------------------------
uploads 目錄列表驗證
------------------------------

驗證 autoindex off 是否生效,存取一個不含 index 檔案的子目錄


應回傳 403 而非目錄檔案列表

curl -I https://域名.com/uploads/






此指令常見「無效果」原因:

若 uploads/ 目錄底下實際存在 index.html 或 index.php(部分論壇程式或外掛會自動產生空的 index 檔案以防止目錄列表,

這是 Apache 時代遺留的習慣),Nginx 會優先回應該 index 檔案而回傳 200,並非代表 autoindex off; 沒有生效。

此時應改用 ls -la /var/www/域名.com/uploads/ 先確認該目錄底下是否已存在 index 檔案,

再決定是否需要刪除以正確測試 autoindex 行為,或者直接以此為防護手段之一(雙重防護並不衝突)。






--------------------------------
TLS 握手後端真實連線驗證
--------------------------------

除了前面以 openssl s_client 搭配 openssl x509 檢查憑證資訊外,

建議再單獨執行一次完整的 TLS 握手測試,確認連線本身(而非僅憑證內容)正常完成,

並可一併觀察實際協商出的 TLS 版本與加密套件:

echo | openssl s_client -connect 域名.com:443 -servername 域名.com 2>/dev/null | grep -E "Protocol|Cipher|Verify return code"



預期應看到類似:
Protocol  : TLSv1.3
Cipher    : TLS_AES_256_GCM_SHA384
Verify return code: 0 (ok)





此指令常見「無效果」原因:

不同 OpenSSL 版本對 s_client 的輸出格式略有差異(例如部分版本會輸出 New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 

而非獨立的 Cipher: 一行),

若 grep -E "Protocol|Cipher|Verify return code" 抓不到任何結果,

請先移除 grep 直接查看完整輸出(拿掉管道後面的部分),

確認實際的關鍵字格式後再調整 grep 模式,而非直接判定憑證或連線異常。





若 Verify return code 非 0 (ok),通常代表憑證鏈不完整、根憑證未受信任,

或 ssl_certificate 指向的檔案缺少中繼憑證(intermediate certificate),需回頭檢查 fullchain 檔案內容是否完整。






-------------------------------------
8. 生產環境 驗收項目(設定稽核)
-------------------------------------


8.1 匯出完整生效設定(含所有 include 展開後內容)


nginx -T > /root/nginx-config-backup.txt



驗證方式
head -50 /root/nginx-config-backup.txt



此指令會將所有 include 的子設定檔(如 ips.conf)展開合併,產出 Nginx 實際載入的完整設定,

便於版本留存與事後稽核,亦可用於與歷史版本比對差異。




8.2 檢查實際載入的 vhost 設定

nginx -T | grep -A20 "server_name 域名.com"







8.3 檢查設定是否符合預期(完整生效設定逐項核對)

僅看片段的 server_name 區塊有時不足以確認所有關鍵指令是否正確生效,建議搭配分頁檢視完整輸出,逐一核對:

nginx -T | less


進入後可使用 / 搜尋以下關鍵字,確認皆已正確載入且數值符合預期:

http2 on;
ssl_protocols
include /etc/nginx/rewrite/ips.conf;



這一步的價值在於:nginx -t 只驗證語法正確性,

並不保證「實際生效的設定」就是你以為已經寫入的內容(例如可能不小心改錯檔案、include 路徑錯誤、或被另一個優先載入的設定檔覆蓋)。

nginx -T | less 看到的才是 Nginx 真正會套用的最終結果,是生產環境變更後不可省略的稽核步驟。






8.4 檢查 PHP-FPM socket 是否正確監聽

ss -xl | grep php8.3-fpm.sock


ss -xl 用於列出本機監聽中的 Unix socket,可確認 php8.3-fpm.sock 確實存在且處於 LISTEN 狀態,是排查 502 Bad Gateway 的第一步。



補充:若你的 PHP-FPM pool 設定改用 TCP(如 listen = 127.0.0.1:9000)而非 Unix socket,此指令自然不會有任何輸出,

這並非錯誤,而是架構選擇不同;請改用 ss -tlnp | grep 9000 對應驗證,

並同步確認 vhost 中的 fastcgi_pass 是否已改為 127.0.0.1:9000 而非 unix:/var/run/php/php8.3-fpm.sock。






8.5 TLS 完整連線驗證

openssl s_client -connect 域名.com:443 \
    -servername 域名.com </dev/null 2>/dev/null \
    | openssl x509 -noout -subject -issuer -dates





8.6 安全標頭與 Server 版本號完整核對

curl -sI https://域名.com/ \
    | grep -Ei "strict-transport|content-type|frame|referrer|permissions|server"



務必確認 Server 標頭僅顯示 Server: nginx,不應出現任何版本號(如 nginx/1.30.x)。

若出現版本號,代表 server_tokens off; 尚未生效或尚未 reload,

須立即修正後重新驗證,避免將版本資訊暴露給外部掃描工具。






----------------------
9. 補充說明
----------------------


9.1 HTTP 安全標頭繼承問題

Nginx 的 add_header 指令有一個常被忽略的繼承陷阱:

當子層 location {} 區塊中存在任何 add_header 指令時,父層 server {} 中的所有 add_header 設定均不會繼承至該子層。

本設定的靜態資源 location 區塊(~* \.(jpg|...)$)因使用了 Cache-Control 標頭,必須在該區塊內完整重新宣告所有安全性標頭。

若日後在 server {} 層新增或修改了安全標頭,務必同步更新靜態資源 location 區塊,否則靜態資源回應將缺少該標頭。




9.2 client_max_body_size 需多處同步

允許 200 MB 的附件上傳,需同步調整三處:

設定位置:Nginx vhost
設定項目:client_max_body_size
建議值:200m


設定位置:PHP-FPM php.ini
設定項目:upload_max_filesize
建議值:200M


設定位置:PHP-FPM php.ini
設定項目:post_max_size
建議值:210M(需大於 upload_max_filesize)


設定位置:IPS 後端
設定項目:附件上傳大小限制
建議值:依需求設定,不超過以上值


post_max_size 必須大於 upload_max_filesize,否則表單提交時資料會被截斷。




9.3 FastCGI 緩衝區說明

本設定加入了以下緩衝區相關指令:

fastcgi_max_temp_file_size 0;



設為 0 表示 Nginx 完全不將 FastCGI 回應暫存至磁碟。

預設情況下,當 PHP 回應大於記憶體緩衝時,Nginx 會將溢出部分寫入 /tmp 暫存檔。

設為 0 強制在記憶體中處理,避免暫存檔洩露敏感資料(如論壇頁面含有用戶資訊),同時降低磁碟 I/O。







9.4 REMOTE_ADDR 不自行覆寫的原因

snippets/fastcgi-php.conf 是 Nginx 官方套件內建的標準片段,

已包含 fastcgi_param REMOTE_ADDR $remote_addr;。

在沒有反向代理層的單機部署架構下,這一行已足夠讓 PHP 端透過 $_SERVER['REMOTE_ADDR'] 取得正確的訪客IP,

本教學不再於 vhost 中重複宣告,使設定檔更精簡,亦降低日後維護時「兩處設定不一致」的風險。





9.5 隱藏檔案封鎖說明

location ~ /\.(?!well-known) {
    deny all;
    access_log    off;
    log_not_found off;
}


此規則攔截所有路徑中包含 /.(dot)的請求,但排除 .well-known,涵蓋:

路徑:.git/
風險:原始碼與 commit 歷史洩露


路徑:.svn/
風險:版本控制資料洩露


路徑:.env
風險:環境變數(資料庫密碼、API Key)洩露


路徑:.htaccess
風險:Apache 設定洩露

路徑:.DS_Store
風險:macOS 目錄結構資訊洩露


常見疏失:從開發機器部署時,不小心將 .git 目錄一起上傳至生產伺服器,此規則可作為最後一道防線。





9.6 危險副檔名擴充說明

location ~* \.(env|sql|sqlite3?|bak|old|orig|save|log|ini|conf|sh|key|pem|swp|dist|tmp|zip|tar|gz|tgz|7z|rar)$ {
    deny all;
    access_log    off;
    log_not_found off;
}



.swp、.orig、.old、.save、.dist、.tmp 等副檔名,主要對應「部署或編輯流程殘留檔案」這類常被忽略的風險;

.env、.sql、.sqlite/.sqlite3 對應「業務資料外洩」風險;

.zip、.tar、.gz、.tgz、.7z、.rar 則對應「整站備份檔外洩」風險。

三者成因不同,但同樣可能造成原始檔案內容、設定結構或完整資料庫外洩,故一併納入封鎖清單。





9.7 連線逾時與 Slowloris 防護說明

client_body_timeout   30s;
client_header_timeout 30s;
send_timeout           30s;
reset_timedout_connection on;



Slowloris 類型攻擊的核心手法是刻意以極慢速率傳送資料(或乾脆不傳送完整資料),

讓伺服器持續等待請求完整送達,藉此用極少的頻寬佔滿伺服器的可用連線數。

在資源有限的小型VPS上,worker 可承載的並發連線數本身就不多,

一旦被此類攻擊鎖定,影響會比大型叢集更快顯現,因此這組逾時設定是基本配備,而非僅是效能優化選項。





------------------------------
10. 常見問題排查
------------------------------

症狀:nginx -t 失敗
可能原因:設定語法錯誤
排查指令:nginx -t 2>&1 查看詳細錯誤


症狀:回傳 502 Bad Gateway
可能原因:PHP-FPM 未啟動或 socket 路徑錯誤
排查指令:systemctl status php8.3-fpm;ss -xl \| grep php8.3-fpm.sock


症狀:回傳 403 但預期 200
可能原因:root 路徑或目錄權限問題
排查指令:ls -la /var/www/域名.com/


症狀:HTTPS 憑證警告
可能原因:憑證路徑錯誤或憑證過期
排查指令:openssl x509 -noout -dates -in /etc/nginx/ssl/域名.com.fullchain.pem


症狀:靜態資源缺少安全標頭
可能原因:忘記在靜態資源 location 重新宣告
排查指令:參閱「HTTP 安全標頭繼承問題」


症狀:上傳附件失敗(413 錯誤)
可能原因:client_max_body_size 或 PHP upload_max_filesize 未同步調整
排查指令:參閱「client_max_body_size 需多處同步」


症狀:Nginx 啟動失敗,error.log 無記錄
可能原因:systemd 層級錯誤
排查指令:journalctl -u nginx -n 50 --no-pager


症狀:.well-known 驗證路徑被擋(續簽失敗)
可能原因:隱藏檔案規則攔截到 ACME 驗證
排查指令:確認規則已改為 location ~ /\.(?!well-known),並執行「ACME 驗證路徑未被誤擋驗證」


症狀:上傳 .orig / .dist 殘留檔可被下載
可能原因:危險副檔名規則未擴充或未 reload
排查指令:確認 regex 已包含完整副檔名清單,並執行 nginx -t && systemctl reload nginx


症狀:http2 on; 設定無效或啟動警告
可能原因:使用了舊版 listen ... http2; 語法
排查指令:確認 Nginx ≥ 1.25.1 並改用獨立 http2 on; 指令


症狀:啟動出現 "ssl_stapling" ignored 警告
可能原因:CA 已停止提供 OCSP 服務,但設定仍啟用 stapling
排查指令:將 ssl_stapling / ssl_stapling_verify 設為 off


症狀:reload 後現有連線疑似中斷
可能原因:誤以為語法錯誤會中斷服務
排查指令:確認實際原因——語法錯誤時 reload 本身會失敗,舊 worker 持續服務;若真的發生中斷,應排查是否為憑證檔案損毀或權限錯誤導致新 worker 無法啟動


症狀:getent ahosts 與 dig 結果不一致
可能原因:本機 /etc/hosts 有額外覆寫,或本機 DNS 快取未更新
排查指令:檢查 /etc/hosts 是否有對應網域的手動設定;清除本機 DNS 快取後重新查詢


症狀:dig 指令找不到(command not found)
可能原因:Debian 13 預設未安裝 DNS 查詢工具
排查指令:apt install -y bind9-dnsutils


症狀:curl -I --http2 測試結果顯示 HTTP/1.1
可能原因:用戶端 curl 版本過舊或未編譯 HTTP/2 支援,與伺服器設定無關
排查指令:curl --version \| grep -i http2;改用瀏覽器開發者工具或線上測試工具驗證


症狀:curl -sI https://域名.com/path/to/style.css 沒有任何輸出
可能原因:測試路徑在論壇上實際不存在,回傳 404
排查指令:先用瀏覽器開發者工具找出真實存在的靜態資源路徑再測試


症狀:curl -I https://域名.com/.env 等測試回傳 404 而非 403
可能原因:設定尚未 reload,或規則順序被其他 location 覆蓋
排查指令:重新執行 nginx -t && systemctl reload nginx,並用 nginx -T \| less 核對規則順序


症狀:curl -sI https://域名.com:443 ... \| grep "Protocol\|Cipher" 沒有任何輸出
可能原因:不同 OpenSSL 版本 s_client 輸出格式不同,grep 關鍵字未命中
排查指令:先移除 grep 查看完整輸出,確認實際格式


症狀:curl -I http://域名.com/.well-known/acme-challenge/test-token 回傳 403 或逾時
可能原因:驗證目錄權限過嚴、Nginx 未 reload,或 DNS/防火牆尚未生效
排查指令:namei -om 逐層檢查目錄權限;確認已 reload;往前排查 DNS 與防火牆


症狀:curl -I https://域名.com/.env 系列測試「整段無效果」(連基本 200/301 測試也不通)
可能原因:DNS 尚未生效,或雲端安全群組/本機防火牆未開放對應連接埠
排查指令:先完成「DNS 解析驗證」與防火牆雙重確認,再回頭測試 vhost 規則





---------------------------------------
11. 常見疏漏補充
---------------------------------------

以下整理本次稽核過程中,無論是初次部署的新手,或是已有經驗的管理者都容易遺漏的細節,作為額外提醒:



證書檔案權限。私鑰檔案(*.key)權限務必設為 600,且所有者應為 root 或執行 Nginx master process 的使用者,

避免一般使用者帳號可讀取私鑰內容。可用 stat -c "%a %U:%G" /etc/nginx/ssl/域名.com.key 快速確認。



acme.sh 自動續簽與 Nginx reload 的串接。若 acme.sh 的 --reloadcmd 寫的是相對路徑指令(如僅寫 systemctl reload nginx 而未確認執行環境的 PATH),

在 cron 環境下可能因環境變數不同而執行失敗,建議於 --reloadcmd 中使用絕對路徑(如 /usr/bin/systemctl reload nginx),

並定期檢查 acme.sh 的續簽日誌,而非假設續簽必定成功。



多個 server 區塊共用 80 連接埠時的 default_server 設定。

若同一台機器上有多個網站,務必確認只有一個 vhost 設定了 default_server,

否則 Nginx 啟動時雖不一定報錯,但可能造成非預期網站被當作預設站台回應,影響憑證驗證或安全性掃描結果。



/etc/nginx/sites-enabled/ 的軟連結是否確實建立。新手常見疏失是僅在 sites-available 建立設定檔,

卻忘記以 ln -s 建立到 sites-enabled 的軟連結,導致設定檔語法正確、nginx -t 也通過,

但實際上該 vhost 從未被載入。可用 nginx -T | grep "server_name 域名.com" 確認是否真的生效。



日誌檔案的 logrotate 設定。隨著論壇流量增加,access.log 與 error.log 若未設定 logrotate,

長期會占滿磁碟空間(在 2 GB RAM / 有限磁碟空間的 VPS 上更需留意),建議確認 /etc/logrotate.d/nginx 是否已涵蓋自訂路徑的日誌檔案。



忘記同步調整 PHP-FPM pool 的資源限制。

即使 Nginx 端的 client_max_body_size 與 PHP upload_max_filesize / post_max_size 都已調整,

仍須留意 PHP-FPM pool 設定(如 pm.max_children)是否能承受大型檔案上傳時較長的請求佔用時間,

在 2 GB RAM 環境下,pm.max_children 設定過高可能導致記憶體耗盡(OOM),

建議搭配 free -h 與 systemctl status php8.3-fpm 觀察實際記憶體使用狀況後再微調。



忽略 IPv6 的對應測試。

設定檔中已加入 listen [::]:443 ssl; 等 IPv6 監聽指令,

但許多管理者只用 IPv4 進行驗收測試,從未確認 IPv6 連線路徑是否正常。

若VPS服務商已配置 IPv6,建議額外執行 curl -6 -I https://域名.com/ 進行驗證,避免 IPv6 使用者連線異常卻未被察覺。



HSTS max-age 逐步升級的執行紀律。

教學中提到應從 86400(1 天)逐步調高至 31536000(1 年),但實務上很容易設定後便忘記回頭調整。

建議在維運排程或工單系統中建立明確的提醒事項,避免長期停留在過短的 max-age,削弱 HSTS 應有的保護效果;

但也切記在加入 preload 前要有充分把握,因為一旦提交至瀏覽器內建的 HSTS preload list,撤回需要數月時間。



忽略本機防火牆與雲端安全群組的雙重設定。

Nginx 設定再正確,若雲端服務商的安全群組(Security Group)或本機 nftables 規則未開放 443/80 連接埠,外部仍無法連線。

驗收時應同時確認雲端控制台的網路規則與本機防火牆規則,而非僅檢查 Nginx 本身。

本帖最后于,由Jack编辑

参与讨论

你可立刻发布并稍后注册。 如果你有帐户,立刻登录发布帖子。

游客
抱歉,你的帖子内容包括我们不允许的字词。请编辑你的帖子,删除下面高亮的屏蔽字。
回帖…

帐户

导航

搜索

搜索

配置浏览器推送通知

Chrome (安卓)
  1. 轻敲地址栏旁的锁形图标。
  2. 轻敲权限 → 通知。
  3. 调整你的偏好。
Chrome (台式电脑)
  1. 点击地址栏中的挂锁图标。
  2. 选择网站设置。
  3. 找到通知选项并调整你的偏好。