Chuyển đến nội dung chính

第4課:反向代理

本課程介紹 Nginx 中的反向代理——概念、proxy_pass 設定、代理標頭、上游伺服器與健康檢查。 指導如何將 Nginx 設定為 Node.js、Python、PHP 等後端應用程式的反向代理。 包含最佳實踐與疑難排解。

🔒 DevSecOps — 第4課 第4課:反向代理

Nginx 從基礎到進階

第2部分:反向代理與負載均衡

xdev.asia

1. 反向代理的概念

1.1. 什麼是反向代理?

反向代理是位於客戶端與後端伺服器之間的伺服器,負責接收客戶端的請求,將其轉發給後端伺服器,再將回應回傳給客戶端。

差異說明:

正向代理(客戶端側):
Client → Forward Proxy → Internet → Server
(隱藏客戶端)

反向代理(伺服器側): Client → Reverse Proxy → Backend Server (隱藏伺服器)

示意圖:

┌─────────┐         ┌──────────────┐         ┌──────────────┐
│         │         │              │         │              │
│ Client  │────────▶│    Nginx     │────────▶│   Backend    │
│         │         │ Reverse Proxy│         │   Server     │
│         │◀────────│              │◀────────│              │
└─────────┘         └──────────────┘         └──────────────┘

1.2. 為何使用反向代理?

1. 負載均衡:

  • 將流量分散到多個後端伺服器
  • 提升處理能力與可靠性

2. SSL/TLS 終止:

  • Nginx 處理 SSL 加密/解密
  • 後端伺服器無需處理 HTTPS

3. 快取:

  • 快取靜態內容與 API 回應
  • 降低後端伺服器負載

4. 安全性:

  • 隱藏後端伺服器基礎架構
  • 保護層(速率限制、防火牆)
  • 集中式驗證

5. 壓縮:

  • 對回應進行 Gzip 壓縮
  • 減少頻寬使用量

6. 靜態檔案服務:

  • Nginx 直接提供靜態檔案
  • 後端僅處理動態內容

7. 多個後端:

  • 將請求路由到不同應用程式
  • 微服務架構

1.3. 常見應用場景

