RSS加入WebSub Hub後無法推播的原因:被Cloudflare防火牆阻擋

通常有些網站內容需要即時性通知(例如新聞網站),建議將RSS加入WebSub宣告,因為它是主動推播,而一般RSS是傳統輪詢。可以看一下這篇文章的解說:〈How WebSub works: and why it’s good for your podcast〉。

但是網站有用Cloudflare服務的話,WebSub Hub極有可能被其防火牆阻擋而無法推播。

注意

本文由 Claude Sonnet 4.6 語言模型產生。

前言

WebSub(前身為 PubSubHubbub)是一個讓網站能「主動推播」內容更新給訂閱者的協定,常見應用包含 RSS to Email 服務(如 Blogtrottr)、RSS 閱讀器的即時更新(如 Feedly、Inoreader、FreshRSS),以及加速 Google 搜尋收錄。

對靜態網站來說,實作 WebSub 的標準流程是:

  1. 在 RSS feed 裡宣告使用的 Hub(如 Google Hub 或 Superfeedr)。
  2. 在每次部署後,透過 CI/CD(如 GitHub Actions)ping Hub 通知「內容已更新」。

這個流程看起來簡單,但有一個很容易被忽略的問題,導致設定完全正確、ping 也成功,Hub 卻無法推播。

問題描述

症狀

  • GitHub Actions ping Hub 回傳 HTTP 204(表示 Hub 成功收到通知)。
  • 但 RSS 訂閱者(如 Blogtrottr)收到更新的時間仍然很慢,沒有明顯改善。
  • 到 Google Hub 的 Topic Details 頁面查看,發現 Last successful fetch 停在舊的時間點
  • Google Hub 甚至出現 HTTP 504 的 fetch error。

WebSub 的完整流程如下:

你發文
  ↓
CI/CD ping Hub(你告訴 Hub「我更新了」)← ping 成功 ✅
  ↓
Hub 去抓你的 rss.xml(Hub 確認新內容)← 504 失敗 ❌
  ↓
Hub 推播給訂閱者                       ← 永遠不會發生

ping 成功只是第一步,Hub 收到通知後,還需要自己去抓你的 feed 才能確認新內容。如果這個抓取動作被擋下來,整個推播流程就斷掉了。

根本原因:Cloudflare 默默丟棄 Hub 的抓取請求

使用 Cloudflare 的網站,預設的 Security Level 或相關機制會對「可疑的 Bot 流量」發出 JavaScript Challenge。

Hub 的爬蟲是純 HTTP 客戶端,無法解 JavaScript Challenge,結果就是:

  • 請求超時 → 回傳 504
  • 這個過程不會在 WAF Log 留下記錄(因為被更前面的層級擋住了),所以 WAF Log 顯示 0,讓人誤以為沒有任何請求被擋。

Google Hub 遇到 504 後,會啟動指數退避(Exponential Backoff)機制──即使之後的 ping 都正常回傳 204,Hub 內部對這個 feed 的抓取排程已經被推遲很久,可能是數小時到數天,導致推播長期失效。

解法:在 Cloudflare 對 /rss.xml 建立 WAF 跳過規則

在 Cloudflare Dashboard → 網路安全 → WAF → 自訂規則,新增一條規則:

欄位 設定
規則名稱 Allow RSS Feed Crawlers
編輯運算式 http.request.uri.path eq "/rss.xml"
選擇動作 Skip
要跳過的 WAF 元件 所有受控規則 + 安全等級(Security Level)

這樣所有對 /rss.xml 的請求都會跳過安全檢查,Hub 的爬蟲就能正常抓取 feed。

設定完成後,WAF Log 就會開始出現 採取的動作:跳過 的記錄,確認規則有命中。

驗證方式

確認 Hub 能正常抓取
到 Google Hub 的 Topic Details 頁面查看:

https://pubsubhubbub.appspot.com/topic-details?hub.url=https://yoursite.com/rss.xml

Last successful fetch 的時間應該會在你 ping 之後更新。

確認推播正常
發文後,RSS to Email 服務(如 Blogtrottr)應在數分鐘內收到信,而非半小時甚至更久。

附:靜態網站實作 WebSub 的完整設定

RSS feed 宣告 Hub(以 Astro 為例)

// src/pages/rss.xml.js
return rss({
  // ...
  xmlns: { atom: 'http://www.w3.org/2005/Atom' },
  customData: `
    <language>zh-TW</language>
    <atom:link href="${feedUrl}" rel="self" type="application/rss+xml" />
    <atom:link href="https://pubsubhubbub.appspot.com/" rel="hub" />
  `,
});

HTML <head> 也宣告 Hub(可選但建議)

根據 W3C WebSub 規格,訂閱者做 discovery 時的順序是:HTTP Link Header → HTML <head> → RSS feed 內的 <link>

RSS feed 裡的宣告對 Blogtrottr 這類直接訂閱 feed URL 的服務已經夠用,但如果有工具是從網頁 URL 開始做 discovery,就需要 HTML <head> 裡的宣告才能找到 Hub。加上這行可以讓設定更完整:

<!-- HTML <head> 裡,例如 Layout.astro -->
<link rel="hub" href="https://pubsubhubbub.appspot.com/">

GitHub Actions 在部署後 ping Hub

- name: Ping WebSub Hub
  run: |
    curl -s -X POST "https://pubsubhubbub.appspot.com/" \
      -d "hub.mode=publish" \
      -d "hub.url=https://yoursite.com/rss.xml" \
      -w "\nWebSub Ping HTTP Status: %{http_code}\n"

HTTP 204 代表 Hub 成功收到通知。

防止 Cloudflare Edge Cache 導致 504

public/_headers 設定(Cloudflare Pages):

/rss.xml
  Cache-Control: no-cache, no-store, must-revalidate, max-age=0

或在 Cloudflare Dashboard 的 Cache Rules 加一條 Bypass Cache 規則,確保每次請求都拿到最新的 feed 內容。

總結

現象 原因
ping 回傳 204,但 Hub 不推播 Hub 的抓取請求被 Cloudflare 擋住(504)
WAF Log 顯示 0 筆記錄 請求在 WAF 之前的層級就被默默丟棄
前幾天正常,之後突然失效 一次 504 觸發 Hub 的指數退避機制
加了 WAF 跳過規則後恢復正常 Hub 爬蟲終於能正常抓取 feed

結論:WebSub 的 ping 成功只是第一步,Hub 能不能抓到你的 feed 才是關鍵。使用 Cloudflare 的網站,記得對 RSS feed 路徑加上 WAF 跳過規則。