這是本文件的舊版!


網站取得用戶真實 IP 的方法

  • 目前網站的網路架構愈來愈複雜, 要取得用戶真實的 IP 需要透過特別指定與層層的傳遞才有機會取得, 以下透過一個實驗環境的架構案例進行說明

graph TD %% 1. 定義主要節點 - 移除括號 A[Client - 1.2.3.4] B{Cloudflare CDN} C[Host A - Cloudflared] D[Host B - 192.168.1.100:80 - NPM] E[Backend Service - dokuwiki_dokuwiki:80] R([Response]) F[Docker Swarm Overlay Network - rproxy-net] %% 2. 結構化 - 邏輯分區 subgraph Edge & Host A A -- HTTPS --> B B -- Encrypted Tunnel --> C end subgraph Host B Docker Swarm & NPM %% Host A 到 Host B 的連線 C -- HTTP - LAN 192.168.1.x --> D %% NPM 經由 Overlay Network 到後端 D -- HTTP --> E end subgraph Backend Service E -- dokuwiki-net 接入 --> F end %% 3. 主要流程連線 A --> B B --> C C --> D D --> E E --> R %% 4. 補充細節/網絡連線 D -- 連線使用 rproxy-net --> F F -- 服務名稱解析 --> E

如果沒有特別設定, 因為 Docker Swarm 預設 Port Mode:Ingress 所以在 Backend Service 看到的用戶 IP 就會是 docker networks 的 ingress overlay 的 IP 網段 Exp. 10.0.0.0/24

  • NPM 的 Stack 部署設定, 要特別處理, 只能在特定 Host 上面, 以下是範例
    services:
      nginx-proxy-manager:
        image: jc21/nginx-proxy-manager:latest
        ports:
          - target: 80
            published: 80
            mode: host  # 重要:使用 host mode
          - target: 443
            published: 443
            mode: host
          - target: 81
            published: 81
            mode: host
        volumes:
          - npm-data:/data
          - npm-letsencrypt:/etc/letsencrypt
        networks:
          - rproxy-net
        deploy:
          replicas: 1 # NPM 本身不適合多副本
          placement:
            constraints:
              - node.labels.rproxy == true  # 只部署在有 rproxy label 的節點
  • 因為是 host 模式, 所以需要在特定 Swarm Host 上設定 Label Exp. swarm-100(192.168.1.100)

    docker node update --label-add rproxy=true swarm-100

  • 所以除了這個 IP 之外, 其餘 Swarm Host 的 IP 就無不能連上 NPM 服務
  • 在 Cloudflare → Zero Trust → Networks → Tunnels → Your-cf-tunnel → Published application routes 就只能設定這個 IP Exp. www.ichiayi.com → 192.168.1.100:80 (NPM 服務)
  • Domain Names : www.ichiayi.com
  • Scheme : http
  • Forward Hostname / IP : dokuwiki_dokuwiki (假設 Backend Service 的 Stack Name : dokuwiki , Service Name : dokuwiki)
  • Forward Port : 80
  • Host 都在 192.168.1.0/24 的網段內, 所以 set_real_ip_from 192.168.1.0/24;
    # ========================================
    # 真實 IP 處理
    # ========================================
    
    set_real_ip_from 192.168.1.0/24;
    
    # 使用 Cloudflare 的 CF-Connecting-IP header
    real_ip_header CF-Connecting-IP;
    real_ip_recursive on;
    
    # ========================================
    # Proxy Headers - 傳遞真實 IP 到後端
    # ========================================
    
    # 真實 IP (現在 $remote_addr 已經是真實 IP)
    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 X-Forwarded-Host $host;
    proxy_set_header Host $host;
    
    # 保留原始 Cloudflare headers
    proxy_set_header CF-Connecting-IP $http_cf_connecting_ip;
    proxy_set_header CF-Ray $http_cf_ray;
    proxy_set_header CF-IPCountry $http_cf_ipcountry;
    proxy_set_header CF-Visitor $http_cf_visitor;
  • 此時在 NPM 看存取 www.ichiayi.com 的 log 應該就可看到類似以下的紀錄, 可看到真實 IP 出現在 Client 後面 Exp.

    :
    [03/Oct/2025:11:40:15 +0000] - 200 200 - GET http www.ichiayi.com "/tag/%E4%B8%AD%E5%AD%B8%E5%90%8C%E5%AD%B8?tab_details=history&do=media&tab_files=upload&image=tech%3As_15294475.jpg&ns=tech%2Fmail" [Client 189.1.241.14] [Length 6901] [Gzip -] [Sent-to dokuwiki_dokuwiki] "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" "https://www.ichiayi.com/tag/%E4%B8%AD%E5%AD%B8%E5%90%8C%E5%AD%B8?tab_details=history&do=media&tab_files=upload&image=tech%3As_15294475.jpg&ns=tech%2Fmail"
    :

  • tech/http_realip.1759491666.txt.gz
  • 上一次變更: 2025/10/03 19:41
  • jonathan