最近有人反馈说,通过 Feedly 订阅我的博客总是失败。我自己在浏览器里打开wuqishi.com/feed/明明一切正常,这典型的“RSS 403”问题,相当于关上了订阅的大门。折腾一圈下来,发现根源无非两个:服务器上的 PHP 乱输出,或者Cloudflare 等 CDN 的“误杀” 。下面就把我的排查过程和解决方案贴出来,希望能帮你一把。

RSS订阅修复

首先,我们得知道问题出在哪个环节。打开终端,用下面这条命令模拟一下 RSS 阅读器的请求:

curl -I -H "User-Agent: Feedly/1.0" https://wuqishi.com/feed/

重点看返回的 HTTP 状态码和响应头:

  • 如果状态码是403,并且响应头里带有 CF-RAY字段:那问题八成出在Cloudflare的WAF或者Bot防护规则上,你的请求在CDN边缘就被掐掉了。直接看下面关于Cloudflare的章节。
  • 如果状态码不是200,也没有 CF-RAY:这是你的源站服务器(比如Nginx/PHP)自己返回的错误,去看PHP修复那部分。
  • 如果返回了XML,但内容最前面混入了PHP的Warning或Deprecated信息:这就是经典的“PHP输出污染”,XML格式被破坏,阅读器自然无法识别。解决方案也在PHP部分。

PHP层修复:搞定版本兼容性与错误输出

不同 PHP 版本,坑点不一样。这里我简单列一下,方便大家对号入座:

  • PHP 7.4 / 8.0 / 8.1​:这几个版本比较安分,主要确保php.ini​里display_errors = Off别让警告输出就行。不过PHP 7.4已经停止官方支持了,能升级还是建议升级。
  • PHP 8.2​:重点关注! 这个版本开始默认弃用“动态属性”,很多老程序会因此抛出Deprecated​警告,这些警告会直接插在RSS的XML开头,导致解析失败。解决办法是在程序入口或php.ini里关掉错误显示。
  • PHP 8.3 / 8.4:类型系统越来越严格。8.3对动态属性控制更狠,8.4则弃用了隐式可空类型声明。确保你用的博客程序(如Typecho, WordPress)已经发布了兼容这些新版本的更新。

具体操作

在你的网站入口文件(例如index.php)最开头,或者直接在php.ini配置文件中,加入以下代码来压制错误输出:

// 方法一:在程序入口文件(如 index.php)顶部添加
error_reporting(0);
ini_set('display_errors', '0');

// 方法二:修改 php.ini(效果更全局)
display_errors = Off
error_reporting = E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED

我的建议是,无论用哪个版本,在生产环境永远关闭 display_errors 修改完配置后,别忘了重启 PHP-FPM 服务(例如 systemctl restart php8.2-fpm)。

Cloudflare层修复:让WAF规则放行你的订阅源

浏览器能打开,阅读器报 403——这是 Cloudflare 把 RSS 阅读器的请求当成可疑机器人给拦了。我们需要调整它的安全规则。

第一步:关闭“Bot Fight Mode”

操作路径:Cloudflare 控制台 → 安全性 (Security) → 自动程序 (Bots)

找到“自动程序攻击模式”(Bot Fight Mode),把它关掉。免费版的这个模式有点“宁杀错不放过”,会误伤很多正常的自动请求,比如 RSS 阅读器、搜索引擎爬虫。

如果你是 Pro 版用户,可以开启更智能的“Super Bot Fight Mode”,然后在“已验证的机器人”(Verified Bots) 分类中,设置规则为“允许”(Allow),这样 Feedly、Googlebot 这些正规军就能通行了。

第二步:配置精准的WAF规则

我们需要创建一条规则,专门放行 RSS 订阅地址的请求,不让后续的安防规则拦截它。

在 Cloudflare 控制台,进入安全性 → WAF → 自定义规则,创建一个新规则:

  1. 规则名称:比如“Allow RSS Feed Subscribers”

  2. 规则表达式 (When incoming requests match…)

    (http.request.uri.path contains "/feed") or (http.request.uri.path contains "/rss") or (http.request.uri.path contains "/atom")
    

    (请根据你博客实际的 RSS 路径修改)

  3. 操作 (Then…) :选择 跳过 (Skip)

  4. 在 Skip 的子选项中,勾选“跳过所有剩余的自定义规则”(Skip remaining custom rules) 。这一步非常关键,确保 RSS 请求不受其他可能存在的拦截规则影响。

然后,把这个规则的优先级拖到比较靠前的位置,确保它先于其他可能的拦截规则生效。

第三步:清除缓存

修改完任何 Cloudflare 设置后,一个好习惯是清除缓存。

操作路径:Cloudflare 控制台 → 缓存 (Caching) → 配置 (Configuration) → 清除所有内容 (Purge Everything)。

点“清除所有”。之后,再用阅读器或curl命令测试一下你的 RSS 地址,应该就能正常返回了。

源站加固:Nginx配置兜底

为了防止有人绕过 Cloudflare 直接攻击源站,或者作为你没用 CDN 时的防护,可以在 Nginx 里加一层基础防护。

把下面这段配置加到你的网站 Nginx 配置文件中(通常是 server块内):

location ~ ^/(feed|rss|atom) {
    # 只允许GET请求访问RSS
    limit_except GET { deny all; }
    
    # 拦截一些常见的恶意扫描工具的UA
    if ($http_user_agent ~* (sqlmap|nikto|nmap|zgrab|gobuster)) {
        return 444;
    }
    
    # 设置访问频率限制,防止被刷
    limit_req zone=one burst=5 nodelay;
    
    # 这里是你的原始PHP处理逻辑,保持不变
    try_files $uri $uri/ /index.php?$args;
}

# 可以顺便拦一下其他常见恶意请求路径
location ~* /(wp-admin|xmlrpc\.php|\.env|\.git) {
    return 444;
}

记得根据你服务器的实际情况,调整频率限制区域 (zone) 的名称和limit_req的参数。配置好后,运行 nginx -t测试配置,然后 systemctl reload nginx重载生效。

验证清单

都配置完之后,可以用下面这个清单快速检查一下是否全部生效:

  1. 检查PHP输出:直接用浏览器打开你的RSS订阅地址,查看网页源代码,看最前面有没有不该出现的PHP错误信息。
  2. 模拟阅读器请求​:在终端运行 curl -H "User-Agent: Feedly/1.0" https://你的域名.com/feed,确认返回状态码是200,并且内容是干净的XML。
  3. 清理Cloudflare缓存:在Cloudflare控制台的“缓存”设置里,执行“清除所有”的操作,确保新规则立即生效。
  4. 终极验证​:去 W3C Feed验证服务或你的Feedly订阅器里,重新添加一遍订阅链接,看看是否成功。

按照上面步骤走一遍,绝大多数 RSS 403 的问题都能解决。如果还有问题,欢迎在评论区留言讨论。