---
# System prepended metadata

title: nginx
tags: [Web]

---

# nginx

## References
+ 🎬 [**程序員魚皮 - 光速入門 Nginx**](https://youtu.be/VjJmEf8muow)
+ 🎬 [**程序員魚皮 - 負載均衡**](https://youtu.be/XRvR_3fIqFU)
+ 🎬 [**技術蛋老師 - Nginx 入門必須懂 3 大配置**](https://youtu.be/wnO7xlm-yIo)
+ 🔗 [**nginx - Beginner’s Guide**](https://nginx.org/en/docs/beginners_guide.html)


## Setup
> 創建一個目錄，其路徑設定為環境變數 `NGINX_HOME`，之後網站內容都會在這個目錄
```
nginx
 ├─ conf
 │   └─ nginx.conf
 ├─ html
 │   └─ index.html
 ├─ logs
 └─ temp
     └─ client_body_temp
```


## Commands

| 參數 | 功能 |
| - | - |
| `-h` | 說明 |
| `-v` | 版本號 |
| `-t` | 測試 config 語法正確性 |
| `-T` | 顯示 config 內容並測試語法 |
| `-q` | 安靜模式 |
| `-g "<directives>"` | 全域指令<br><span style="color:grey;">(比如 `"daemon off;"` 是在背景執行)</span> |
| `-s <signal>` | 傳送控制訊號給主進程<br><span style="color:grey;">(🚨 `stop`、`quit`、`reload`、`reopen`)</span> |
| `-p <prefix>` | 指定 nginx 工作目錄<br><span style="color:grey;">(🚨 config 中所有路徑皆相對於此)</span> |
| `-c <file>` | 指定設定檔路徑<br><span style="color:grey;">(🚨 預設 `conf/nginx.conf`)</span> |



## Phase
```
1. 解析 config
2. 接收 request
3. 匹配 server / location
4. rewrite
5. access
6. try_files / content
7. log
```

## Config

```nginx
events {}

http {
    ...
}

stream {
    ...
}
```

### `$1`
+ 說明
  + <mark>若 location 使用 regex math 的話，可以使用 capture grioup</mark>

### `$request_uri` / `$uri` / `$args`
+ 說明
  + `$request_uri`：<mark>請求 URI</mark>
  + `$uri`：<mark>請求路由</mark>
  + `$args`：<mark>請求 query</mark>
+ 範例
  + `$request_uri`：`/api/user?id=10&name=Rogelio`
  + `$uri`：`/api/user`
  + `$args`：`id=10&name=Rogelio`
	
### `listen`
+ 說明
  + <mark>指定監聽的 port，預設為 80</mark>

### `root`
+ 說明
  + <mark>某個 `location` 路由所對應的 file system 目錄</mark>
  + 若 `location` 路由無設定 `root`，會以 `server` 區塊定義的 `root` 作為預設值
+ 範例
  ```nginx
    server {
        listen 80;

        root /data/up1;

        # root: /data/up1
        location / {
        }

        # root: /data/static
        location /images/ {
            root /data/static;
        }
    }
  ```
### `index`
+ 說明
  + <mark>請求路由對應到的是 file system 的目錄時，要回傳哪個檔案</mark>
  + <mark>預設值是 `index.html`</mark>，可自己指定多個
+ 範例
  ```nginx
  server {
      listen 80;

      root /data/www;

      # 若請求 / ，會依序尋找 index.html → index.htm → home.html
      index index.html index.htm home.html;

      location / {
      }
  }
  ```


### `location`
+ 說明
  + <mark>匹配請求路由</mark>
+ 匹配模式
  + prefix match
    + 找出所有與請求路由開頭相符的區塊，記住 <mark>longest prefix 的那個 location</mark>
      ```nginx
      # 👉 請求 /images/icons/logo.png
      location / { ... }
      location /images/ { ... }
      location /images/icons/ { ... }
      # 👉 longest prefix 為 /images/icons/ 
      ```
  + regex match
    + prefix match 結束後，再輪到 regex match
    + <mark>若 regex match 有出現匹配，優先選擇此 location</mark>
    + 若 regex match 無出現匹配，選擇先前 longest prefix 的那個 location

+ 符號
  + `~`：使用 regex、case-sensitive
  + `~*`：使用 regex、case-insensitive

### `try_files`
+ 說明
  + <mark>對已匹配的請求路由，進行各種拼接嘗試，看是否能找出一個檔案回傳</mark>
+ 範例
  ```nginx
  server {
      root /data/www;
      index index.html;

      location / {
          try_files $uri $uri/ =404;
      }
  }
  # 請求路由：/data/www/admin/dashboard

  # 檢查 $uri：/data/www/admin/dashboard
  # (🤔 沒有這個檔案)

  # 檢查 $uri/：/data/www/admin/dashboard/
  # (✅ 這是個目錄，嘗試導向它的 index.html，假設真的有這檔案)

  # fallbak：404 response
  ```

### `proxy_pass`
+ 說明
  + <mark>將請求轉發（反向代理）到其他 URI</mark>
+ 範例
  ```nginx
  # --- 反向代理 ---
  server {
       # API：轉發給後端
      location /api/ {
          proxy_pass http://localhost:8000/;  # 只轉發 API 給 FastAPI
      }
  
      # 圖片：由 Nginx 提供
      location /images/ {
          root /data;  
      }
  
      # 前端頁面：由 Nginx 提供
      location / {
        root /data/www;  
        try_files $uri $uri/ /index.html;
      }
  }
  ```

### `server_name`
+ 說明
  + <mark>匹配 domain name</mark>
+ 範例
  > 假設 server IP 為 203.0.113.10，\
  > example.com 和 rogeliokg.com 的 DNS A record 即為 203.0.113.10。\
  > 雖然 client 都是由同一個 IP 和 port 訪問，
  > 但 nginx 可以藉由 HTTP 的 host header 來自動分流。
  ```nginx
  server {
      listen 80;
      server_name example.com;
      root /data/www/example;
  }

  server {
      listen 80;
      server_name rogeliokg.com;
      root /data/www/rogeliokg;
  }
  ```

### `error_page`
+ 說明
  + 自訂錯誤頁面
+ 範例
  ```nginx
  server {
      listen 80;
      server_name example.com;

      root /data/www;
      index index.html;

      # 自訂 404 頁面
      error_page 404 /404.html;

      location / {
          try_files $uri $uri/ /index.html;
      }
  }
  ```
  ```nginx
  # 可指定多種錯誤
  error_page 403 404 500 502 503 /error.html;
  ```

### `gzip`
+ 說明
  + <mark>傳輸檔案時，壓縮檔案</mark>
+ 範例
  ```nginx
  http {
      # 開啟 gzip 壓縮
      gzip on;
      # 指定哪些 MIME 類型的回應要壓縮
      gzip_types text/plain text/css application/javascript application/json application/xml;
      # 小於 1KB 的內容不壓縮
      gzip_min_length 1024;
      # 壓縮等級 (1-9，越高越慢但壓縮率更好)
      gzip_comp_level 5;
      # 讓代理伺服器可根據是否支援 gzip 快取不同版本
      gzip_vary on;
  }
  ```

### `expires`
+ 說明
  + <mark>瀏覽器快取時間</mark>
+ 範例
  ```nginx
  location ~* \.(js|css)$ {
      expires 30d;
  }
  location ~* \.(png|jpg|jpeg|gif|svg)$ {
      expires 90d;
  }
  ```

### `add-header`
+ 說明
  + <mark>在 response 時，額外加上 header</mark>
+ 範例 
  ```nginx
  server {
    # HTTPS
    listen 443 ssl;

    # 不允許被 <iframe> 嵌入，防止點擊劫持
    add_header X-Frame-Options DENY;

    # 防止 MIME 類型偽造攻擊
    add_header X-Content-Type-Options nosniff;

    # 啟用瀏覽器的基本 XSS 防護
    add_header X-XSS-Protection "1; mode=block";

    # 使用 HTTPS 時強制瀏覽器自動轉向 HTTPS
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  }
  ```

### `return`
+ 說明
  + *ngx_http_rewrite_module redirect*
  + <mark>回傳的 response</mark>
+ 範例
  ```nginx
  location /hello {
      add_header Content-Type text/plain;
      return 200 "Hello Nginx!";
  }
  ```
  + `return <code> <text>`：回傳文字
  + `return <code> <url>`：重導向

### `last`
+ 說明
  + *ngx_http_rewrite_module redirect*
  + <mark>結束 rewrite 階段</mark> (後方 *ngx_http_rewrite_module redirect* 取消執行)
  + <mark>回到 server / location 階段</mark> (重新匹配改寫後的請求路由)

### `break`
+ 說明
  + *ngx_http_rewrite_module redirect*
  + <mark>結束 rewrite 階段</mark> (後方 *ngx_http_rewrite_module redirect* 取消執行)
  + <mark>不會回到 server / location 階段</mark>



### [`rewrite`](https://blog.yuyansoftware.com.tw/2024/01/nginx-rewrite-return-301/)
+ 說明
  + *ngx_http_rewrite_module redirect*
  + <mark>改寫請求路由</mark>
  + `rewrite <regex>  <replacement> <flag>`
+ 範例
  > `/path1` 內容：You are in path3\
  > `/path2` 內容：(`/path4/index.html` 的內容)\
  > `/path3` 內容：You are in path3
  ```nginx
  server {
      location /path1 {
          rewrite /path1 /path3 last;
          return 200 'Hello from path1';
          add_header Content-Type text/plain;
      }

      location /path2 {
          rewrite /path2 /path4 break;
          try_files $uri $uri/;
      }

      location /path3 {
          return 200 'You are in path3';
          add_header Content-Type text/plain;
      }
  }
  ```


### `set`
+ 說明
  + *ngx_http_rewrite_module redirect*
  + <mark>設定變數</mark>


### `if`
+ 說明
  + *ngx_http_rewrite_module redirect*
  + <mark>條件控制</mark>


### `include`
+ 說明
  + <mark>引入外部設定檔</mark> (例如：`mime.types`)


### `default_type`
+ 說明
  + <mark>當無法判斷檔案 MIME 類型時使用的預設值</mark> (例如：`application/octet-stream`)


## Reverse Proxy

> **核心目的**：統一前端請求路徑為 `/api`，解決 CORS 問題，並保持開發與生產環境架構一致。

#### 1. 開發環境 (Development)

**檔案**：`vite.config.ts`
**原理**：利用 Vite Dev Server 轉發請求。

```typescript
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080', // 後端真實位址
        changeOrigin: true,              // 欺騙後端這來自同源
        rewrite: (path) => path.replace(/^\/api/, '') // (選用) 若後端路由無 /api 則移除
      }
    }
  }
})
```

#### 2. 生產環境 (Production)

**檔案**：`nginx.conf`
**原理**：利用 Nginx 轉發請求至後端服務。

```nginx
server {
    listen 80;
    server_name www.app.com;

    # 處理前端 SPA 路由 (防止重新整理 404)
    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
    }

    # 處理 API 轉發
    location /api/ {
        # 注意結尾斜線：若有 /，Nginx 會自動把 /api 去掉才轉給後端
        proxy_pass http://backend-service:8080/; 
        
        # 必要 Header：讓後端知道真實 Domain 與 User IP
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

```