Cloudflare 2025 年 11 月 18 日故障报告
本文是Cloudflare 2025 年 11 月 18 日故障报告翻译版本
2025 年 11 月 18 日 11:20 协调世界时 (UTC),Cloudflare 网络在传输核心流量时开始出现严重故障。对于试图访问我们客户网站的互联网用户而言,这表现为一个错误页面,提示 Cloudflare 网络内部发生了问题。

事件期间显示的 HTTP 错误页面
本次故障并非由任何形式的网络攻击或恶意活动直接或间接导致。 相反,其根源在于我们某个数据库系统的权限变更,这一变更导致数据库向“机器人管理 (Bot Management)”系统所使用的一个“特征文件”中输出了大量重复条目,导致该文件体积倍增。随后,这个超出预期的特征文件被分发到了构成我们全球网络的所有机器上。
这些机器上运行的流量路由程序需要读取此特征文件,以确保机器人管理系统能够及时应对不断变化的威胁。然而,该软件对特征文件的体积设有一个上限,而翻倍后的文件大小超出了这一限制,最终导致软件运行失败。
最初,我们曾错误地将所观察到的现象归咎于超大规模的 DDoS 攻击。但随后,我们正确识别了核心问题,并及时阻止了异常大小特征文件的传播,将其替换为早期版本。到 14:30,核心流量已基本恢复正常。在接下来的几个小时里,我们努力缓解因流量回涌对网络各部分造成的负载压力。截至 17:06,Cloudflare 所有系统均已恢复正常运行。
对于此次故障给客户乃至整个互联网造成的影响,我们深感歉意。鉴于 Cloudflare 在互联网生态系统中的重要地位,我们任何系统的任何服务中断都是不可接受的。我们的网络在一段时间内无法正常路由流量,这让团队的每一位成员都深感痛心。我们知道,今天辜负了大家的信任。
本文将全面复盘此次事件,详细剖析导致问题的系统及流程。同时,这也标志着我们已着手采取措施,以确保此类故障不再发生——这只是一个开始,绝非结束。## 故障详情
下图展示了 Cloudflare 网络在事件期间记录的 HTTP 5xx 错误状态码的数量。正常情况下,这个数值应该非常低,并且在故障发生前也确实如此。

Cloudflare网络记录的 HTTP 5xx 请求量
在11:20 之前,图表中的数据是我们网络上 5xx 错误的正常基线水平。随后的峰值和波动,揭示了系统因加载了错误的特征文件而出现故障。尤为值得关注的是,系统在此期间竟然一度恢复正常,这对于一种内部错误而言,是极其反常的现象。
原因在于,该文件由一个在 ClickHouse 数据库集群上运行的查询每五分钟生成一次,而当时我们正在逐步更新该集群以改进权限管理。只有当查询在已更新权限的集群节点上运行时,才会生成错误数据。因此,每隔五分钟,系统就有可能生成一组正确或错误的配置文件,并迅速分发到整个网络。
这种时好时坏的波动让故障排查变得异常困难,因为整个系统会先恢复正常,然后随着新配置文件的分发再次宕机。这使得我们最初认为这可能是一次网络攻击。最终,随着所有 ClickHouse 节点都开始生成错误的配置文件,系统彻底陷入了持续故障的状态。
错误一直持续到 14:30,我们定位并解决了根本问题。解决方案是停止生成和分发错误的特征文件,手动将一个已知的正常文件注入分发队列,并强制重启我们的核心代理服务。
图表中拖长的尾部,是我们团队重启其他进入异常状态的服务所致。到 17:06,5xx 错误码的数量最终恢复到了正常水平。
以下是受影响的服务:
| 服务 / 产品 | 影响描述 |
|---|---|
| 核心 CDN 和安全服务 | 返回 HTTP 5xx 状态码。本文开头的截图就是一个典型的终端用户收到的错误页面。 |
| Turnstile | Turnstile 无法加载。 |
| WorkersKV | 由于核心代理故障,访问 Workers KV 前端网关的请求失败,导致其返回的 HTTP 5xx 错误显著增加。 |
| 控制面板 (Dashboard) | 虽然控制面板本身大部分功能正常,但由于登录页面的 Turnstile 服务不可用,大多数用户无法登录。 |
| 邮件安全 | 邮件处理和投递未受影响,但我们观察到对一个 IP 信誉源的访问短暂中断,这降低了垃圾邮件的检测精度,并阻止了一些新域名时效性检测的触发,但未对客户造成关键影响。此外,部分“自动移动”操作也出现失败;所有受影响的邮件均已得到审查和修复。 |
| Access | 从故障开始到 13:05 回滚启动前,大多数用户普遍遇到身份验证失败问题。已有的 Access 会话不受影响。所有失败的身份验证尝试都会返回一个错误页面,这意味着在验证失败期间,用户无法访问目标应用。在此期间的成功登录被正确记录。任何在此期间尝试的 Access 配置更新都可能直接失败或分发极其缓慢。所有配置更新现已恢复。 |
除了返回 HTTP 5xx 错误外,我们还观察到在故障期间 CDN 的响应延迟显著增加。这是因为我们的调试和可观测性系统消耗了大量 CPU,这些系统会自动为未捕获的错误附加额外的调试信息。
Cloudflare 的请求处理流程及故障根源每个请求都会遵循一条明确定义的路径,流经 Cloudflare 网络。无论是来自加载网页的浏览器、调用 API 的移动应用,还是来自其他服务的自动化流量,这些请求首先会到达我们的 HTTP 和 TLS 层,然后进入我们的核心代理系统(我们内部称之为 FL,即 “Frontline”),最后通过 Pingora 进行缓存查找或在必要时从源站获取数据。
我们之前曾在此博客文章中分享过关于核心代理工作原理的更多细节。

