配置指南

用 Cloudflare 给网站做安全防护(网站上线后)

发给 ChatGPT / Claude 等 AI,可更快消化本页并上手上站。

Created: 10/09/2026
Updated: 10/16/2026

用 Cloudflare 给网站做安全防护:通用上手教程

适用场景:任何经过 Cloudflare 代理的网站,不限框架。Next.js、TanStack Start、Nuxt、Remix、Astro、SvelteKit,或传统的 Node/PHP/Go 后端都适用;部署在 Cloudflare Workers/Pages,或者自己的服务器、域名 DNS 开了橙色云朵代理,也都适用。
目标:后台别人进不来、扫描器打不进来、封禁的账号登不上、数据库不被注入。
文中 www.example.com、example.com、my-worker、/admin、/api/admin、/api/auth/sign-in 都是示例,换成你自己的域名、Worker 名和路径。
全程跟着点就行,大部分操作不用改代码。


0. 先搞清楚:Cloudflare 默认帮你挡了什么

攻击类型 Cloudflare 默认能挡吗 怎么补
流量型 DDoS(大量请求把网站打挂) 能,免费版自带 不用管
简单爬虫 / 无头浏览器滥刷 不能自动拦 第 2 步 Bot Fight(可选,默认关);真人靠 Turnstile
AI 爬虫抓取内容 不能自动拦 第 2 步 AI Crawl Control(Free)
接口被刷(/api/*) 不能自动拦 第 2 步 1 条 Rate Limit(Free 限 1 条)
慢慢试密码(暴力破解登录) 不能自动识别 第 1 步 Zero Trust 保护后台 + 第 4 步限速
扫描器探测漏洞(偷 .env 等) 不能自动拦 第 3 步自定义规则
SQL 注入 免费版只覆盖少量 代码用参数化查询(第 5 步)
被封账号继续登录 跟 Cloudflare 无关 代码服务端校验(第 6 步)

一句话:Cloudflare 挡流量,后台和账号安全要靠你自己配和代码兜底。

前提:域名必须走 Cloudflare 代理(DNS 记录是橙色云朵,或者是 Worker 自定义域名)。灰色云朵(仅 DNS)的流量不经过 Cloudflare,下面的规则都不生效。


1. 用 Zero Trust 给后台加一道“门禁”(最重要)

这是什么

Zero Trust 是 Cloudflare 的免费安全服务(50 人以内免费)。其中的 Access 功能相当于在后台前面加一道门:
访问后台的请求还没到你的代码,Cloudflare 就先要求用你指定的邮箱验证一次。不在白名单里的人连后台登录页都看不到,就算密码被试出来也没用。跟你用什么框架写的没有关系。

操作步骤

第一步:开通
Cloudflare 控制台左侧进入 Zero Trust(新版叫 Cloudflare One),按提示开通免费计划。

第二步:确认登录方式
左侧 Integrations → Identity providers,确认有 One-time PIN(邮箱一次性验证码),一般默认就有。这是最简单的方式,不用去 Google Cloud 建 OAuth。

第三步:新建 Access 应用

  1. 左侧 Access controls → Applications → Add an application。
  2. 选 Self-hosted and private,下面的小标签选 Public DNS(网站用自己的域名时选这个;Workers 选项只用于保护 workers.dev 地址)。
  3. 点 Continue with Self-hosted and private。

第四步:填受保护的地址(Destinations → Public hostnames)
Subdomain 填 www(或你的子域名),Domain 选你的域名,Path 填后台路径。点 Add public hostname 逐行添加。先列出你项目里所有能进后台的地址,常见的有:

admin          # 后台页面
api/admin      # 后台接口

Path 填 admin 会连同下面所有子路径(如 /admin/users)一起保护。
多语言站点要注意:如果用了 /zh/admin、/en/admin 这种语言前缀路由(next-intl、TanStack Router 的 $locale、Nuxt i18n 等都可能这样),每个语言前缀都要单独加一行,例如 zh/admin、en/admin、ja/admin。可以在无痕窗口挨个试一下哪些地址能打开后台。
后台用独立子域名更省事:比如 admin.example.com,Subdomain 填 admin、Path 留空就保护了整个子域。
如果不带 www 的 example.com 也能直接打开网站(而不是跳转到 www),把上面的路径用空 Subdomain 再加一遍。

中间的 "browser-based RDP, SSH, or VNC" 保持 Off。

第五步:设置谁能进(Access policies)
新建一条 policy:

  • Policy name:only-me
  • Action:Allow
  • 规则:Include → Emails,填你自己的邮箱(可以填多个)

保存。

验证

  1. 打开无痕窗口访问 https://www.example.com/admin,应该先出现 Cloudflare Access 登录页。
  2. 不要点上面的 "Cloudflare" 按钮(那是用 Cloudflare 账号登录,会带上 Cloudflare 账号的邮箱),直接在下面 Email 框手动输入白名单邮箱,点 Send login code。
  3. 收到 6 位验证码填进去,才会进入网站自己的登录页。
  4. 换一个不在白名单的邮箱试:页面同样显示“已发送”(这是故意的,不暴露谁有权限),但实际收不到验证码。
  5. 也可以用命令行检查,返回 302 跳到 xxx.cloudflareaccess.com 就说明保护上了:
    curl -sI https://www.example.com/admin | grep -i location
    

常见问题

  • Gmail 别名不算同一个邮箱:Cloudflare 按字面完全匹配。想用哪个邮箱进,就把哪个加进白名单。
  • 不要把前台页面和登录接口(如 /api/auth)加进去,否则普通用户就登录不了。
  • 前后端分离的项目:后台接口如果在另一个域名(如 api.example.com/admin),那个域名的路径也要加。

别忘了:关掉绕过 Cloudflare 的后门

Access 只保护你填的域名,其他能直接访问到网站的地址都是后门:

  • 部署在 Workers / Pages:默认还有 xxx.workers.dev 或 xxx.pages.dev 地址。进入 Workers & Pages → 你的项目 → Settings → Domains & Routes,把 workers.dev 和 Preview URLs 都关掉。Pages 项目的预览地址可以单独再加一个 Access 应用保护。
  • 部署在自己的服务器:源站 IP 如果能直接访问,别人可以绕过 Cloudflare。服务器防火墙只放行 Cloudflare 的 IP 段,或者改用 Cloudflare Tunnel。

2. Free 域名实用防爬三件套(不另收费 · 人工与浏览器 AI 通用)

最高原则:宁可漏拦,不可误伤正常用户。 参数一律偏松;拿不准就跳过该项或保持 Allow。
费用说明:域名套餐 Free 就能开,不另收费。Workers Paid $5 与站点 Security 无关,不必升级 Pro。
Pro 才有的 Super Bot Fight Mode、更多 Rate Limit,本步骤不要开、不要升级。
目标域名示例:www.example.com(请替换为你自己的域名)。
前提:DNS 橙色云朵。灰色云朵则下列设置不生效。

给操作者 / 浏览器 AI 的总指令(可整段复制)

你正在 Cloudflare Dashboard 为域名 www.example.com 配置 Free 防爬。
硬约束(必须遵守):

  1. 禁止误伤正常真人用户:正常打开首页、浏览内容、登录、支付等相关业务页面必须畅通;出现验证码死循环、整站打不开、正常 API 被 429/Block,立刻回滚该项(关掉或放宽)。
  2. 只做下面 B → C → A(A 可选);不要升级套餐;不要改 Zero Trust / Access;不要新建第 2 条 Rate Limit。
  3. 搜索引擎爬虫(Googlebot、Bingbot、DuckDuckBot 等)一律 Allow,不要 Block。
  4. Rate Limit 必须用偏松频率;动作优先 Managed Challenge(没有再用 Block);缓解时间选最短。
  5. Bot Fight Mode 先不要开;仅当 B、C 完成后且自检通过,再确认是否开启 A;未明确要求「开 Bot Fight」则跳过 A。
  6. 每做完一项用中文回报:「已完成 / 已存在跳过 / 因防误伤跳过 / 失败原因」。

B. 配置 AI Crawl Control(优先做 · 对真人几乎无感)

  1. 打开 https://dash.cloudflare.com,进入域名 example.com。
  2. 左侧进入 AI Crawl Control(或 Manage AI crawlers;文案以 Dashboard 为准)。
  3. 只 Block「明确用于 AI 训练/抓取的爬虫」,例如列表里的:GPTBot、ClaudeBot、Bytespider、Google-Extended、CCBot、Amazonbot(若标注为 AI)、meta-externalagent 等。
  4. 以下必须保持 Allow(禁止改成 Block):
    • Googlebot、Googlebot-Image、Bingbot、Slurp、DuckDuckBot、Baiduspider、YandexBot 等常规搜索引擎
    • 任何名称含 search / seo、或说明为「search indexing」的条目
    • 看不清用途的条目 → 保持默认/Allow,不要 Block
  5. 不要对「Verified bots」或「Good bots」一键全拦。
  6. 同页若有 Block AI bots:可以开(只针对已识别 AI bot)。AI Labyrinth 若说明会迷惑爬虫且不影响真人,可开;若提示可能影响正常抓取/不确定,则跳过。
  7. 保存;若弹窗要求升级 Pro → 取消,不要升级。

完成标准:仅 AI 训练类爬虫为 Block;所有搜索引擎类为 Allow;未升级套餐。


C. 新建 1 条 Rate Limit(盯 /api/ · 偏松,防刷不防人)

Free 只能 1 条 Rate Limit。已有规则则先读内容:已覆盖 /api 且偏松 → 跳过;过严(如 10 秒内 >5)→ 放宽到下表,勿再新建第二条。

  1. Security → Security rules → Create rule,类型 Rate limiting rules(不是 Custom / Managed)。
  2. 按表填写(字段名略有差异时选最接近的;禁止选过严数值):
项 建议值(防误伤)
Rule name api-rate-limit-soft
匹配条件 URI Path starts with /api/
排除(若界面有) Verified Bot / 已知良好爬虫 → 不计入或跳过;没有该字段就忽略
计数维度 同一 IP
频率 10 秒内超过 100 次(或 Free 界面里 ≥100 的最松档;若最高只能选更小,选允许的最大值,并在回报里写明实际值)
Action 优先 Managed Challenge;没有则 Challenge;再没有才用 Block
缓解时长 最短(如 10 秒)
  1. 禁止把匹配改成整站 / 或所有路径——只会误伤静态页与正常浏览。
  2. 禁止对 /_next/、静态资源建限流(本条也不要匹配它们)。
  3. Deploy。不要再建第 2 条。

完成标准:仅 1 条、匹配 /api/、频率偏松(约 10s/100+)、动作非永久封禁风格。


A. Bot Fight Mode(可选 · 默认跳过,易误伤)

Free 的 Bot Fight Mode 整站对「疑似简单机器人」发计算挑战,无法按路径白名单跳过,隐私浏览器、公司网、部分正常用户可能被反复验证。
默认:跳过,保持 Off。 仅当明确需要开启时才执行下面步骤。

  1. Security → Bots,确认当前是 Bot Fight Mode(不是 Super Bot Fight)。
  2. 打开 On。
  3. 不要开 Super Bot Fight(需 Pro)。
  4. 打开后立刻用无痕窗口访问首页;若出现反复人机验证、页面打不开 → 立即改回 Off,并记录「因误伤已关闭」。

完成标准:要么保持 Off(推荐),要么 On 且无痕访问首页一次成功、无死循环验证。


自检(必须做;任一失败则回滚对应项)

用无痕窗口(不要带管理员 Cookie)依次访问,全部应成功(可正常出页面,无 Cloudflare 死循环):

  1. https://www.example.com/ 首页
  2. 任意一个详情页(站内点进一个内容或商品页即可)
  3. 打开登录弹窗/登录页,确认能看到登录 UI(不必真的登录成功)

若 C 导致正常操作被拦:把频率改到界面最大,或暂时 Disable 该 Rate Limit 规则并排查。
若 A 导致验证死循环:立刻 Off Bot Fight Mode。
B 一般不影响真人;若误伤搜索收录,把对应爬虫改回 Allow。

与业务层的关系(本步不要改代码)

层 内容
本步 Cloudflare AI Crawl Control(必做)+ 偏松 /api/ Rate Limit(必做)+ Bot Fight(默认不做)
业务限流 应用内接口限流,代码已有则不动
Turnstile 登录/关键操作人机验证,已有则不动

真人交互主要靠 Turnstile + 业务限流;Cloudflare 本步只挡明显 AI 爬虫和极端刷接口,参数必须偏松。


3. 用自定义规则拦截漏洞扫描器

为什么要做

日志里经常能看到这类请求,1 分钟内试几十个路径:

/userfiles?path=../../../../.env
/api/fs/read?path=/app/.env
/api/proc/self/environ
/graphql  /inngest  /api/fs/exec

这是自动化扫描器在找漏洞、偷 .env。如果项目里没有这些路径,只会 404,不会泄露东西,但会刷屏日志、浪费请求额度。直接在 Cloudflare 门口拦掉。

操作步骤

  1. Cloudflare 控制台进入你的域名,左侧 Security → Security rules(旧版叫 WAF)。
  2. 点 Create rule,类型选 Custom rules(不是 Rate limiting,也不是 Managed rules)。
  3. Rule name 填 block-scanners。
  4. 点 Edit expression 切到手写模式,整段粘贴:
(lower(url_decode(http.request.uri)) contains ".env")
or (lower(url_decode(http.request.uri)) contains "/proc/self")
or (lower(url_decode(http.request.uri)) contains "../")
or (lower(url_decode(http.request.uri)) contains "/.git")
or (lower(url_decode(http.request.uri)) contains "/wp-admin")
or (lower(url_decode(http.request.uri)) contains "/wp-login")
  1. Choose action 选 Block,点 Deploy。

说明:http.request.uri 同时包含路径和 ? 后面的参数;url_decode 把 %2F 还原成 /,防止编码绕过;lower 防止大小写绕过。
按自己的项目删改:如果你的站本身就是 WordPress,去掉 /wp-admin、/wp-login 两行;如果有合法路径包含 .env 字样(很少见),把那行改得更精确。加规则前先想一下有没有正常页面会被误伤。

验证

浏览器打开 https://www.example.com/userfiles?path=../.env,看到 Cloudflare 拦截页就生效了。正常页面不受影响。


4. 防暴力破解:给登录接口限速(可选与配合)

注意:Free 只有 1 条 Rate Limit。若已按 第 2 步 建了盯 /api/* 的规则,不要再新建第二条;登录暴力破解已被 /api/ 覆盖时,本节能跳过。只有在你把唯一那条规则改成「只限登录路径、更严」时,才按下面改匹配条件。

Cloudflare 不会自动识别“慢慢试密码”。如果后台已经用第 1 步保护,前台登录可以再加一层:

  1. Security → Security rules → Create rule,类型选 Rate limiting rules。
  2. 条件:URI Path 以你的登录接口开头。常见写法:better-auth 是 /api/auth/sign-in,NextAuth/Auth.js 是 /api/auth/callback/credentials,自己写的就按实际路径。不确定的话,打开浏览器开发者工具的 Network 面板,登录一次看请求地址。
  3. 频率:同一 IP 每 10 秒超过 5 次就 Block 一段时间(免费版可选的数值有限,按界面能选的来)。
  4. Deploy。

如果代码里自己做了按 IP 限流,但计数存在内存里,在 Workers 或多实例部署下各实例各算各的,只能挡一部分,所以门口这层更可靠。


5. SQL 注入:代码是真正的防线

原则

用户输入只能以参数形式传给数据库,不能拼进 SQL 字符串。主流 ORM 和查询构建器(Drizzle、Prisma、Kysely、TypeORM、Sequelize 等)默认都是参数化的,只要不用它们的“原始 SQL”接口拼字符串就安全。

写代码的规矩

// 正确:参数化,安全
db.select().from(user).where(eq(user.email, email));          // Drizzle
prisma.user.findUnique({ where: { email } });                  // Prisma
sql`select * from user where email = ${email}`;               // 模板标签里的变量会被绑定成参数
env.DB.prepare('select * from user where email = ?').bind(email); // D1 / SQLite 原生

// 错误:字符串拼接,危险
sql.raw(`SELECT * FROM user WHERE email = '${email}'`);        // Drizzle
prisma.$queryRawUnsafe(`SELECT * FROM user WHERE email = '${email}'`); // Prisma
env.DB.prepare(`select * from user where email = '${email}'`);

自查方法

在项目里全局搜这些关键词,逐个看有没有把用户输入拼进去:

sql.raw   $queryRawUnsafe   $executeRawUnsafe   .prepare(`   query(`

搜不到,或者搜到的地方都没有拼接用户输入,就没问题。

Cloudflare 这边

  • 免费版自带 Free Managed Ruleset,只覆盖少量高危漏洞。到 Security rules → Managed rules 确认它是开启的。
  • 完整的 SQL 注入 / XSS 规则(Cloudflare Managed Ruleset、OWASP 规则集)需要 Pro 及以上。
  • 只要代码层是参数化的,WAF 是额外保险,不必专门为此升级。

6. 封禁账号:必须在服务端拦截

常见错误

很多项目的封禁只在前端做:登录页先调一个接口问“这个邮箱是否被封”,被封就弹提示。但服务端不检查,只要有人绕过页面直接调登录接口,密码正确就能登进去;如果这个账号还挂着管理员角色,后台照样能用。而且那个“是否被封”的接口谁都能调,可以拿来探测哪些邮箱注册过。

正确做法(不管用什么框架和认证库)

  • 登录时拦:账号密码、第三方登录(Google/GitHub)、验证邮件后自动登录,所有创建会话的入口都要在服务端检查封禁,被封就拒绝,不发登录 cookie。认证库一般有钩子可以挂,例如 better-auth 的 databaseHooks.session.create.before、Auth.js 的 signIn 回调,自己写的就在签发 token 前检查。
  • 已登录的也要拦:封禁时同时删掉该用户所有会话;服务端“获取当前用户”的公共函数里,被封账号一律当作未登录。这样被封的管理员也进不了后台页面,调不了后台接口。
  • 防探测:登录前不要提供“查询某邮箱是否被封”的公开接口,只有密码验证通过后才返回封禁原因。
  • 管理员不能封禁自己,避免误操作把自己锁在外面。

管理员的操作建议

  1. 后台主账号用第三方登录(如 Google),并设为超管。Google 账号自带两步验证,比密码安全。
  2. 不再使用的邮箱密码管理员账号:先移除它的管理员角色,再封禁。
  3. 部署后验证:用被封账号输对密码登录,应该被拦住;用主账号确认能正常进后台。

7. 看日志:怎么排查攻击与异常

Workers 项目在 Workers & Pages → 你的项目 → Logs 查看或导出日志;自己服务器的项目看服务器日志,或在 Cloudflare 的 Security → Events 看被拦下的请求。

现象 结论
1 分钟内大量请求不存在的路径(.env、/graphql、/proc/self 等),全是 404 扫描器,不是 bug,用第 3 步规则拦掉
请求带 ?_rsc=xxx(Next.js)或 /_serverFn/(TanStack Start)等框架内部参数 框架自己的页面跳转、预取或服务端函数调用,是正常用户在操作,不是攻击
Network connection lost 偶尔一次 Cloudflare 网络偶发中断,不用管
同一个正常页面反复报错 代码问题,需要看完整错误原文

日志里只有几行 at worker.js:xxxx 的堆栈时,错误原文往往在同一个 requestId 下的另一条日志里。Workers 项目也可以用 npx wrangler tail 你的Worker名 实时看线上日志,再去页面上复现。


检查清单

  • [ ] 域名 DNS 是橙色云朵(走 Cloudflare 代理)
  • [ ] 第 1 步:Zero Trust 已开通,Access 应用保护了所有后台页面和后台接口路径(含各语言前缀)
  • [ ] 第 1 步:Access policy 只放行自己的邮箱,已用无痕窗口验证
  • [ ] 第 1 步:workers.dev、pages.dev、Preview URLs 已关闭,或源站 IP 已限制只允许 Cloudflare 访问
  • [ ] 第 2 步:AI Crawl Control 只 Block AI 训练爬虫,搜索引擎保持 Allow
  • [ ] 第 2 步:仅 1 条偏松 Rate Limit(约 10s/100+,匹配 /api/);无痕访问首页/详情页/登录正常
  • [ ] 第 2 步:Bot Fight Mode 默认 Off;若开启则无痕验证无死循环(未开 Super Bot Fight / 未升级 Pro)
  • [ ] 第 3 步:自定义规则 block-scanners 已部署并验证
  • [ ] 第 4 步:(可选)登录接口限速——仅当未用第 2 步的 /api/ 规则时再单独配置
  • [ ] 第 5 步:Free Managed Ruleset 已开启,代码已检查无原生 SQL 字符串拼接
  • [ ] 第 6 步:所有登录入口和“获取当前用户”都在服务端检查封禁,旧管理员账号已降权
  • [ ] 第 7 步:掌握查看 Security Events 与实时排查日志的方法