1. 單頁應用程式(SPA):
/          → React/Vue/Angular app
/api/*     → 後端 API 伺服器

  1. 微服務: /users/* → 使用者服務 /orders/* → 訂單服務 /payments/* → 付款服務

  2. 多個應用程式: site.com → 主網站 blog.site.com → WordPress 部落格 api.site.com → API 伺服器

  3. 舊版 + 新版: /old/* → 舊版 PHP 應用程式 /new/* → 新版 Node.js 應用程式


2. 基本 proxy_pass 設定

2.1. proxy_pass 語法

location /path/ {
proxy_pass http://backend_server;
}

簡單範例:

server {
listen 80;
server_name example.com;

location / { # 將所有請求轉發到後端 proxy_pass http://localhost:3000; } }

2.2. proxy_pass 與 URI

方式1:無尾部斜線

location /api {
proxy_pass http://localhost:3000;
}

請求:/api/users

代理至:http://localhost:3000/api/users

(保留 /api 前綴)

方式2:有尾部斜線

location /api/ {
proxy_pass http://localhost:3000/;
}

請求:/api/users

代理至:http://localhost:3000/users

(移除 /api 前綴)

方式3:指定特定路徑

location /api/ {
proxy_pass http://localhost:3000/v1/;
}

請求:/api/users

代理至:http://localhost:3000/v1/users

(以 /v1 取代 /api)

詳細範例:

server {
listen 80;
server_name example.com;

# 代理根路徑
location / {
    proxy_pass http://localhost:3000;
}

# 代理 API(移除 /api 前綴)
location /api/ {
    proxy_pass http://localhost:4000/;
}

# 代理 admin(保留 /admin 前綴)
location /admin {
    proxy_pass http://localhost:5000;
}

# 精確比對代理
location = /health {
    proxy_pass http://localhost:3000/healthcheck;
}

}

2.3. 多後端代理

server {
listen 80;
server_name example.com;

# 前端 SPA
location / {
    root /var/www/html;
    try_files $uri $uri/ /index.html;
}

# API 後端
location /api/ {
    proxy_pass http://localhost:3000/;
}

# 驗證服務
location /auth/ {
    proxy_pass http://localhost:4000/;
}

# WebSocket 伺服器
location /ws/ {
    proxy_pass http://localhost:5000/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

# 靜態資源(CDN)
location /static/ {
    proxy_pass http://cdn.example.com/;
}

}

2.4. 使用變數的代理

server {
listen 80;
server_name example.com;

# 根據子網域代理
location / {
    proxy_pass http://$http_host$request_uri;
}

# 使用自訂變數的代理
set $backend "localhost:3000";
location /api/ {
    proxy_pass http://$backend/;
}

# 條件代理
location /dynamic/ {
    if ($arg_version = "v2") {
        proxy_pass http://localhost:4000/;
    }
    proxy_pass http://localhost:3000/;
}

}

2.5. 代理逾時設定

location /api/ {
proxy_pass http://localhost:3000/;

# 逾時設定
proxy_connect_timeout 60s;      # 連線到上游的逾時
proxy_send_timeout 60s;         # 發送請求的逾時
proxy_read_timeout 60s;         # 讀取回應的逾時

# 緩衝區設定
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;

}


3. 代理標頭

標頭對於讓後端伺服器了解原始請求資訊非常重要。

3.1. 必要的代理標頭

location / {
proxy_pass http://localhost:3000;

# Host 標頭
proxy_set_header Host $host;

# 客戶端真實 IP
proxy_set_header X-Real-IP $remote_addr;

# 代理鏈
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# 協定(http/https)
proxy_set_header X-Forwarded-Proto $scheme;

# 原始主機
proxy_set_header X-Forwarded-Host $host;

# 連接埠
proxy_set_header X-Forwarded-Port $server_port;

}

3.2. 標頭說明

Host:

proxy_set_header Host $host;

$host = 請求中的網域名稱

範例:example.com

後端收到:Host: example.com

X-Real-IP:

proxy_set_header X-Real-IP $remote_addr;

$remote_addr = 直接連線到 Nginx 的客戶端 IP

範例:192.168.1.100

後端收到:X-Real-IP: 192.168.1.100

X-Forwarded-For:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

將客戶端 IP 附加到現有的 X-Forwarded-For 標頭

範例:

請求1:Client → Nginx → Backend

X-Forwarded-For: 192.168.1.100

請求2:Client → CDN → Nginx → Backend

X-Forwarded-For: 192.168.1.100, 10.0.0.50

$proxy_add_x_forwarded_for 保留代理鏈

X-Forwarded-Proto:

proxy_set_header X-Forwarded-Proto $scheme;

$scheme = http 或 https

後端可知道原始請求是 HTTP 還是 HTTPS

對重新導向邏輯很重要

3.3. 完整標頭設定

http {
# 定義標頭範本
proxy_set_header Host $host;
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 X-Forwarded-Port $server_port;

server {
    listen 80;
    server_name example.com;
    
    location / {
        proxy_pass http://localhost:3000;
        # 繼承 http context 的標頭
    }
}

}

3.4. 自訂標頭

location /api/ {
proxy_pass http://localhost:3000/;

# 標準標頭
proxy_set_header Host $host;
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-Request-ID $request_id;
proxy_set_header X-Server-Name $hostname;
proxy_set_header X-Forwarded-User $remote_user;

# 移除標頭
proxy_set_header Authorization "";  # 移除驗證標頭

# 新增自訂值
proxy_set_header X-API-Version "v1";
proxy_set_header X-Environment "production";

}

3.5. WebSocket 標頭

location /ws/ {
proxy_pass http://localhost:3000/;

# WebSocket 專用標頭
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

# 標準標頭
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# WebSocket 逾時
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;

}

3.6. 保留原始標頭

location / {
proxy_pass http://localhost:3000;

# 傳遞所有原始標頭
proxy_pass_request_headers on;

# 特定標頭
proxy_set_header Accept-Encoding $http_accept_encoding;
proxy_set_header Accept-Language $http_accept_language;
proxy_set_header Cookie $http_cookie;
proxy_set_header Referer $http_referer;
proxy_set_header User-Agent $http_user_agent;

}

3.7. 安全標頭

location / {
proxy_pass http://localhost:3000;

# 標準代理標頭
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# 隱藏 Nginx 版本
proxy_hide_header X-Powered-By;
proxy_hide_header Server;

# 新增安全標頭
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;

}


4. 上游伺服器與負載均衡

4.1. 基本 Upstream 區塊

# 定義 upstream
upstream backend {
server localhost:3000;
server localhost:3001;
server localhost:3002;
}

server { listen 80; server_name example.com;

location / {
    proxy_pass http://backend;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

}

4.2. 負載均衡方法

1. 輪詢(預設):

upstream backend {
# 輪詢:依序分配給每台伺服器
server localhost:3000;
server localhost:3001;
server localhost:3002;
}

請求1 → 3000

請求2 → 3001

請求3 → 3002

請求4 → 3000(循環)

2. 最少連線:

upstream backend {
least_conn;  # 連線數最少的伺服器

server localhost:3000;
server localhost:3001;
server localhost:3002;

}

適合長連線

均勻分散負載

3. IP 雜湊(黏性會話):

upstream backend {
ip_hash;  # 相同客戶端 → 相同伺服器

server localhost:3000;
server localhost:3001;
server localhost:3002;

}

客戶端 192.168.1.100 → 始終路由到伺服器 3000

客戶端 192.168.1.101 → 始終路由到伺服器 3001

適合基於 Session 的應用程式

4. 通用雜湊:

upstream backend {
hash $request_uri consistent;  # 按 URI 雜湊

server localhost:3000;
server localhost:3001;
server localhost:3002;

}

相同 URI → 相同伺服器

適合快取

5. 隨機:

upstream backend {
random;  # 隨機選擇伺服器

server localhost:3000;
server localhost:3001;
server localhost:3002;

}

4.3. 伺服器權重

upstream backend {
# 權重較高的伺服器接收更多請求
server localhost:3000 weight=3;  # 60% 流量
server localhost:3001 weight=1;  # 20% 流量
server localhost:3002 weight=1;  # 20% 流量
}

總權重 = 5

伺服器 3000:3/5 = 60%

伺服器 3001:1/5 = 20%

伺服器 3002:1/5 = 20%

權重使用場景:

upstream backend {
# 正式環境伺服器
server prod1.example.com weight=5;
server prod2.example.com weight=5;

# 金絲雀部署 - 10% 流量
server canary.example.com weight=1;

}

4.4. 備援伺服器

upstream backend {
server localhost:3000;
server localhost:3001;
server localhost:3002 backup;  # 僅在主要伺服器故障時使用
}

3002 只有在 3000 和 3001 都無法使用時才接收流量

4.5. 伺服器參數

upstream backend {
server localhost:3000 weight=5 max_fails=3 fail_timeout=30s;
server localhost:3001 weight=5 max_fails=3 fail_timeout=30s;
server localhost:3002 backup;
server localhost:3003 down;  # 暫時停用
}

參數說明:

weight=N - 權重(預設 1)

max_fails=N - 標記為故障前的失敗次數(預設 1)

fail_timeout=T - 逾時持續時間(預設 10s)

backup - 備援伺服器

down - 暫時停用

4.6. 進階 Upstream 設定

upstream backend {
least_conn;  # 負載均衡方法

# 伺服器設定
server srv1.example.com:8080 weight=3 max_fails=2 fail_timeout=30s;
server srv2.example.com:8080 weight=3 max_fails=2 fail_timeout=30s;
server srv3.example.com:8080 weight=2 max_fails=2 fail_timeout=30s;
server srv4.example.com:8080 backup;

# 保持連線
keepalive 32;  # 保持 32 個閒置連線到上游
keepalive_timeout 60s;
keepalive_requests 100;

}

server { listen 80;

location / {
    proxy_pass http://backend;
    
    # 保持連線所需的 HTTP 版本
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    
    # 標準標頭
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

}

4.7. 多個 Upstream

# API 後端
upstream api_backend {
least_conn;
server api1.example.com:3000;
server api2.example.com:3000;
server api3.example.com:3000;
}

驗證服務

upstream auth_backend { server auth1.example.com:4000; server auth2.example.com:4000; }

WebSocket 服務

upstream websocket_backend { ip_hash; # WebSocket 的黏性會話 server ws1.example.com:5000; server ws2.example.com:5000; }

server { listen 80; server_name example.com;

location /api/ {
    proxy_pass http://api_backend/;
}

location /auth/ {
    proxy_pass http://auth_backend/;
}

location /ws/ {
    proxy_pass http://websocket_backend/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

}


5. 基本健康檢查

5.1. 被動健康檢查

Nginx 根據伺服器的回應自動偵測故障的伺服器。

upstream backend {
server localhost:3000 max_fails=3 fail_timeout=30s;
server localhost:3001 max_fails=3 fail_timeout=30s;
server localhost:3002 max_fails=3 fail_timeout=30s;
}

max_fails=3:連續失敗 3 次後

fail_timeout=30s:伺服器標記為停機 30 秒

30 秒後,Nginx 重試該伺服器

運作方式:

1. 請求發送到 localhost:3000
2. 伺服器返回 502、503、504 或逾時 → 失敗次數 = 1
3. 下一個請求傳到 3000
4. 伺服器再次失敗 → 失敗次數 = 2
5. 下一個請求傳到 3000
6. 伺服器第三次失敗 → 失敗次數 = 3 → 伺服器標記為停機
7. 流量路由到 3001 和 3002
8. 30 秒後,Nginx 重試 3000
9. 若 3000 正常回應 → 失敗次數重置,伺服器上線

5.2. 主動健康檢查(Nginx Plus)

Nginx Plus 支援主動健康檢查(開源版本不支援)。

# 僅 Nginx Plus
upstream backend {
zone backend 64k;
server localhost:3000;
server localhost:3001;
server localhost:3002;
}

server { listen 80;

location / {
    proxy_pass http://backend;
    health_check interval=5s fails=3 passes=2 uri=/health;
}

}

interval=5s:每 5 秒檢查一次

fails=3:失敗 3 次後標記為停機

passes=2:成功 2 次後標記為上線

uri=/health:要檢查的端點

5.3. 自訂健康檢查端點

後端實作(Node.js 範例):

// health.js
const express = require('express');
const app = express();

app.get('/health', (req, res) => { // 檢查資料庫連線 // 檢查相依服務 // 檢查記憶體使用量等

const health = {
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    memory: process.memoryUsage()
};

res.status(200).json(health);

});

app.listen(3000);

Nginx 設定:

upstream backend {
server localhost:3000 max_fails=3 fail_timeout=30s;
server localhost:3001 max_fails=3 fail_timeout=30s;
}

server { listen 80;

location / {
    proxy_pass http://backend;
}

# 健康檢查端點(非公開)
location /health {
    access_log off;
    proxy_pass http://backend;
    
    # 僅允許本機存取
    allow 127.0.0.1;
    deny all;
}

}

5.4. 外部健康檢查

使用外部腳本監控並更新上游設定。

監控腳本:

#!/bin/bash

health_check.sh

UPSTREAM_SERVERS=( "localhost:3000" "localhost:3001" "localhost:3002" )

HEALTH_ENDPOINT="/health"

for server in "${UPSTREAM_SERVERS[@]}"; do response=$(curl -s -o /dev/null -w "%{http_code}" "http://$server$HEALTH_ENDPOINT")

if [ "$response" = "200" ]; then
    echo "$(date) - $server 正常"
else
    echo "$(date) - $server 停機(HTTP $response)"
    # 發送警報
    # 更新 upstream 設定
    # 重新載入 Nginx
fi

done

Crontab:

# 每分鐘執行健康檢查

          • /usr/local/bin/health_check.sh >> /var/log/health_check.log 2>&1

5.5. 使用 Stub Status 監控

server {
listen 8080;
server_name localhost;





location /nginx_status { stub_status; access_log off; allow 127.0.0.1; deny all; } }

查看狀態:

curl http://localhost:8080/nginx_status

輸出:

Active connections: 291

server accepts handled requests

16630948 16630948 31070465

Reading: 6 Writing: 179 Waiting: 106

5.6. 使用腳本進行健康檢查

Python 健康檢查:

#!/usr/bin/env python3

health_monitor.py

import requests import time import smtplib from email.message import EmailMessage

BACKENDS = [ 'http://localhost:3000/health', 'http://localhost:3001/health', 'http://localhost:3002/health', ]

def check_health(url): try: response = requests.get(url, timeout=5) return response.status_code == 200 except: return False

def send_alert(backend, status): msg = EmailMessage() msg['Subject'] = f'後端警報:{backend}' msg['From'] = '[email protected]' msg['To'] = '[email protected]' msg.set_content(f'後端 {backend} 狀態:{status}')

with smtplib.SMTP('localhost') as s:
    s.send_message(msg)

def main(): while True: for backend in BACKENDS: if not check_health(backend): print(f'{backend} 停機') send_alert(backend, '停機') else: print(f'{backend} 正常')

    time.sleep(60)  # 每分鐘檢查一次

if name == 'main': main()


6. 實際應用範例

6.1. Node.js 應用程式

後端(app.js):

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) => { res.json({ message: 'Hello from Node.js', server: localhost:${PORT}, headers: req.headers }); });

app.get('/api/users', (req, res) => { res.json([ { id: 1, name: 'User 1' }, { id: 2, name: 'User 2' } ]); });

app.get('/health', (req, res) => { res.status(200).json({ status: 'ok' }); });

app.listen(PORT, () => { console.log(Server running on port ${PORT}); });

Nginx 設定:

upstream nodejs_backend {
least_conn;
server localhost:3000 max_fails=3 fail_timeout=30s;
server localhost:3001 max_fails=3 fail_timeout=30s;
server localhost:3002 max_fails=3 fail_timeout=30s;
keepalive 32;
}

server { listen 80; server_name api.example.com;

access_log /var/log/nginx/nodejs.access.log;
error_log /var/log/nginx/nodejs.error.log;

location / {
    proxy_pass http://nodejs_backend;
    
    # HTTP 版本
    proxy_http_version 1.1;
    
    # 標頭
    proxy_set_header Host $host;
    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 Connection "";
    
    # 逾時
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;
    
    # 緩衝
    proxy_buffering on;
    proxy_buffer_size 4k;
    proxy_buffers 8 4k;
}

location /health {
    access_log off;
    proxy_pass http://nodejs_backend/health;
}

}

6.2. Python Flask/Django 應用程式

後端(app.py):

from flask import Flask, jsonify, request
import os

app = Flask(name) PORT = int(os.environ.get('PORT', 5000))

@app.route('/') def home(): return jsonify({ 'message': 'Hello from Python', 'server': f'localhost:{PORT}', 'headers': dict(request.headers) })

@app.route('/api/data') def get_data(): return jsonify([ {'id': 1, 'value': 'Data 1'}, {'id': 2, 'value': 'Data 2'} ])

@app.route('/health') def health(): return jsonify({'status': 'ok'}), 200

if name == 'main': app.run(host='0.0.0.0', port=PORT)

Nginx 設定:

upstream python_backend {
server localhost:5000;
server localhost:5001;
server localhost:5002;
}

server { listen 80; server_name python.example.com;

client_max_body_size 10M;

location / {
    proxy_pass http://python_backend;
    
    proxy_set_header Host $host;
    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;
    
    # Python 應用程式可能較慢
    proxy_read_timeout 300s;
    proxy_connect_timeout 300s;
    proxy_send_timeout 300s;
}

}

6.3. PHP 應用程式與 PHP-FPM

Nginx 設定:

upstream php_backend {
server unix:/var/run/php/php8.1-fpm.sock;
# 或
# server localhost:9000;
}

server { listen 80; server_name php.example.com; root /var/www/php; index index.php index.html;

location / {
    try_files $uri $uri/ /index.php?$args;
}

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_pass php_backend;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    
    # 標頭
    fastcgi_param HTTP_X_REAL_IP $remote_addr;
    fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
    fastcgi_param HTTP_X_FORWARDED_PROTO $scheme;
}

location ~ /\.ht {
    deny all;
}

}

6.4. 微服務架構

# 使用者服務
upstream user_service {
server user1.internal:8001;
server user2.internal:8001;
}

訂單服務

upstream order_service { server order1.internal:8002; server order2.internal:8002; }

付款服務

upstream payment_service { server payment1.internal:8003; server payment2.internal:8003; }

產品服務

upstream product_service { server product1.internal:8004; server product2.internal:8004; }

server { listen 80; server_name api.example.com;

# 共用標頭
proxy_set_header Host $host;
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-Request-ID $request_id;

location /api/users/ {
    proxy_pass http://user_service/;
}

location /api/orders/ {
    proxy_pass http://order_service/;
}

location /api/payments/ {
    proxy_pass http://payment_service/;
}

location /api/products/ {
    proxy_pass http://product_service/;
}

}


7. 練習題

練習1:基本反向代理

  1. 建立一個簡單的 Node.js/Python 伺服器,監聽連接埠 3000
  2. 將 Nginx 設定為反向代理
  3. 測試並確認標頭是否正確傳遞

練習2:多個後端

  1. 在連接埠 3000、3001、3002 上各執行一個應用程式實例
  2. 設定採用輪詢的 upstream
  3. 測試負載均衡(查看日誌以確認請求分配情況)

練習3:黏性會話

  1. 設定採用 ip_hash 的 upstream
  2. 測試相同客戶端是否始終連到同一後端
  3. 與輪詢進行比較

練習4:健康檢查

  1. 使用 max_fails 和 fail_timeout 設定被動健康檢查
  2. 停止一個後端伺服器
  3. 確認 Nginx 自動將流量路由到健康的伺服器
  4. 重新啟動伺服器並確認流量恢復正常

練習5:微服務

  1. 建立 2-3 個簡單的 API(可使用模擬)
  2. 設定 Nginx 根據 URL 路徑路由請求:
    • /api/users → 使用者服務
    • /api/products → 產品服務
  3. 測試路由

練習6:WebSocket 代理

  1. 建立一個簡單的 WebSocket 伺服器
  2. 將 Nginx 設定為 WebSocket 代理
  3. 測試連線與訊息傳遞

8. 疑難排解

8.1. 常見問題

1. 502 Bad Gateway:

# 原因:後端未執行或無法連線

檢查後端

curl http://localhost:3000

查看 Nginx 錯誤日誌

sudo tail -f /var/log/nginx/error.log

檢查防火牆

sudo ufw status

2. 504 Gateway Timeout:

# 原因:後端回應時間過長

修正:增加逾時時間

location / { proxy_pass http://backend; proxy_read_timeout 300s; proxy_connect_timeout 300s; }

3. 標頭未傳遞:

# 在後端驗證標頭

記錄請求標頭

Nginx 設定

proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

4. 大型上傳失敗:

# 增加 client_max_body_size
http {
client_max_body_size 100M;
}

5. WebSocket 連線失敗:

# 必須有 Upgrade 標頭
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

8.2. 除錯指令

# 測試上游連線
curl -v http://localhost:3000

檢查 Nginx 設定

sudo nginx -t

重新載入 Nginx

sudo systemctl reload nginx

即時監看錯誤日誌

sudo tail -f /var/log/nginx/error.log

查看上游狀態(需啟用 stub_status)

curl http://localhost/nginx_status

使用特定標頭測試

curl -H "Host: example.com" http://localhost

測試代理標頭

curl -H "X-Forwarded-For: 1.2.3.4" http://localhost


9. 最佳實踐

9.1. 設定

  1. 使用 upstream 區塊:
# 推薦
upstream backend {
server localhost:3000;
}

不推薦(無故障轉移或負載均衡)

location / { proxy_pass http://localhost:3000; }

  1. 設定適當的逾時:
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
  1. 啟用 keepalive:
upstream backend {
server localhost:3000;
keepalive 32;
}

location / { proxy_http_version 1.1; proxy_set_header Connection ""; }

  1. 使用健康檢查:
server localhost:3000 max_fails=3 fail_timeout=30s;

9.2. 安全性

  1. 不暴露內部架構:
proxy_hide_header X-Powered-By;
  1. 限制請求大小:
client_max_body_size 10M;
  1. 速率限制:
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

location /api/ { limit_req zone=api burst=20; }

9.3. 效能

  1. 緩衝區設定:
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
  1. 快取:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m;

location / { proxy_cache my_cache; proxy_cache_valid 200 10m; }


總結

在本課中,您學到了:

  • ✅ 反向代理的概念與使用場景
  • ✅ proxy_pass 設定與路由
  • ✅ 代理標頭與 X-Forwarded 標頭
  • ✅ 上游伺服器與負載均衡
  • ✅ 健康檢查與監控
  • ✅ Node.js、Python 和 PHP 的實際應用範例

下一課:我們將深入探討負載均衡——演算法、策略與進階設定。