反向代理架构图
当请求通过核心代理时,我们会依次执行网络中各项安全检测和性能优化措施。代理会应用每个客户独特的配置和设置,从执行 WAF 规则和 DDoS 防护,到将流量路由到开发者平台和 R2。这是通过一系列特定领域的模块实现的,这些模块将配置和策略规则应用于流经我们代理的流量。其中一个模块——机器人管理 (Bot Management)——正是今天故障的源头。
Cloudflare 的 机器人管理 系统包含一个机器学习模型,我们用它为流经网络的每个请求生成机器人评分。我们的客户利用这个评分来控制哪些机器人可以访问他们的网站。
该模型需要一个“特征”配置文件作为输入。这里的“特征”是指机器学习模型用来预测请求是否为自动化的单个属性。而特征配置文件就是这些单个特征的集合。
这个特征文件每隔几分钟就会刷新并发布到我们的整个网络,使我们能够应对互联网流量流的各种变化,并对新型机器人和新型机器人攻击做出反应。因此,随着攻击者策略的快速变化,频繁而迅速地推出更新至关重要。
我们底层用于生成此文件的 ClickHouse 查询行为发生了变化(下文将解释),导致文件中包含了大量重复的“特征”行。这改变了原本固定大小的特征配置文件的体积,从而触发了机器人模块的错误。
结果,核心代理系统在处理客户流量时,只要流量依赖于机器人模块,就会返回 HTTP 5xx 错误码。这也影响了同样依赖于核心代理的 Workers KV 和 Access 服务。
与此次事件无关的是,我们目前正在将客户流量迁移到一个新版本的代理服务,内部称为 FL2。两个版本都受到了这个问题的影响,尽管观察到的影响有所不同。
部署在新 FL2 代理引擎上的客户,观察到了 HTTP 5xx 错误。而部署在我们旧代理引擎(称为 FL)上的客户没有看到错误,但机器人评分未能正确生成,导致所有流量的机器人评分都为零。对于部署了拦截机器人规则的客户来说,这会导致大量的误报。而那些规则中未使用机器人评分的客户则未受影响。
另一个让我们误判为攻击的现象是,Cloudflare 的状态页面也曾宕机。状态页面完全托管在 Cloudflare 基础设施之外,对 Cloudflare 没有任何依赖。虽然事后证明这只是一个巧合,但它让一些参与故障诊断的团队成员认为,攻击者可能同时瞄准了我们的系统和状态页面。当时访问状态页面的访客会看到一条错误信息:

Cloudflare 状态页面的错误信息

查询行为的变更
如前所述,底层查询行为的变更导致特征文件中出现了大量重复行。这个数据库系统使用的是 ClickHouse 软件(一种流行的开源列式数据库)。
为了更好地理解背景,了解 ClickHouse 的分布式查询工作原理会很有帮助。一个 ClickHouse 集群由许多分片 (shard) 组成。为了从所有分片查询数据,我们在一个名为 default 的数据库中使用了所谓的分布式表(由 Distributed 表引擎驱动,该引擎用于聚合多个分片的数据)。Distributed 引擎会查询一个 r0 数据库中的底层表(实际存储数据的物理表)。
对分布式表的查询通过一个共享的系统账户运行。为了提高我们分布式查询的安全性和可靠性,我们正在进行一项工作,使其转而使用发起查询的初始用户账户来运行。
在今天之前,当 ClickHouse 用户从 system.tables 或 system.columns 等系统表中查询表元数据时,他们只能看到 default 数据库中的表。由于用户已经对 r0 中的底层表拥有隐式访问权限,我们在 11:05 对访问权限进行了调整,将原本的隐式访问改为显式授权,以便用户也能看到这些表的元数据。通过确保所有分布式的子查询都能在初始用户下运行,查询限制和访问授权可以得到更精细的评估,从而避免某个用户的单个不良子查询影响到其他人。
上述变更使得所有用户都能访问到他们有权访问的表的准确元数据。遗憾的是,以往的部分代码假定像下面这样的查询返回的列列表将只包含来自 default 数据库的列:
SELECT name, type FROMsystem.columns WHERE table = 'http_requests_features' order by name;
请注意,该查询并未按数据库名称进行筛选。随着我们将显式授权逐步推广到特定 ClickHouse 集群的用户,在11:05 的变更之后,上述查询开始返回“重复”的列,因为这些重复的列实际上是 r0 数据库中底层表的元数据。
不幸的是,机器人管理系统的特征文件生成逻辑正是使用了这类查询来构建文件中提到的每个输入“特征”。
上述查询会返回一个类似下图所示的结果集(简化示例):

代码块示例
然而,由于授予了用户额外的权限,响应现在包含了 r0 模式的所有元数据,这使得响应中的行数增加了一倍以上,最终影响了最终输出文件中的行数(即特征数量)。
内存预分配我们代理服务上运行的每个模块均设置了上限,以防止内存无限膨胀,并通过预分配内存来优化性能。具体到这次事件,机器人管理系统对运行时可以使用的机器学习特征数量设有一个上限。目前该限制设置为 200 个,远高于我们当前约 60 个特征的使用量。同样,设置这个限制是为了性能考虑,预先为这些特征分配内存。
当包含超过 200 个特征的错误文件被分发到我们的服务器时,就触发了这个上限,导致系统产生 Panic(程序异常终止)。执行检查并导致未处理错误的 FL2 Rust 代码如下所示:

导致错误的代码
这导致了以下 panic,进而产生了 5xx 错误:thread fl2_worker_thread panicked: called Result::unwrap() on an Err value
事件期间的其他影响
其他依赖我们核心代理的系统在事件期间也受到了影响,包括 Workers KV 和 Cloudflare Access。团队在 13:04 通过为 Workers KV 应用一个补丁以绕过核心代理,成功减轻了对这些系统的影响。随后,所有依赖 Workers KV 的下游系统(例如 Access 本身)的错误率都有所下降。
Cloudflare 控制面板也受到了影响,因为它内部使用了 Workers KV,并且登录流程中部署了 Cloudflare Turnstile。
Turnstile 在这次故障中受到冲击,导致没有活动会话的客户无法登录控制面板。如下图所示,这在两个时间段内表现为可用性下降:11:30 至 13:10,以及 14:40 至 15:30。

