小試 Cloudflare 反向伺服器與取得真實來源 IP
| | | 0 | |
我習慣在 Linux 用 Nginx + Certbot 容器 跑個人用的對外網站,TLS 憑證靠自動排程從 Let's Encrypt 定期更新,由於已整理好 SOP 甚至有自動化腳本,幾分鐘便能搞定,差不多可做到 Fire and Forget,故成為我最常用的對外網站架構。
對於部落格之類的簡單應用,其實還有一個好選擇,Cloundflare 的免費方案包含反向伺服器 (Reverse Proxy,以下簡稱 RP) 服務,用來串接對外網站有一堆好處:
- 省去自行管理 TLS 憑證的麻煩
- 對外可隱藏真實伺服器 IP,外部使用者只會看到 Cloudflare IP,隱藏真實 IP 可降低被鎖定攻擊的機率
- 靜態檔案無痛升級 Cloudflare CDN 快取,可提升網頁效能
- 提供基本的惡意程式攻擊防禦,以 Cloudflare 見識過的攻擊種類與數量,專業度不容懷疑下 (網站防火牆(WAF)進階功能則需付費)
這麼多東西是可以免費用的嗎?YES! 你要認為這是養套殺也無妨,Cloudflare 有自己的算盤,靠這些免費服務可蒐集大量真實網路流量及攻擊行為資料、吸引人才、方便新功能的全民公測、擴大經濟規模有效利用資源... 等,至少以目前 Cloudflare 的規模與獲利,看來是成功的策略,我們就放心蹭吧~
若想使用 Cloudflare RP,第一步是在 Cloudflare 管理介面加入你的網域並將 DNS 伺服器指向 Cloudflare 的 DNS 伺服器,Cloudflare 到 Origin Servrer (你的真實網站伺服器) 間的傳輸,可選擇不加密走 HTTP,或是裝 Cloudfare 簽發的內部憑證(效期 15 年,支援萬用字元,可全網域共用),介面還算簡單直覺,相關設定則可參考舊文體驗 Cloudflare CDN 與 HTTPS 傻瓜設定,網路上也有很多教學,這裡不再贅述。

前陣子部落格常被惡意機器人掃瞄(類似這種,搞到有點煩,決定搬進 Cloudflare RP 的保護傘,看能不能清靜一點。
安全規則我都先用預設值,有個 Bot Fight Mode 預設沒開啟,該不該啟用讓我有點猶豫,查了資料,它放了一段 JavaScript 挑戰識別並阻擋機器人(對已知 Bot 包括 Google、Bing、Facebook 有設白名單,減少對 SEO 影響),不過也可能誤擋 AI 主導的爬網行為,算是雙面刃,這也是它預設關閉的理由吧! 我決定先放著,看看社群心得再決定是否啟用。
另外,Cloudflare 預設會擋各大 AI 廠商的爬網機器人,防止資料被無償拿去訓練,維護著作者權益 vs 開放知識交流推動進步 各有支持者,我偏向後者,選擇允許 AI Crawler 來抓資料,如此封鎖行為可專注於惡意程式,從統計資料更容易觀測到攻擊行為。

上線後我遇到一個問題,我原本的 Nginx + ASP.NET Core 設定,串接 Cloudflare RP 後會抓不到真實來源 IP。先用以下程式觀察:
using System.Text;
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", (HttpContext context) =>
{
var sb = new StringBuilder();
foreach (var header in context.Request.Headers)
{
if (header.Key.StartsWith("X-Forwarded-", StringComparison.OrdinalIgnoreCase) ||
header.Key.StartsWith("CF-", StringComparison.OrdinalIgnoreCase))
{
sb.AppendLine($"{header.Key}: {header.Value}");
}
}
sb.AppendLine($"Remote IP: {context.Connection.RemoteIpAddress}");
return sb.ToString();
});
app.Run();
可發現從 Cloudflare 轉發的請求量,X-Forwarded-For 會有兩個 IP,第一個是客戶端真實 IP,第二個是 Cloudflare RP 伺服器 IP,而 Cloudflare 則多附了一個 cf-connecting-ip 標示真實來源 IP:

ASP.NET Core 串在 RP 後方想抓取真實來源 IP 需要一點技巧,參考舊文 ASP.NET Core Docker 筆記 4 - ASP.NET Core 網站容器化經驗分享 提到的 ForwardedHeadersOptions 設定方式,再配合 Cloudflare 指定 Header 名稱 cf-connecting-ip,便能成功抓到真實來源 IP。
// .NET 9-:System.Net.IPNetwork
// .NET 10:Microsoft.AspNetCore.HttpOverrides.IPNetwork
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
// 指定來自 Cloudflare 的轉送來源 IP Header
options.ForwardedForHeaderName = "cf-connecting-ip";
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});

Good,搞定收工... 了嗎?並沒有!!
這段程式有個問題,Header 是可以被捏造的,Request 傳什麼就信什麼是嚴重資安漏洞! 因此嚴謹做法該用白名單表列 Cloudflare Proxy 源網段,唯有來自這些 IP 的請求,其 Forwarded 相關 Header 才可被信任。理論上 ASP.NET Core 端可改寫強化如下:
// Cloudflare IPs from https://www.cloudflare.com/ips-v4/#
var cloudflareIPs =
"""
173.245.48.0/20
103.21.244.0/22
103.22.200.0/22
103.31.4.0/22
141.101.64.0/18
108.162.192.0/18
190.93.240.0/20
188.114.96.0/20
197.234.240.0/22
198.41.128.0/17
162.158.0.0/15
104.16.0.0/13
104.24.0.0/14
172.64.0.0/13
131.0.72.0/22
"""
.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
// .NET 9-:System.Net.IPNetwork
// .NET 10:Microsoft.AspNetCore.HttpOverrides.IPNetwork
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.ForwardedForHeaderName = "cf-connecting-ip";
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
foreach (var ip in cloudflareIPs)
{
if (System.Net.IPNetwork.TryParse(ip, out var network))
{
options.KnownNetworks.Add(network);
}
}
});
但在本案例這招並不管用,理由是 Cloudflare RP 拋來的請求會先經過 Nginx 再到 ASP.NET Core,ASP.NET Core 看到的連線來源 IP 已是 Nginx 的 IP。因此,限定來源網段要從 Nginx 設定下手,Nginx 有個內建 ngx_http_realip_module 模組,可指定來源白名單、Header 名稱,讓 $remote_addr 直接對應 Cloudflare 由 cf-conecting-ip 傳遞的真實 IP,完美解決問題。
server {
listen 443 ssl http2;
server_name preview.darkthread.net;
ssl_certificate /etc/cloudflare/darkthread.net/cert.pem;
ssl_certificate_key /etc/cloudflare/darkthread.net/privkey.pem;
# Trusted Cloudflare IP range
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
# ForwardedFor header name
real_ip_header cf-connecting-ip;
location / {
proxy_pass http://localhost:5001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $remote_addr; # $remote_addr 取自 cf-connecting-ip
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 10M;
}
}
Cloudflare 反向伺服器與 Nginx 設定經驗 +1。
Describes migrating a personal blog behind Cloudflare’s free reverse proxy for security and TLS simplicity, then solving real client IP detection by combining Cloudflare headers, Nginx realip configuration, and ASP.NET Core forwarded headers safely.
Comments
Be the first to post a comment