> For the complete documentation index, see [llms.txt](https://docs.ipcheck.ing/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ipcheck.ing/developer/zh-tw/configuration/security-options.md).

# 安全選項

保護公開的 MyIP 實例：先從邊緣防火牆開始（以 Cloudflare 為範例），再以應用程式內建的速率限制與 referer 檢查作為最後的安全防線。

MyIP 的後端會代理多個第三方 API，其中有些是付費的。若完全敞開，公開實例就會成為任何找到它的人都能匿名使用的免費代理。

把防護想成兩層，順序如下：

1. **邊緣層（建議的第一層）。** 位於原始伺服器前方的 CDN/WAF——以下以 Cloudflare 為例——會在惡意流量到達你的伺服器之前就將其封鎖，而且提供比任何應用程式都更好的工具：受管理的機器人偵測、彈性的速率限制規則、挑戰，以及對攻擊者 IP 的全球視角。
2. **應用程式本身（安全網）。** 四個環境變數會在後端建立最後一道防線。即使已經有邊緣層，這些也值得設定——當有人找到你原始伺服器的真實位址並完全繞過邊緣層時，它們就是保護你的機制——但它們只是後備措施，不是主要防禦。

## 先在邊緣層防護（建議）

任何具備每個 IP 速率規則的 CDN/WAF 都能以同樣方式運作；以下步驟以 Cloudflare 為例，因為它是最常見的選擇，而且其免費方案已涵蓋必要功能。

{% stepper %}
{% step %}

#### 將你的 DNS 記錄透過 Cloudflare 代理

在 Cloudflare 的 DNS 面板中，保留你的 MyIP 主機名稱所對應的記錄 **已代理** （橘色雲朵），讓所有流量都經由 Cloudflare 的邊緣進入。MyIP 就是為此而設計的：後端已經讀取 `CF-Connecting-IP` 來識別真實的用戶端 IP，而其可快取的 `/api/*` 路由會由邊緣快取提供服務，在流量到達你這裡之前就吸收掉相當大一部分負載。
{% endstep %}

{% step %}

#### 為你的 API 路徑新增速率限制規則 `/api/*`

在 **安全性 → WAF → 速率限制規則**下，建立一條符合你的 API 路徑的規則——例如，表達式 `(http.request.uri.path wildcard "/api/*")` ——，當來源 IP 超過你的門檻時就封鎖它。所有方案都提供速率限制；規則數量與時間視窗選項會依方案而異。由於邊緣快取已經會回應重複查詢，合法訪客通常不需要很高的請求量——請從比你預期更嚴格的設定開始，若真實使用者抱怨再放寬。
{% endstep %}

{% step %}

#### 開啟機器人防護

啟用 **Bot Fight Mode** （安全性 → 機器人）。公開 MyIP 實例最常見的濫用，是對地理位置端點進行腳本化抓取，這正是此功能要針對的情況。若你的方案支援自訂 WAF 規則，對 **受管理挑戰** 套用在非瀏覽器流量上 `/api/*` 是較溫和的替代方案，且絕不會對真實使用者做出硬性封鎖。
{% endstep %}

{% step %}

#### 將原始伺服器鎖住

只有在流量無法繞過邊緣層時，邊緣規則才有用。將原始伺服器的防火牆設定為只接受來自 [Cloudflare 的 IP 範圍](https://www.cloudflare.com/ips/) 的 HTTP(S)；或者使用 Cloudflare Tunnel 讓原始伺服器完全不暴露於公開網際網路。如果原始伺服器能直接回應請求，任何找到其位址的攻擊者都能繞過上述每一條規則；這正是下面這些應用層變數存在的原因。
{% endstep %}
{% endstepper %}

{% hint style="info" %}
由 Cloudflare 快取提供的請求永遠不會到達原始伺服器，因此應用層限流器看不見它們——這也是為什麼容量控制應該放在邊緣層。
{% endhint %}

## 應用程式自己的安全網

四個環境變數會把後備層建立到後端中。預設下，這四個都是可選且關閉（或寬鬆）狀態。

| 變數                                 | 用途               | 預設              |
| ---------------------------------- | ---------------- | --------------- |
| `ALLOWED_DOMAINS`                  | 哪些網站可呼叫 `/api/*` | `localhost` 唯一的 |
| `SECURITY_RATE_LIMIT`              | 每個 IP 的硬性請求上限    | `0` — 已停用       |
| `SECURITY_DELAY_AFTER`             | 每個 IP 的漸進式降速     | `0` — 已停用       |
| `SECURITY_BLACKLIST_LOG_FILE_PATH` | 磁碟上的受限速 IP 台帳    | 空白 — 沒有檔案       |

## `ALLOWED_DOMAINS` — Referer 門檻

每個 `/api/*` 路由會經過一次 Referer 檢查。後端會讀取 `Referer` 標頭，擷取其 **hostname**主機名稱，並要求該主機名稱必須在允許清單中。

允許清單為 `localhost` 加上在 `ALLOWED_DOMAINS`.

拒絕回應如下： `403`:

| 情境                          | 回應                    |
| --------------------------- | --------------------- |
| 無 `Referer` 標頭              | `{"error": "你在做什麼？"}` |
| 已存在 Referer，但主機名稱不允許（或無法解析） | `{"error": "存取遭拒"}`   |

{% hint style="danger" %}
**如果你是在正式網域上提供 MyIP，就必須設定這個。** 搭配 `ALLOWED_DOMAINS` 為空時，只有 `localhost` 會通過——因此透過 `https://ip.example.com` 或 `http://192.168.1.10:18966` 的請求會得到 `403` 可達的部署，在每一次 API 呼叫時都會失敗，而靜態頁面卻能正常載入，看起來就像應用程式壞掉了一樣。
{% endhint %}

需要記住的規則：

* **僅限主機名稱。** 不要有 scheme、port、path： `example.com`，不是 `https://example.com:443/`.
* **必須完全相符。** `example.com` 不包含 `www.example.com`。兩者都要列出。
* **IP 位址也算主機名稱。** 將應用程式開在 `http://192.168.1.10:18966` 表示要加入 `192.168.1.10`.
* **`localhost` 永遠允許**，所以本機開發完全不需要設定。

{% code title=".env" %}

```bash
ALLOWED_DOMAINS="example.com,www.example.com,ip.example.com"
```

{% endcode %}

{% hint style="warning" %}
參照來源檢查是濫用抑制手段，不是驗證。任何用戶端都可以送出任意的 Referer 標頭。 `Referer` 它能阻止隨手的熱連結與順手寫出的腳本；但無法阻止有決心的抓取器。請搭配速率限制一起使用。
{% endhint %}

在反向代理之後，請確保你的代理會原樣轉送 `Referer` 標頭。參見 [反向代理與網域](/developer/zh-tw/getting-started/reverse-proxy-and-domains.md).

## `SECURITY_RATE_LIMIT` — 硬性上限

對 `/api/*`，由 `express-rate-limit`.

* **的每個 IP 請求上限**：20 分鐘，滾動式。
* **限制**：你設定的數值。 `0` 或空白表示限制器根本不會掛載。
* **超過上限**: `429` 搭配 `{"message": "請求過多"}`.
* **豁免路由**: `/api/monitoring`，Sentry tunnel，其本身有自己的限制器——見 [錯誤監控](/developer/zh-tw/configuration/error-monitoring.md).

{% hint style="info" %}
啟動時會印出 `🛡️ 已啟用速率限制器——每 60 分鐘 N 個請求`。程式碼中強制的時間視窗為 **20 分鐘**.
{% endhint %}

### 記錄的內容

後端會在 IP 跨越門檻的那一刻記錄—— **一次**，只在狀態轉變時記錄，而不是在之後每一次被封鎖的請求都記錄。這能避免惡意用戶端持續轟炸受限制端點時把你的日誌灌爆。

```
警告：IP 受速率限制
    ip: "203.0.113.45"
```

當設定了 Sentry 後端 DSN 時，這一行也會同步送到 Sentry。

### 如何判定用戶端 IP

限制器與日誌行會依以下順序解析呼叫者的 IP：

1. `CF-Connecting-IP`
2. 的第一個項目 `X-Forwarded-For`
3. `CF-Connecting-IPv6`
4. Express 看到的 socket 位址

應用程式以 `trust proxy` 設為 `1`執行，表示它只信任一層代理標頭。

{% hint style="warning" %}
如果你的反向代理沒有設定 `X-Forwarded-For`，那麼每個請求看起來都像是來自代理伺服器——一個 IP、共享的額度，而第一個流量大的訪客就會把所有人都擋掉。在啟用限制器之前，請先確認標頭轉送無誤。
{% endhint %}

## `SECURITY_DELAY_AFTER` — 漸進式降速

一個更溫和的搭檔，由 `express-slow-down`支援。它不是拒絕，而是延遲。

* **的每個 IP 請求上限**：60 分鐘，滾動式。
* **免費請求**：你設定的數值。超過之後的請求會被慢慢回應。
* **延遲**: `400 毫秒 × 視窗內的總請求數`.
* `0` 或空白表示中介軟體根本不會掛載。
* 同樣 `/api/monitoring` 的豁免。

{% hint style="warning" %}
延遲是根據 **總** 命中次數計算，而不是根據超額部分——所以一開始就很高，並且上升得很快。使用 `SECURITY_DELAY_AFTER="40"`時，第 41 個請求就已經大約要等 16 秒；使用 `"100"`時，第 101 個請求要等大約 40 秒。請在知道第一個被節流的請求就已經要等很久的前提下選擇數值，並預期超過該等待時間後會出現用戶端逾時。
{% endhint %}

降速與速率限制會疊加。把 `SECURITY_RATE_LIMIT` 視為主要防禦，只有在你想讓腳本化的突發流量被拖慢而不是直接失敗時，才啟用降速。

## `SECURITY_BLACKLIST_LOG_FILE_PATH` — 磁碟上的台帳

選用。設定後，每一次速率限制狀態轉換也會附加寫入一個純文字檔。

{% code title=".env" %}

```bash
SECURITY_BLACKLIST_LOG_FILE_PATH="logs/blacklist-ip.log"
```

{% endcode %}

* 路徑會解析為 **相對於應用程式根目錄**。缺少的目錄會自動建立。
* 每個 IP 一行 CSV： `ip,count,first-seen-timestamp`.
* 時間戳記是主機本機時間，並帶有明確的 UTC 偏移，例如 `2026-07-14 10:23:45 +0800`.
* 對於重複違規者， **計數會遞增，而原始時間戳記保持**不變，因此你可以看出該 IP 最初是何時出現的。

```
203.0.113.45,7,2026-07-14 10:23:45 +0800
198.51.100.9,1,2026-07-15 02:11:07 +0800
```

把它留空不會改變執行效果——警告日誌仍然會觸發。這個檔案是給那些想保留永久記錄的部署使用的，例如要餵給防火牆或 fail2ban 風格的腳本。

{% hint style="info" %}
在 Docker 中，請把台帳寫到掛載的磁碟區。容器可寫入層中的路徑在容器重建時會消失。
{% endhint %}

## 中介軟體順序

在你除錯時值得知道 `403` 或 `429`:

1. HTTP 請求記錄時，如果 `LOG_HTTP=true` ——所以 429 會出現在日誌中
2. 速率限制器（如果已啟用）
3. 降速器（如果已啟用）
4. JSON 主體解析
5. Referer 閘門
6. 路由處理器

因此速率限制發生在 **之前** 之前。帶有錯誤 referer 的請求洪流仍會消耗違規者的額度——這正是預期行為。

另外要注意，位於應用程式前方的 CDN 會提供快取的 `/api/*` 回應，而不會到達原始伺服器，因此這些請求對限制器來說是不可見的。大多數讀取密集型路由都可在邊緣快取。

## 公開實例的建議後備值

即使邊緣已經配置好，也要設定這些值，讓原始伺服器能自行防禦：

{% tabs %}
{% tab title="Node（.env）" %}
{% code title=".env" %}

```bash
ALLOWED_DOMAINS="example.com,www.example.com"
SECURITY_RATE_LIMIT="600"
SECURITY_BLACKLIST_LOG_FILE_PATH="logs/blacklist-ip.log"
```

{% endcode %}
{% endtab %}

{% tab title="Docker" %}

```bash
docker run -d -p 18966:18966 \\
  -e ALLOWED_DOMAINS="example.com,www.example.com" \\
  -e SECURITY_RATE_LIMIT="600" \\
  -e SECURITY_BLACKLIST_LOG_FILE_PATH="logs/blacklist-ip.log" \\
  -v "$(pwd)/logs:/app/logs" \\
  --name myip \\
  jason5ng32/myip:latest
```

{% endtab %}
{% endtabs %}

這些數字只是起點，不是規則。請根據實際情況調整：

* 一次完整的頁面載入會觸發 **很多** `/api/*` 呼叫——IP 來源、連線檢查、DNS 探測。單一訪客每個工作階段很容易消耗數十個請求。
* 把限制設得太低，正常使用者就會在 `429` 診斷到一半時撞上上限。
* 觀察台帳和 `IP 速率受限` 警告一週，然後再收緊。
* 在 CGNAT 或企業 NAT 之後，許多真實使用者會共用同一個 IP。請保留餘裕。

{% hint style="success" %}
用一句話來說這種分層：讓邊緣層吸收並過濾流量，並把這些變數設得足夠寬鬆，使它們只會在漏過邊緣層的流量上觸發。
{% endhint %}

## 相關頁面

* [環境變數](/developer/zh-tw/reference/environment-variables.md) — 完整清單
* [反向代理與網域](/developer/zh-tw/getting-started/reverse-proxy-and-domains.md) — 你的代理伺服器必須轉送的標頭
* [記錄](/developer/zh-tw/configuration/logging.md) — 警告會顯示在哪裡
* [選用 API 金鑰](/developer/zh-tw/configuration/optional-api-keys.md) — 你正在保護的配額


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.ipcheck.ing/developer/zh-tw/configuration/security-options.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