第一个时段(11:30 至 13:10)是由于 Workers KV 受到影响,而一些控制平面和控制面板功能依赖于它。在 13:10,当 Workers KV绕过核心代理系统后,可用性得以恢复。第二个影响时段发生在恢复特征配置文件之后。积压的登录尝试开始压垮控制面板。这种积压加上重试尝试,导致延迟升高,从而降低了控制面板的可用性。通过扩展控制平面的并发能力,我们在大约 15:30 恢复了可用性。
补救措施与后续步骤
现在我们的系统已经恢复在线并正常运行,我们已经开始着手加固系统,以防范未来发生此类故障。具体措施包括:
- 以对待用户输入的严谨态度,对 Cloudflare 内部生成的配置文件加强校验。
- 为各项功能启用更多的全局紧急关闭开关。
- 消除核心转储 (core dump) 或其他错误报告可能耗尽系统资源的情况。
- 全面审查所有核心代理模块在错误条件下的故障模式。
今天是 Cloudflare 自 2019 年以来最严重的一次故障。我们曾经历过导致控制面板不可用的故障,也曾有过导致一些较新功能在一段时间内无法使用的故障。但在过去六年多的时间里,我们从未再发生过导致大部分核心流量无法通过我们网络的故障。
像今天这样的故障是不可接受的。我们的系统架构旨在实现高度的故障恢复能力,以确保流量始终能够畅通无阻。过去每一次的故障都促使我们构建了新的、更具弹性的系统。
我谨代表 Cloudflare 全体团队,为今天给互联网带来的深刻影响和不便深表歉意。
| 时间 (UTC) | 状态 | 描述 |
|---|---|---|
| 11:05 | 正常 | 部署数据库访问控制变更。 |
| 11:28 | 影响开始 | 部署到达客户环境,客户 HTTP 流量首次观察到错误。 |
| 11:32-13:05 | 团队调查 | 团队调查 Workers KV服务的流量和错误率升高。初步症状表现为 Workers KV 响应率下降,导致其他 Cloudflare 服务受到下游影响。尝试了流量调整和账户限制等缓解措施以使 Workers KV 服务恢复正常。首个自动化测试在 11:31 检测到问题,手动调查于 11:32 开始。事件响应会议在 11:35 建立。 |
| 13:37 | 聚焦回滚 | 我们确信机器人管理配置文件是事件的触发器。团队兵分多路修复服务,其中最快的方案是恢复文件的先前版本。 |
| 14:24 | 停止生成文件 | 我们确定机器人管理模块是 500 错误的源头,且由一个错误的配置文件引起。我们停止了新机器人管理配置文件的自动部署。 |
| 14:24 | 新文件版本测试完成 | 我们观察到使用旧版本配置文件后系统成功恢复,随后集中力量在全球范围内加速修复。 |
| 14:30 | 主要影响解决 | 正确的机器人管理配置文件已在全球部署,大多数服务开始正常运行。下游受影响的服务的错误率开始下降。 |
| 17:06 | 所有服务恢复 | 所有下游服务均已重启,所有操作完全恢复。影响结束。 |
| 反向代理架构图 |
当请求通过核心代理时,我们会依次执行网络中各项安全检测和性能优化措施。代理会应用每个客户独特的配置和设置,从执行 WAF 规则和 DDoS 防护,到将流量路由到开发者平台和 R2。这是通过一系列特定领域的模块实现的,这些模块将配置和策略规则应用于流经我们代理的流量。其中一个模块——机器人管理 (Bot Management)——正是今天故障的源头。
Cloudflare 的 机器人管理 系统包含一个机器学习模型,我们用它为流经网络的每个请求生成机器人评分。我们的客户利用这个评分来控制哪些机器人可以访问他们的网站。
该模型需要一个“特征”配置文件作为输入。这里的“特征”是指机器学习模型用来预测请求是否为自动化的单个属性。而特征配置文件就是这些单个特征的集合。
这个特征文件每隔几分钟就会刷新并发布到我们的整个网络,使我们能够应对互联网流量流的各种变化,并对新型机器人和新型机器人攻击做出反应。因此,随着攻击者策略的快速变化,频繁而迅速地推出更新至关重要。
我们底层用于生成此文件的 ClickHouse 查询行为发生了变化(下文将解释),导致文件中包含了大量重复的“特征”行。这改变了原本固定大小的特征配置文件的体积,从而触发了机器人模块的错误。
结果,核心代理系统在处理客户流量时,只要流量依赖于机器人模块,就会返回 HTTP 5xx 错误码。这也影响了同样依赖于核心代理的 Workers KV 和 Access 服务。
与此次事件无关的是,我们目前正在将客户流量迁移到一个新版本的代理服务,内部称为 FL2。两个版本都受到了这个问题的影响,尽管观察到的影响有所不同。
部署在新 FL2 代理引擎上的客户,观察到了 HTTP 5xx 错误。而部署在我们旧代理引擎(称为 FL)上的客户没有看到错误,但机器人评分未能正确生成,导致所有流量的机器人评分都为零。对于部署了拦截机器人规则的客户来说,这会导致大量的误报。而那些规则中未使用机器人评分的客户则未受影响。
另一个让我们误判为攻击的现象是,Cloudflare 的状态页面也曾宕机。状态页面完全托管在 Cloudflare 基础设施之外,对 Cloudflare 没有任何依赖。虽然事后证明这只是一个巧合,但它让一些参与故障诊断的团队成员认为,攻击者可能同时瞄准了我们的系统和状态页面。当时访问状态页面的访客会看到一条错误信息:

