將WordPress 使用的CDN 從CloudFlare 切換到AWS CloudFront
AKA “如何白嫖貴死人的AWS”
CloudFlare 這間公司是個大善人,多年前剛起步的時候我不由得感嘆我靠!為什麼會有人做這種事情,在Twitter 上給CEO 發消息感激他的貢獻,當時他還回我了。
後來順著internet https 的流行,率先推出不要錢的SSL/TLS offloading 服務,引起一大波網站從其他CDN 遷移,因為其他的CDN 光是證書就要收上一大筆錢。
再後來他的WAF 規則對阻止wordpress 的spam comments 效果很好,所以就一直用他。
但,他從中國訪問的效果一向不佳,早年和百度合作的時候,曾經有一段時間很順暢但非常短暫,他的香港節點並不直接提供給中國用戶使用,中國用戶直接訪問會被遞送到美國的節點,如果網站啟用了CloudFlare 的收費服務Argo Smart Routing,中國用戶才會被遞送到香港節點。是不是聽起來和AWS Global Accelerator很像?
之所以想要換成AWS CloudFront ,是因為經過這些年行業廝殺,也有了0$ 的方案,主要特徵如下:
3個Free Plan (官方網站沒說)
5GB S3 存儲用於靜態文件。
每月1M 請求量。
每月100GB 數據傳輸量。
5 條cache behavior rules (官方網站上說的是5條WAF rules,沒有講cache behavior rules)。
好了看這張圖吧,吧啦吧啦一大堆,有用的沒幾個:

簡單來說,每天的流量小於3.3G,請求量小於33333,就可以考慮使用0$ 方案的CloudFront。
之所以想要切換是因為CloudFront 雖然也會時不時因為別的網站連帶被GFW 屏蔽掉,但比起來,CloudFlare 看起來是無差別在中國境內被限速,有時候在中國境內,CloudFront 還是蠻快的,畢竟浙江那麼多做海外電商的中小企業,每天都在抱怨錄入一個商品清單都要花兩個小時。
首先為我的源站更換TLS 證書,因為CloudFront 只接受使用合法證書的源站,不接受自簽發證書。CloudFlare 雖然是支持無法驗證的自簽發證書,但是他也支持使用client/server 證書雙向驗證這種更安全的機制確保源站不會被直接訪問。
免費的合法證書,騰訊雲以曾經提供過一段時間,不知道現在還有沒有,不過現在流行的是letsencrypt 可以免費續期,90天效期的合法數字證書。
安裝certbot和nginx plugin:
dnf install certbot python3-certbot-nginx
進行證書簽發,此時需要確保站點是可以訪問的,因為letsencrypt 會通過訪問站點完成驗證,建議先將網站域名直接解析至IP地址。
certbot --nginx -d bbken.org -d www.bbken.org
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Renewing an existing certificate for bbken.org and www.bbken.org
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/bbken.org/fullchain.pem
Key is saved at: /etc/letsencrypt/live/bbken.org/privkey.pem
This certificate expires on 2026-02-27.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
Deploying certificate
Successfully deployed certificate for bbken.org to /etc/nginx/vhosts/bbken.org.conf
Successfully deployed certificate for www.bbken.org to /etc/nginx/vhosts/bbken.org.conf
Your existing certificate has been successfully renewed, and the new certificate has been installed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
* Donating to EFF: https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
雖然他說已經setup a scheduled task 但是有時候並沒有,如果驗證後沒有,那麼需要自己創建一個定時器:
創建定時器:
sudo tee /etc/systemd/system/certbot-renew.timer << 'EOF'
[Unit]
Description=Certbot Renewal Timer
[Timer]
OnCalendar=daily
RandomizedDelaySec=12h
Persistent=true
[Install]
WantedBy=timers.target
EOF
創建服務:
sudo tee /etc/systemd/system/certbot-renew.service << 'EOF'
[Unit]
Description=Certbot Renewal
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
EOF
啟用服務:
sudo systemctl daemon-reload
sudo systemctl enable --now certbot-renew.timer
驗證服務:
sudo systemctl status certbot-renew.timer
sudo systemctl list-timers | grep certbot
到這裡,證書就會每天檢查,一旦需要更新就會自動更新,那就等90天後再看看吧,反正現在看也看不出什麼來。
源站配置好之後,接下來需要配置CloudFront,添加一個新的distribution,
Create distribution ,輸入一個名字,例如CF-bbken.org,隨便輸入什麼貓貓狗狗都可以。