Cloudflare 状态页面的错误信息
在内部的事件沟通群里,我们一度担心这可能是近期一系列大流量 Aisuru DDoS 攻击 的延续:

内部聊天截图
查询行为的变更
如前所述,底层查询行为的变更导致特征文件中出现了大量重复行。这个数据库系统使用的是 ClickHouse 软件(一种流行的开源列式数据库)。
为了更好地理解背景,了解 ClickHouse 的分布式查询工作原理会很有帮助。一个 ClickHouse 集群由许多分片 (shard) 组成。为了从所有分片查询数据,我们在一个名为 default 的数据库中使用了所谓的分布式表(由 Distributed 表引擎驱动,该引擎用于聚合多个分片的数据)。Distributed 引擎会查询一个 r0 数据库中的底层表(实际存储数据的物理表)。
对分布式表的查询通过一个共享的系统账户运行。为了提高我们分布式查询的安全性和可靠性,我们正在进行一项工作,使其转而使用发起查询的初始用户账户来运行。
在今天之前,当 ClickHouse 用户从 system.tables 或 system.columns 等系统表中查询表元数据时,他们只能看到 default 数据库中的表。由于用户已经对 r0 中的底层表拥有隐式访问权限,我们在 11:05 对访问权限进行了调整,将原本的隐式访问改为显式授权,以便用户也能看到这些表的元数据。通过确保所有分布式的子查询都能在初始用户下运行,查询限制和访问授权可以得到更精细的评估,从而避免某个用户的单个不良子查询影响到其他人。
上述变更使得所有用户都能访问到他们有权访问的表的准确元数据。遗憾的是,以往的部分代码假定像下面这样的查询返回的列列表将只包含来自 default 数据库的列:
SELECT name, type FROMsystem.columns WHERE table = 'http_requests_features' order by name;
请注意,该查询并未按数据库名称进行筛选。随着我们将显式授权逐步推广到特定 ClickHouse 集群的用户,在11:05 的变更之后,上述查询开始返回“重复”的列,因为这些重复的列实际上是 r0 数据库中底层表的元数据。
不幸的是,机器人管理系统的特征文件生成逻辑正是使用了这类查询来构建文件中提到的每个输入“特征”。
上述查询会返回一个类似下图所示的结果集(简化示例):

代码块示例
然而,由于授予了用户额外的权限,响应现在包含了 r0 模式的所有元数据,这使得响应中的行数增加了一倍以上,最终影响了最终输出文件中的行数(即特征数量)。
内存预分配我们代理服务上运行的每个模块均设置了上限,以防止内存无限膨胀,并通过预分配内存来优化性能。具体到这次事件,机器人管理系统对运行时可以使用的机器学习特征数量设有一个上限。目前该限制设置为 200 个,远高于我们当前约 60 个特征的使用量。同样,设置这个限制是为了性能考虑,预先为这些特征分配内存。
当包含超过 200 个特征的错误文件被分发到我们的服务器时,就触发了这个上限,导致系统产生 Panic(程序异常终止)。执行检查并导致未处理错误的 FL2 Rust 代码如下所示:

导致错误的代码
这导致了以下 panic,进而产生了 5xx 错误:thread fl2_worker_thread panicked: called Result::unwrap() on an Err value
事件期间的其他影响
其他依赖我们核心代理的系统在事件期间也受到了影响,包括 Workers KV 和 Cloudflare Access。团队在 13:04 通过为 Workers KV 应用一个补丁以绕过核心代理,成功减轻了对这些系统的影响。随后,所有依赖 Workers KV 的下游系统(例如 Access 本身)的错误率都有所下降。
Cloudflare 控制面板也受到了影响,因为它内部使用了 Workers KV,并且登录流程中部署了 Cloudflare Turnstile。
Turnstile 在这次故障中受到冲击,导致没有活动会话的客户无法登录控制面板。如下图所示,这在两个时间段内表现为可用性下降:11:30 至 13:10,以及 14:40 至 15:30。

第一个时段(11:30 至 13:10)是由于 Workers KV 受到影响,而一些控制平面和控制面板功能依赖于它。在 13:10,当 Workers KV绕过核心代理系统后,可用性得以恢复。第二个影响时段发生在恢复特征配置文件之后。积压的登录尝试开始压垮控制面板。这种积压加上重试尝试,导致延迟升高,从而降低了控制面板的可用性。通过扩展控制平面的并发能力,我们在大约 15:30 恢复了可用性。
补救措施与后续步骤
现在我们的系统已经恢复在线并正常运行,我们已经开始着手加固系统,以防范未来发生此类故障。具体措施包括:
- 以对待用户输入的严谨态度,对 Cloudflare 内部生成的配置文件加强校验。
- 为各项功能启用更多的全局紧急关闭开关。
- 消除核心转储 (core dump) 或其他错误报告可能耗尽系统资源的情况。
- 全面审查所有核心代理模块在错误条件下的故障模式。
今天是 Cloudflare 自 2019 年以来最严重的一次故障。我们曾经历过导致控制面板不可用的故障,也曾有过导致一些较新功能在一段时间内无法使用的故障。但在过去六年多的时间里,我们从未再发生过导致大部分核心流量无法通过我们网络的故障。
像今天这样的故障是不可接受的。我们的系统架构旨在实现高度的故障恢复能力,以确保流量始终能够畅通无阻。过去每一次的故障都促使我们构建了新的、更具弹性的系统。
我谨代表 Cloudflare 全体团队,为今天给互联网带来的深刻影响和不便深表歉意。
| 时间 (UTC) | 状态 | 描述 |
|---|---|---|
| 11:05 | 正常 | 部署数据库访问控制变更。 |
| 11:28 | 影响开始 | 部署到达客户环境,客户 HTTP 流量首次观察到错误。 |
| 11:32-13:05 | 团队调查 | 团队调查 Workers KV服务的流量和错误率升高。初步症状表现为 Workers KV 响应率下降,导致其他 Cloudflare 服务受到下游影响。尝试了流量调整和账户限制等缓解措施以使 Workers KV 服务恢复正常。首个自动化测试在 11:31 检测到问题,手动调查于 11:32 开始。事件响应会议在 11:35 建立。 |
| 13:37 | 聚焦回滚 | 我们确信机器人管理配置文件是事件的触发器。团队兵分多路修复服务,其中最快的方案是恢复文件的先前版本。 |
| 14:24 | 停止生成文件 | 我们确定机器人管理模块是 500 错误的源头,且由一个错误的配置文件引起。我们停止了新机器人管理配置文件的自动部署。 |
| 14:24 | 新文件版本测试完成 | 我们观察到使用旧版本配置文件后系统成功恢复,随后集中力量在全球范围内加速修复。 |
| 14:30 | 主要影响解决 | 正确的机器人管理配置文件已在全球部署,大多数服务开始正常运行。下游受影响的服务的错误率开始下降。 |
| 17:06 | 所有服务恢复 | 所有下游服务均已重启,所有操作完全恢复。影响结束。 |
134

被折叠的 条评论
为什么被折叠?