選擇源站類型,靜態網站也可以使用Amazon S3,比如做圖片網站,相冊,使用hugo 做blog,純靜態html 發布,這裡我使用Other,然後在下方輸入源站域名,因為源站不支持IP地址,所以你必須使用一個二級域名指向IP地址,例如source.bbken.org。

頁面拉到下方,選擇Customize origin settings,創建一個custom header,因為,失去了CloudFlare 的client/server 雙向數字證書驗證保護源站,要如何來保護CloudFront 後面的源站呢?
通過設置源站的 custom header,CloudFront 在向源站發送的每一個請求都會附加這個header,源站將通過這個header 識別來自於CloudFront 的請求並拒絕其他請求,從而保護源站不受到攻擊,這裡我使用如下的配置:
header name = X-Origin-Verify
value = 5Pb9Qsf2XbZK23C

與此同時,在nginx 的host 配置文件中,加入下面的配置,該配置將拒絕所有不帶有這個header 的請求從而保護源站:
###bbken.org at 443####
server {
server_name bbken.org www.bbken.org;
listen 443 ssl;
listen [::]:443 ssl;
# Block all requests without CloudFront header
if ($http_x_origin_verify != "5Pb9Qsf2XbZK23C") {
return 403;
}
同時,為了保護wordpress 的login page 和xmlrpc api,可以加入下面的配置,例如我可以只允許我的IP地址31.13.87.36 訪問wp-login.php,並限制只有來自於日本的IP地址可以訪問到xmlrpc api:
location = /wp-login.php {
allow 31.13.87.36; # Your IP address
deny all;
}
include fastcgi-ssl.conf;
fastcgi_pass unix:/home/www/php.sock;
}
location = /xmlrpc.php {
if ($http_cloudfront_viewer_country != "JP") {
return 403;
}
但用戶的請求中並不會帶有country code 呢,這個country code 從何而來?這就是下一步的cache settings 中需要修改的部分:

將cache policy 設置為CachingOptimozed,將Origin request policy 設置為AllViewerAndCloudFrontHeaders-2022-06。
這裡配置的是default 路徑下所有文件的cache policy,在源站請求策略中,需要為源站送去所有來自於用戶的headers 並且加上CloudFront 加上額外的headers, 其中就包括了根據Maxmind GeoIP database 分析用戶IP地址所得出的 country code。
你可以仔細閱讀官方用戶指南中,CloudFront 加上了哪些headers。
也可以寫一個頁面來獲取他,例如headers.php
<?php
header('Content-Type: text/plain');
print_r(getallheaders());
?>
他長這個樣子,比如我在日本,訪問的時候甚至可以精確到城市Osaka,當然,Maxmind 的GeoIP city 免費資料庫出錯的機率有時候還是很大的,我想以AWS 很摳的風格來說,應該不會去買收費的資料庫。
Array
(
[Cloudfront-Viewer-Latitude] => 34.84230
[Cloudfront-Viewer-Time-Zone] => Asia/Tokyo
[Cloudfront-Viewer-Postal-Code] => 562-0021
[Cloudfront-Viewer-City] => Osaka
[Cloudfront-Viewer-Country-Region-Name] => Osaka
[Cloudfront-Viewer-Country-Region] => 27
[Cloudfront-Viewer-Country-Name] => Japan
[Cloudfront-Viewer-Country] => JP
[Cloudfront-Viewer-Http-Version] => 3.0
[Priority] => u=0, i
[Sec-Fetch-Dest] => document
[Sec-Fetch-User] => ?1
[Sec-Fetch-Mode] => navigate
[Sec-Fetch-Site] => none
[Upgrade-Insecure-Requests] => 1
[Sec-Ch-Ua-Platform] => "macOS"
[Sec-Ch-Ua-Mobile] => ?0
[Sec-Ch-Ua] => "Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"
[X-Amz-Cf-Id] => Sc-drS9O65XlBKlqmXBgQy5o8GKUtQ4pRw8NiqKxWEBvkIk7Bh4VKg==
[Cloudfront-Viewer-Tls] => TLSv1.3:TLS_AES_128_GCM_SHA256:fullHandshake
[Cloudfront-Viewer-Address] => 152.69.197.33:49333
[Accept-Encoding] => br,gzip
[Cloudfront-Viewer-Longitude] => 135.50400
[Cloudfront-Viewer-Asn] => 31898
[Connection] => keep-alive
[Via] => 3.0 0cf2f9f29d4ea64bbc1cf639883c7e5a.cloudfront.net (CloudFront)
[User-Agent] => Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
[X-Forwarded-For] => 152.69.197.33
[Cloudfront-Forwarded-Proto] => https
[Accept] => text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
[Accept-Language] => en-US,en;q=0.9
[Cookie] => _ga=GA1.1.1737721359.1763112581; _ga_PBL0EPJRYT=GS2.1.s1764820194$o3$g1$t1764820240$j14$l0$h0; cf_clearance=AeyyOOy1qnnUVxucgyJEPLGjgOaxN7w3CJX0f7Kkty8-1764388065-1.2.1.1-84shCFFoEVI.YrH7rr6BuaQRU0UILV7DKkL8GyWY8O5cDTyCJ0LL9RleTZVQS11eUsBX.npUGs3UkUgDRUkBHsC3JndxqSC.W9TNlLYSOncByE5Mj88kppK55MB4.CwjTcDq6QoB7ULnu7I4hrFFiD6PQnRMY6TfBFZL_Sm2dyMcuXX3GF2Yvlr8wBqbsUWVLx_v8OuzDFViHOUXS9PqG4BsG1.R5ASPCPqbyr_bgXI
[Cloudfront-Is-Android-Viewer] => false
[Cloudfront-Is-Ios-Viewer] => false
[Cloudfront-Is-Desktop-Viewer] => true
[Cloudfront-Is-Smarttv-Viewer] => false
[Cloudfront-Is-Tablet-Viewer] => false
[Cloudfront-Is-Mobile-Viewer] => false
[X-Origin-Verify] => 5Pb9Qsf2XbZK23C
[Host] => bbken.org
[Content-Length] =>
[Content-Type] =>
)
預覽一下,堅決的創建他。

接下來進入Cache Behaviors 設置,添加幾個新的cache policy,網路上有很多關於wordpress 的cache policy 設置,他們使用傳統的legacy settings, 都已經老舊而顯得不合時宜,這裡有兩種方案提供給你,注意喔,不能超過5條喔:
第一種:預設緩存所有文件和路徑,然後將必要的不需要cache 的路徑單獨拿出來,請注意先後順序,有優先級。
優點:靜態化很強,如果使用了wp-supercache 進行靜態化的話,效果就很好,因為所有blog posts 都是pure html 進行緩存。
缺點:comments 的部分可能還需要加入到 Managed-CachingDisabled ,需要安裝plugin來自動刷新緩存,否則你新增一篇blog 但是首頁不會更新。

/wp-login.php
Managed-CachingDisabled
Managed-AllViewerAndCloudFrontHeaders-2022-06
/xmlrpc.php
Managed-CachingDisabled
Managed-AllViewerAndCloudFrontHeaders-2022-06
/wp-admin/*
Managed-CachingDisabled
Managed-AllViewerAndCloudFrontHeaders-2022-06
Default(*)
Managed-CachingOptimized
Managed-AllViewerAndCloudFrontHeaders-2022-06
第二種:預設不緩存所有文件和路徑,然後將需要緩存的路徑拿出來,請注意先後順序,有優先級。
優點:只緩存必要的部分,這兩個路徑是官方建議以及wp-supercache 建議,以及目前網路上大多數wordpress 和cloudfront 結合建議緩存的路徑。
缺點:對於靜態化的wordpress 無法做到所有的blog 頁面緩存。

/wp-includes/*
Managed-CachingOptimized
Managed-AllViewerAndCloudFrontHeaders-2022-06
/wp-content/*
Managed-CachingOptimized
Managed-AllViewerAndCloudFrontHeaders-2022-06
Default(*)
Managed-CachingDisabled
Managed-AllViewerAndCloudFrontHeaders-2022-06
配置好Behaviors 之後,回到Distribution 的首頁,我還沒有添加domain,沒有添加之前,CloudFront 上面就不能識別你的domain name,即使你將域名解析過來也是不行的,點擊Add domain。

輸入domain name,這裡我輸入兩個,你也可以輸入多個,甚至可以將不同的domains 放在同一個distrbution:

這個時候,會提示沒有TLS 證書,點擊Create certificate,

AWS certificate manager 會自動為你生成兩個需要在domain resolver 那邊需要添加的CNAME,將他們添加進去。

添加之後這個頁面會自動刷新驗證,DNS 解析生效時間不一,有的長有的短,但是現在大多數DNS 服務商都已經縮短為300秒,哪像我剛上網那時候都一天兩天的,不要錢的證書創建成功後,點擊Next。
!注意!如果你上面將多個不同的domains 加入到同一個certificate,client 將可以從webbrowser 中的certificate 識別到你所有的站點,如果你不想讓別人知道所有的站點都是你的,那麼還是把他們分開比較好,比如你做了一個支持藍營的網站,然後又做了一個支持綠營的網站,然後你把兩個網站的domains 放到了同一個certificate,被人發現就不太好了。

預覽一下,沒問題,點擊Add domains,

這樣就完全結束了!等待右上角的Deploying 完成,就可以訪問啦!

是不是很簡單。
好了回過頭來說wordpress 的cloudfront plugin,主要就是刷新緩存,安裝C3 Cloudfront Cache Controller,安裝完成後,首先需要創建一個IAM 用戶,為他賦予CloudFront 讀取和刷新緩存的權限,並分配一個AK/SK,當然,如果你的server 在EC2 ,你也可以使用instance profile ,這樣AK/SK 可以留空即可。輸入Distribution ID 和AK/SK,Save Changes。
輸入post id 來刷新,或是選擇Flush All Cache。
輸入post id 刷新,將會刷新post,分類以及首頁。
在更新一篇blog 後,他會自動刷新。

是不是很簡單。
注意到CloudFront 已經啟用HTTP/3 了我的源站還在HTTP/1.1。
更新一下nginx好了。
全局配置文件nginx.conf 中 http 段落加入:
http {
http2 on;
http3 on;
vhosts.conf 中server 段落加入:
###bbken.org at 443####
server {
server_name bbken.org www.bbken.org;
listen 443 ssl ;
listen 443 quic reuseport;
listen [::]:443 ssl;
listen [::]:443 quic reuseport; # IPv6
add_header Alt-Svc 'h3=":443"; ma=86400';
重啟一下nginx -s reload。
檢查一下udp 監聽:
ss -ulnp | grep :443
UNCONN 0 0 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=281315,fd=28),("nginx",pid=281314,fd=28),("nginx",pid=240301,fd=28))
UNCONN 0 0 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=281315,fd=26),("nginx",pid=281314,fd=26),("nginx",pid=240301,fd=26))
UNCONN 0 0 [::]:443 [::]:* users:(("nginx",pid=281315,fd=27),("nginx",pid=281314,fd=27),("nginx",pid=240301,fd=27))
UNCONN 0 0 [::]:443 [::]:* users:(("nginx",pid=281315,fd=29),("nginx",pid=281314,fd=29),("nginx",pid=240301,fd=29))
測試一下header:
HTTP/2 200
server: nginx/1.28.0
date: Fri, 05 Dec 2025 08:03:12 GMT
content-type: text/html; charset=UTF-8
vary: Accept-Encoding
vary: Accept-Encoding, Cookie
cache-control: max-age=3, must-revalidate
因為curl 不支持HTTP/3 下次再測。



