<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>星影的博客</title><description>No description</description><link>https://starshadow.cc/</link><language>zh_CN</language><item><title>用 Cloudflare、Caddy 和 UFW 保护源站：只接受可信回源</title><link>https://starshadow.cc/posts/cloudflare-origin-protection/</link><guid isPermaLink="true">https://starshadow.cc/posts/cloudflare-origin-protection/</guid><description>记录一次在 AWS EC2 + Docker + Caddy 上做源站保护的思路：Cloudflare IP 白名单、ufw-docker、Full Strict、Host 校验和 Authenticated Origin Pulls。</description><pubDate>Sat, 16 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这篇是一次源站保护折腾记录。环境很普通：东京 AWS EC2，Ubuntu 24.04，1C1G，业务主要跑在 Docker 里，入口交给 Cloudflare。&lt;/p&gt;
&lt;p&gt;我的目标不是“让源站 IP 永远不泄露”。IP 可能出现在历史解析、扫描记录、日志、误配置里。真正要做的是：即使别人知道源站 IP，也尽量不能绕过 Cloudflare 直接访问网站。&lt;/p&gt;
&lt;p&gt;最终思路可以压成一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;让公网访问只走 Cloudflare，让源站同时校验来源 IP、Host、源站证书和 Cloudflare 客户端证书。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;整体链路&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./architecture.png&quot; alt=&quot;源站保护链路图&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这套保护大概分成五层：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Cloudflare DNS 开橙云，让正常访问先进入 Cloudflare。&lt;/li&gt;
&lt;li&gt;UFW 只允许 Cloudflare IP 访问源站的 &lt;code&gt;80/443&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Docker 发布端口后，用 &lt;code&gt;ufw-docker&lt;/code&gt; 把容器转发流量纳入 UFW。&lt;/li&gt;
&lt;li&gt;Caddy 使用 Cloudflare Origin CA 证书，Cloudflare SSL/TLS 模式改成 Full Strict。&lt;/li&gt;
&lt;li&gt;Caddy 严格匹配 Host，并开启 Authenticated Origin Pulls，让源站只接受 Cloudflare 带客户端证书的回源请求。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我这次为了教程演示，AWS 安全组一开始放得比较开，方便对比“保护前”和“保护后”的效果。生产环境不建议这样做，安全组也应该尽量只开放需要的端口，SSH 最好限制为自己的固定 IP。&lt;/p&gt;
&lt;h2&gt;基础环境&lt;/h2&gt;
&lt;p&gt;服务器上只需要准备几个基础组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git&lt;/code&gt;：拉取工具或项目。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker&lt;/code&gt; / &lt;code&gt;docker compose&lt;/code&gt;：跑 Caddy 和后续业务。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ufw&lt;/code&gt;：做主机防火墙。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fail2ban&lt;/code&gt;：可选。AWS 默认密钥登录时 SSH 爆破风险小一些，但装上也没坏处。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Docker 建议用官方 apt 仓库安装 Docker Engine，不要直接用 Ubuntu 源里的 &lt;code&gt;docker.io&lt;/code&gt;。这部分按官方文档走就好，文章里不展开完整命令。&lt;/p&gt;
&lt;p&gt;这次用 Caddy 做一个最小测试站点。Docker Compose 里只发布 TCP 的 &lt;code&gt;80&lt;/code&gt; 和 &lt;code&gt;443&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  caddy:
    image: caddy:2-alpine
    container_name: caddy
    restart: unless-stopped
    ports:
      - &quot;80:80&quot;
      - &quot;443:443&quot;
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./site:/srv:ro
      - ./certs:/certs:ro
      - caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_data:
  caddy_config:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我没有发布 &lt;code&gt;443:443/udp&lt;/code&gt;。Caddy 支持 HTTP/3 时会用到 UDP 443，但如果把 UDP 也暴露出来，防火墙和验证逻辑也要一起处理。为了把教程重点放在源站保护上，我先只保留 TCP。&lt;/p&gt;
&lt;p&gt;刚部署完时，直接访问源站 IP 可以看到测试页面：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./source-ip-before.png&quot; alt=&quot;源站 IP 可以直接访问 Caddy 测试页&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这就是要解决的问题：只要别人知道源站 IP，就可能绕过 Cloudflare 访问到后面的服务。&lt;/p&gt;
&lt;h2&gt;Cloudflare DNS 先接管入口&lt;/h2&gt;
&lt;p&gt;先把域名的 DNS 解析到 EC2 公网 IP，并开启橙云代理。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./cloudflare-dns.png&quot; alt=&quot;Cloudflare DNS 开启橙云&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这一步只能保证正常用户会先进入 Cloudflare，不代表源站已经安全。因为源站 IP 一旦被发现，别人仍然可以直接请求 &lt;code&gt;http://源站IP&lt;/code&gt;，或者伪造 Host / SNI 去试探源站。&lt;/p&gt;
&lt;h2&gt;UFW 只放行 Cloudflare IP&lt;/h2&gt;
&lt;p&gt;Cloudflare 官方公开了自己的 IP 段，可以用脚本定期同步到 UFW。因为这次 Caddy 跑在 Docker 里，脚本同时加普通 &lt;code&gt;allow&lt;/code&gt; 和 Docker 转发用的 &lt;code&gt;route allow&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat &amp;lt;&amp;lt; &apos;EOF&apos; &amp;gt; /root/update_cf_ips.sh
#!/bin/bash
# 专为 ufw-docker 环境设计的 Cloudflare IP 安全更新脚本

CF_V4_URL=&quot;https://www.cloudflare.com/ips-v4&quot;
CF_V6_URL=&quot;https://www.cloudflare.com/ips-v6&quot;

echo &quot;开始更新 Cloudflare IPv4 规则...&quot;
for ip in $(curl -s $CF_V4_URL); do
    # 允许访问宿主机本身
    ufw allow proto tcp from $ip to any port 80 comment &apos;CF-HOST&apos;
    ufw allow proto tcp from $ip to any port 443 comment &apos;CF-HOST&apos;
    # 允许流量转发到 Docker 容器 (ufw-docker 必须)
    ufw route allow proto tcp from $ip to any port 80 comment &apos;CF-DOCKER&apos;
    ufw route allow proto tcp from $ip to any port 443 comment &apos;CF-DOCKER&apos;
done

echo &quot;开始更新 Cloudflare IPv6 规则...&quot;
for ip in $(curl -s $CF_V6_URL); do
    ufw allow proto tcp from $ip to any port 80 comment &apos;CF-HOST&apos;
    ufw allow proto tcp from $ip to any port 443 comment &apos;CF-HOST&apos;
    ufw route allow proto tcp from $ip to any port 80 comment &apos;CF-DOCKER&apos;
    ufw route allow proto tcp from $ip to any port 443 comment &apos;CF-DOCKER&apos;
done

echo &quot;重新加载 UFW...&quot;
ufw reload
echo &quot;更新完成！&quot;
EOF

chmod +x /root/update_cf_ips.sh
(crontab -l 2&amp;gt;/dev/null | grep -v &quot;/root/update_cf_ips.sh&quot;; echo &quot;0 4 * * 1 /bin/bash /root/update_cf_ips.sh &amp;gt;&amp;gt; /var/log/update_cf_ips.log 2&amp;gt;&amp;amp;1&quot;) | crontab -
/root/update_cf_ips.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段命令会创建 &lt;code&gt;/root/update_cf_ips.sh&lt;/code&gt;，每周一凌晨 4 点自动执行，并立即手动跑一次。&lt;code&gt;CF-HOST&lt;/code&gt; 负责宿主机本身的 &lt;code&gt;80/443&lt;/code&gt;，&lt;code&gt;CF-DOCKER&lt;/code&gt; 负责转发到 Docker 容器的流量；脚本只追加规则并 reload，不会重置 UFW，也不会覆盖 &lt;code&gt;/etc/ufw/after.rules&lt;/code&gt;。正式长期使用时，可以再加上错误处理、日志和旧规则清理，避免脚本失败时悄悄跳过。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
远程服务器启用 UFW 之前，先确认 SSH 已经放行。最好保留当前 SSH 会话，再开一个新终端测试能否重新登录，避免把自己锁在外面。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;到这里看起来已经像是“80/443 只允许 Cloudflare IP”了，但如果服务跑在 Docker 里，还没结束。&lt;/p&gt;
&lt;h2&gt;把 Docker 转发流量纳入 UFW&lt;/h2&gt;
&lt;p&gt;Docker 发布端口时会改 iptables / NAT 规则。结果是：你在 UFW 里看见 &lt;code&gt;80/443&lt;/code&gt; 只允许 Cloudflare，但外部直接访问源站 IP 可能仍然能打到容器。&lt;/p&gt;
&lt;p&gt;这也是这篇文章里最容易踩坑的地方。解决办法是把 Docker 转发流量也纳入 UFW 管理，我这里用的是 &amp;lt;a href=&quot;https://github.com/chaifeng/ufw-docker&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;ufw-docker&amp;lt;/a&amp;gt;。&lt;/p&gt;
&lt;p&gt;这里不建议把 Docker 转发规则硬塞进 Cloudflare IP 更新脚本，因为 Docker 网络、容器名和要暴露的端口在每台机器上都不一样。更稳妥的做法是按 &lt;code&gt;ufw-docker&lt;/code&gt; 官方 README 先安装规则，再用它提供的 &lt;code&gt;check&lt;/code&gt;、&lt;code&gt;status&lt;/code&gt;、&lt;code&gt;allow&lt;/code&gt;、&lt;code&gt;reload&lt;/code&gt; 等命令确认 Docker 转发流量已经进入 UFW 管理。&lt;/p&gt;
&lt;p&gt;也就是说，Cloudflare IP 脚本维护的是“哪些来源 IP 可以访问源站 &lt;code&gt;80/443&lt;/code&gt;”；Docker 这一层由 &lt;code&gt;ufw-docker&lt;/code&gt; 接管。只要你的 IP 更新脚本没有 &lt;code&gt;ufw reset&lt;/code&gt;，也没有覆盖 &lt;code&gt;/etc/ufw/after.rules&lt;/code&gt;，一般不会把 &lt;code&gt;ufw-docker&lt;/code&gt; 的接入配置冲掉。真正需要重新处理的是：你重置了 UFW、手动改了 after rules、或者新增/删除了 Docker 网络。&lt;/p&gt;
&lt;p&gt;修复后，再直接访问源站 IP，浏览器应该打不开，或者超时：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./source-ip-after.png&quot; alt=&quot;源站 IP 直接访问失败&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这时通过 Cloudflare 域名访问仍然正常，说明防火墙没有误伤正常链路。&lt;/p&gt;
&lt;h2&gt;源站 TLS：Origin CA + Full Strict&lt;/h2&gt;
&lt;p&gt;下一步是把 Cloudflare 到源站的连接从临时测试状态切到 Full Strict。&lt;/p&gt;
&lt;p&gt;Cloudflare Origin CA 的作用是给源站提供一个 Cloudflare 信任的证书。Caddy 使用这张证书，Cloudflare 回源时校验证书有效且主机名匹配。&lt;/p&gt;
&lt;p&gt;证书文件放在源站，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/opt/caddy/certs/origin.pem
/opt/caddy/certs/origin.key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;私钥权限要收紧：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod 600 /opt/caddy/certs/origin.key
chmod 644 /opt/caddy/certs/origin.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后 Caddy 只响应自己的正式域名，其他 Host 直接返回 &lt;code&gt;403&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://aaa.454849.xyz {
  redir https://aaa.454849.xyz{uri} 301
}

https://aaa.454849.xyz {
  tls /certs/origin.pem /certs/origin.key
  root * /srv
  file_server
}

:80 {
  respond &quot;Forbidden&quot; 403
}

:443 {
  tls /certs/origin.pem /certs/origin.key
  respond &quot;Forbidden&quot; 403
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cloudflare 后台里把 SSL/TLS encryption mode 改成 &lt;code&gt;Full (strict)&lt;/code&gt;。这里不要继续用 &lt;code&gt;Flexible&lt;/code&gt;，否则 Cloudflare 到源站仍然不是严格 HTTPS 回源，后面的 AOP 也不适合放在这个状态下使用。&lt;/p&gt;
&lt;p&gt;Host 校验生效后，直接请求 &lt;code&gt;127.0.0.1&lt;/code&gt; 或伪造别的域名，会得到 &lt;code&gt;403&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./host-check-403.png&quot; alt=&quot;Caddy 对非法 Host 返回 403&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这一层解决的是“伪造 Host 访问源站”的问题。它还不是 mTLS，不能证明请求一定来自 Cloudflare。&lt;/p&gt;
&lt;h2&gt;再加一层 mTLS：Authenticated Origin Pulls&lt;/h2&gt;
&lt;p&gt;Cloudflare 的 Authenticated Origin Pulls，简称 AOP，可以让 Cloudflare 回源时带上客户端证书。源站 Caddy 再验证这个客户端证书，验证不通过就直接在 TLS 阶段拒绝。&lt;/p&gt;
&lt;p&gt;这里有两个证书概念，别混在一起：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Origin CA 证书：装在源站上，给 Cloudflare 校验证源站身份。&lt;/li&gt;
&lt;li&gt;AOP 客户端证书：由 Cloudflare 回源时提供，给源站校验“这次请求来自 Cloudflare”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先下载 Cloudflare Global AOP CA 证书：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -fsSL https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem \
  -o /opt/caddy/certs/cloudflare-origin-pull-ca.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后把 Caddy 的 TLS 配置改成要求客户端证书：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(origin_tls) {
  tls /certs/origin.pem /certs/origin.key {
    client_auth {
      mode require_and_verify
      trust_pool file /certs/cloudflare-origin-pull-ca.pem
    }
  }
}

http://aaa.454849.xyz {
  redir https://aaa.454849.xyz{uri} 301
}

https://aaa.454849.xyz {
  import origin_tls
  root * /srv
  file_server
}

:80 {
  respond &quot;Forbidden&quot; 403
}

:443 {
  import origin_tls
  respond &quot;Forbidden&quot; 403
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启 Caddy 后，先不要急着开 Cloudflare 后台。直接从源站本机模拟访问，应该失败：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -kI --resolve aaa.454849.xyz:443:127.0.0.1 https://aaa.454849.xyz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果看到类似 &lt;code&gt;tlsv13 alert certificate required&lt;/code&gt;，说明 Caddy 已经在要求客户端证书。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./aop-direct-test.png&quot; alt=&quot;没有客户端证书时，直接访问源站 HTTPS 失败&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最后到 Cloudflare 后台开启 Global Authenticated Origin Pulls：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./cloudflare-aop.png&quot; alt=&quot;Cloudflare 开启 Global Authenticated Origin Pulls&quot; /&gt;&lt;/p&gt;
&lt;p&gt;开启后，正常域名访问依然走 Cloudflare，Cloudflare 带客户端证书回源，所以可以访问；直接打源站 IP 或本地伪造解析，因为没有 Cloudflare 客户端证书，会在 TLS 阶段失败。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
Global AOP 用的是 Cloudflare 共享客户端证书，它证明“请求来自 Cloudflare 网络”，不是证明“请求来自我的这个 Cloudflare zone”。不过普通套餐下，别人通常不能随意指定回源端口、改写 Host/SNI 再打到你的源站；这类更强的回源改写能力一般需要 Enterprise 或更高权限配置。对个人站来说，Global AOP + 严格 Host 校验 + Cloudflare IP 白名单已经够用了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;验证清单&lt;/h2&gt;
&lt;p&gt;我最后按这个顺序检查：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;https://aaa.454849.xyz&lt;/code&gt; 正常打开。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http://aaa.454849.xyz&lt;/code&gt; 会跳转到 HTTPS。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http://源站IP&lt;/code&gt; 无法直接打开。&lt;/li&gt;
&lt;li&gt;伪造 Host 请求源站，不会返回真实站点内容。&lt;/li&gt;
&lt;li&gt;没有 Cloudflare 客户端证书时，直接打源站 HTTPS 会失败。&lt;/li&gt;
&lt;li&gt;UFW 里能看到 Cloudflare IP 白名单，&lt;code&gt;ufw-docker&lt;/code&gt; 也能确认 Docker 转发流量已经接入 UFW。&lt;/li&gt;
&lt;li&gt;自动更新 Cloudflare IP 的 systemd timer 正常运行。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;验证时不要只看“能不能访问域名”。源站保护最重要的是反向测试：绕过 Cloudflare 的路径是不是被挡住了。&lt;/p&gt;
&lt;h2&gt;限制和取舍&lt;/h2&gt;
&lt;p&gt;这套方案不是万能的。&lt;/p&gt;
&lt;p&gt;首先，Cloudflare IP 白名单依赖 IP 段同步，脚本要能稳定更新。如果脚本只是追加 Cloudflare allow 规则并 &lt;code&gt;ufw reload&lt;/code&gt;，通常不会影响 &lt;code&gt;ufw-docker&lt;/code&gt;；如果脚本用了 &lt;code&gt;ufw reset&lt;/code&gt;，或者覆盖了 &lt;code&gt;/etc/ufw/after.rules&lt;/code&gt;，才需要把 SSH、Cloudflare 主机规则和 &lt;code&gt;ufw-docker&lt;/code&gt; 接入配置一起恢复回来。&lt;/p&gt;
&lt;p&gt;其次，Global AOP 仍然是共享信任。它适合个人站和普通项目，因为普通 Cloudflare 配置下，攻击者很难同时做到“让 Cloudflare 回源到你的 IP、伪造正确 Host/SNI、并绕过你的源站校验”。如果你的安全模型要求“只有我这个 zone 可以访问源站”，再考虑自定义 AOP。&lt;/p&gt;
&lt;p&gt;另外，教程里的 AWS 安全组开放是为了演示对比。正式服务器不要这样配置。安全组、系统防火墙、Caddy Host 校验、mTLS 都是不同层的保护，能叠加就叠加。&lt;/p&gt;
&lt;p&gt;最后，实际部署时通常让 Caddy 作为唯一公网入口，绑定宿主机的 &lt;code&gt;80/443&lt;/code&gt; 就够了。其它业务容器不需要直接绑定宿主机公网端口，可以只监听本机或 Docker 内网，再由 Caddy 反向代理过去。即使已有服务绑定了宿主机端口，也要用 UFW / ufw-docker 管住入口，不要让它绕过 Cloudflare 暴露在公网。&lt;/p&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&amp;lt;a href=&quot;https://developers.cloudflare.com/fundamentals/concepts/cloudflare-ip-addresses/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;Cloudflare IP ranges&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;li&gt;&amp;lt;a href=&quot;https://developers.cloudflare.com/ssl/origin-configuration/ssl-modes/full-strict/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;Cloudflare Full (strict) SSL/TLS mode&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;li&gt;&amp;lt;a href=&quot;https://developers.cloudflare.com/ssl/origin-configuration/authenticated-origin-pull/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;Cloudflare Authenticated Origin Pulls&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;li&gt;&amp;lt;a href=&quot;https://docs.docker.com/engine/install/ubuntu/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;Docker Engine on Ubuntu&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;li&gt;&amp;lt;a href=&quot;https://caddyserver.com/docs/caddyfile/directives/tls&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;Caddy TLS directive&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;li&gt;&amp;lt;a href=&quot;https://github.com/chaifeng/ufw-docker&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;ufw-docker&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>用 Cloud Mail + Resend 搭建域名邮箱：收信、发信和 Gmail 代发</title><link>https://starshadow.cc/posts/domain-email-cloud-mail-resend-gmail/</link><guid isPermaLink="true">https://starshadow.cc/posts/domain-email-cloud-mail-resend-gmail/</guid><description>使用 Cloud Mail、Resend 和 Gmail 搭建自己的域名邮箱，实现收信、发信和 Gmail 代发。</description><pubDate>Wed, 13 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;域名邮箱最实用的地方，是把不同网站、不同服务的注册邮箱隔离开来。比如给购物、订阅、测试项目、临时服务分别准备不同的邮箱地址，后续如果某个地址开始收到垃圾邮件，也更容易判断泄露来源并停用。&lt;/p&gt;
&lt;p&gt;这篇记录的是我目前使用的一套轻量方案：用 Cloud Mail 搭建邮箱面板和收信逻辑，用 Resend 负责 SMTP 发信，再把 Gmail 作为可选的收发客户端。它不适合替代企业邮箱，但很适合作为个人域名邮箱、注册别名邮箱、项目测试邮箱来用。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
批量管理账号邮箱别名时，仍然要遵守对应网站的服务条款。有些平台会限制自定义域名邮箱，特别是免费域名、低价域名、新注册域名，可能会触发风控或直接不支持。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;整体方案&lt;/h2&gt;
&lt;p&gt;这套方案可以拆成两条链路：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;收信：外部服务发邮件到你的域名邮箱，Cloudflare / Cloud Mail 接收后，转发到 Cloud Mail 面板、Telegram 或 Gmail。&lt;/li&gt;
&lt;li&gt;发信：Cloud Mail 或 Gmail 通过 Resend 的 SMTP 服务，以你的域名邮箱身份发出邮件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./mail-flow-architecture.svg&quot; alt=&quot;域名邮箱收发架构图&quot; /&gt;&lt;/p&gt;
&lt;p&gt;需要准备：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个已经接入 Cloudflare 的域名&lt;/li&gt;
&lt;li&gt;一个 Cloudflare 账号&lt;/li&gt;
&lt;li&gt;一个 GitHub 账号，用来部署 Cloud Mail&lt;/li&gt;
&lt;li&gt;一个 Resend 账号，用来发信&lt;/li&gt;
&lt;li&gt;一个 Gmail 账号，可选，用来当日常收发客户端&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cloud Mail 项目地址：&amp;lt;a href=&quot;https://github.com/maillab/cloud-mail&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;maillab/cloud-mail&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;Cloud Mail 官方部署文档：&amp;lt;a href=&quot;https://doc.skymail.ink/guide/dashboard.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;界面部署&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;h2&gt;先完成 Cloud Mail 收信&lt;/h2&gt;
&lt;p&gt;Cloud Mail 的基础部署建议直接按官方文档走。核心步骤是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fork 或克隆 Cloud Mail 项目到自己的 GitHub。&lt;/li&gt;
&lt;li&gt;在 Cloudflare Workers &amp;amp; Pages 里从 GitHub 导入项目。&lt;/li&gt;
&lt;li&gt;按文档配置环境变量、D1、KV 和自定义域。&lt;/li&gt;
&lt;li&gt;初始化数据库，注册管理员账号并登录 Cloud Mail 面板。&lt;/li&gt;
&lt;li&gt;在 Cloudflare 邮件路由里配置目标地址或 Workers，让邮件能进入 Cloud Mail。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;做到这里，通常已经可以完成收信。但如果想从 Cloud Mail 或 Gmail 发出域名邮箱邮件，还需要继续配置 Resend。&lt;/p&gt;
&lt;h2&gt;配置 Resend 发信&lt;/h2&gt;
&lt;p&gt;打开 &amp;lt;a href=&quot;https://resend.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;Resend&amp;lt;/a&amp;gt; 官网，注册并登录账号，然后进入 &lt;code&gt;Domains&lt;/code&gt; 页面，点击 &lt;code&gt;Add domain&lt;/code&gt; 添加自己的域名。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./step-01-resend-domain.png&quot; alt=&quot;Resend 的 Domains 页面&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Resend 会要求你添加 DNS 记录来验证域名。按页面提示跳转到 Cloudflare，添加对应解析记录，等待状态变成 &lt;code&gt;Verified&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
我这里直接添加主域名。如果你更在意发信信誉隔离，也可以用类似 &lt;code&gt;mail.example.com&lt;/code&gt; 这样的子域名。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Resend 自动添加的 DNS 记录通常会覆盖 SPF / DKIM 等发信认证，但不会替你补主域名的 DMARC。为了防止别人伪造你的域名发信，建议在 Cloudflare DNS 里手动加一条：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;th&gt;代理状态&lt;/th&gt;
&lt;th&gt;TTL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_dmarc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;v=DMARC1;p=reject&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;仅 DNS&lt;/td&gt;
&lt;td&gt;自动&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;p=reject&lt;/code&gt; 是比较严格的策略：当邮件没有通过 SPF / DKIM 对齐时，收件方可以直接拒收。对于刚开始测试的新域名，这样比较干净；如果你的域名之前已经接入过其它发信服务，要先确认所有合法发信服务的 SPF / DKIM 都配置正确，再切到 &lt;code&gt;reject&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;域名验证完成后，进入 &lt;code&gt;API keys&lt;/code&gt; 页面，创建一个新的 API Key。这个 Key 只会完整显示一次，一定要立刻保存好。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./step-02-resend-api-key.png&quot; alt=&quot;创建 Resend API Key&quot; /&gt;&lt;/p&gt;
&lt;p&gt;回到 Cloud Mail 的系统设置，找到 &lt;code&gt;Resend Token&lt;/code&gt;，把刚才保存的 API Key 填进去并保存。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./step-03-cloud-mail-resend-token.png&quot; alt=&quot;在 Cloud Mail 中填写 Resend Token&quot; /&gt;&lt;/p&gt;
&lt;p&gt;到这里，Cloud Mail 端的收信和发信能力就基本完成了。可以先在 Cloud Mail 面板里发一封测试邮件，确认外部邮箱能正常收到。&lt;/p&gt;
&lt;h2&gt;可选：把邮件转发到 Gmail&lt;/h2&gt;
&lt;p&gt;如果你希望继续用 Gmail 作为主收件箱，可以把 Cloud Mail 收到的邮件再转发到 Gmail。&lt;/p&gt;
&lt;p&gt;先进入 Cloudflare 的邮件路由页面，在 &lt;code&gt;目标地址&lt;/code&gt; 中添加自己的 Gmail 地址，并到 Gmail 收件箱里点击 Cloudflare 发来的验证链接。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./step-04-cloudflare-target-address.png&quot; alt=&quot;在 Cloudflare 邮件路由中添加 Gmail 目标地址&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后回到 Cloud Mail，进入系统设置里的 &lt;code&gt;邮件推送&lt;/code&gt;，在 &lt;code&gt;第三方邮箱&lt;/code&gt; 中填入刚刚验证过的 Gmail 地址，启用并保存。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./step-05-cloud-mail-third-party-mailbox.png&quot; alt=&quot;在 Cloud Mail 中启用第三方邮箱推送&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在下方的转发规则里，可以指定哪些域名邮箱需要转发到 Gmail。比如只把 &lt;code&gt;admin@你的域名&lt;/code&gt; 转发过去，其它临时地址仍然留在 Cloud Mail 面板里。&lt;/p&gt;
&lt;h2&gt;可选：让 Gmail 代发域名邮箱&lt;/h2&gt;
&lt;p&gt;转发解决的是收信。如果还想直接在 Gmail 里选择域名邮箱作为发件地址，需要给 Gmail 添加 Resend 的 SMTP 信息。&lt;/p&gt;
&lt;p&gt;在电脑浏览器打开 Gmail，点击右上角设置，选择 &lt;code&gt;查看所有设置&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./step-06-gmail-settings.png&quot; alt=&quot;打开 Gmail 设置&quot; /&gt;&lt;/p&gt;
&lt;p&gt;进入 &lt;code&gt;账号和导入&lt;/code&gt;，在 &lt;code&gt;用这个地址发送邮件&lt;/code&gt; 一栏里点击 &lt;code&gt;添加其他电子邮件地址&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./step-07-gmail-send-as.png&quot; alt=&quot;Gmail 的账号和导入页面&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在弹出的窗口里填入你想显示的名称和域名邮箱地址。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./step-08-gmail-add-address.png&quot; alt=&quot;在 Gmail 中添加域名邮箱地址&quot; /&gt;&lt;/p&gt;
&lt;p&gt;下一步会要求填写 SMTP 信息。Resend 的 SMTP 参数如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SMTP 服务器：&lt;code&gt;smtp.resend.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;端口：&lt;code&gt;465&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;用户名：&lt;code&gt;resend&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;密码：你的 Resend API Key&lt;/li&gt;
&lt;li&gt;安全连接：选择 &lt;code&gt;SSL&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些信息也可以在 Resend 的 &lt;code&gt;Settings -&amp;gt; SMTP&lt;/code&gt; 页面找到。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./step-09-resend-smtp-settings.png&quot; alt=&quot;Resend 的 SMTP 信息&quot; /&gt;&lt;/p&gt;
&lt;p&gt;把 SMTP 信息填入 Gmail，点击 &lt;code&gt;添加账号&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./step-10-gmail-smtp-settings.png&quot; alt=&quot;在 Gmail 中填写 Resend SMTP 信息&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Gmail 会向这个域名邮箱发送一封确认邮件。你可以在 Cloud Mail 里复制确认链接，也可以等它转发到 Gmail 后直接点击链接完成验证。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./step-11-gmail-confirmation.png&quot; alt=&quot;Gmail 发件地址确认成功&quot; /&gt;&lt;/p&gt;
&lt;p&gt;验证成功后，回到 Gmail 设置页，按 &lt;code&gt;F5&lt;/code&gt; 刷新。然后把域名邮箱设置成默认发件地址。这样以后在 Gmail 写邮件时，就可以默认用域名邮箱发出。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./step-12-gmail-default-sender.png&quot; alt=&quot;把域名邮箱设为 Gmail 默认发件地址&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;验证一下&lt;/h2&gt;
&lt;p&gt;配置完成后，我建议至少做三次测试：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从外部邮箱发信到 &lt;code&gt;admin@你的域名&lt;/code&gt;，确认 Cloud Mail 能收到。&lt;/li&gt;
&lt;li&gt;如果启用了转发，确认 Gmail 也能收到同一封邮件。&lt;/li&gt;
&lt;li&gt;从 Cloud Mail 或 Gmail 使用域名邮箱发信给外部邮箱，确认对方看到的发件人是你的域名邮箱。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果 Gmail 迟迟收不到确认邮件，可以先检查 Cloud Mail 是否已经收到，再检查 Cloudflare 邮件路由的目标地址是否完成验证。&lt;/p&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;Gmail 添加 SMTP 时提示无法连接&lt;/h3&gt;
&lt;p&gt;优先确认端口和加密方式是否对应：&lt;code&gt;465&lt;/code&gt; 选择 &lt;code&gt;SSL&lt;/code&gt;，&lt;code&gt;587&lt;/code&gt; 选择 &lt;code&gt;TLS&lt;/code&gt; 或 &lt;code&gt;STARTTLS&lt;/code&gt;。如果你按本文使用 &lt;code&gt;465&lt;/code&gt;，Gmail 里就选 &lt;code&gt;SSL&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;发出去的邮件进垃圾箱&lt;/h3&gt;
&lt;p&gt;先检查 Resend 的域名验证状态，确认 SPF、DKIM、DMARC 记录都已经正确生效。DNS 刚添加后可能需要等一会儿，Cloudflare 一般很快，但不同收件方缓存时间不一样。&lt;/p&gt;
&lt;h3&gt;Gmail 收不到确认邮件&lt;/h3&gt;
&lt;p&gt;先看 Cloud Mail 面板是否已经收到确认邮件。如果 Cloud Mail 收到了但 Gmail 没有收到，再检查 Cloudflare Email Routing 的目标地址是否完成验证，以及 Cloud Mail 的第三方邮箱推送是否开启。&lt;/p&gt;
&lt;h3&gt;为什么不直接用 Cloudflare 发信？&lt;/h3&gt;
&lt;p&gt;传统的 Cloudflare Email Routing 主要负责收信和转发，并不提供常规 SMTP 发信服务器。Cloudflare 现在也在推进 Email Service，可以通过 REST API 或 Workers 发信；但如果只是想接入 Gmail 的“用其他地址发送邮件”，Resend 的 SMTP 配置会更简单。&lt;/p&gt;
&lt;h2&gt;限制和注意事项&lt;/h2&gt;
&lt;p&gt;Resend 免费档适合个人低频使用。目前免费额度是 100 封/天、3000 封/月、1 个自定义域。如果你使用 Resend 的接收功能，收到的邮件也会计入额度；但这篇方案主要使用 Resend 发信。&lt;/p&gt;
&lt;p&gt;传统的 Cloudflare Email Routing 主要负责收信转发，不提供常规 SMTP 发信服务器。Cloudflare Email Service 已经提供 REST API / Workers 发信能力，但本文选择 Resend，是因为它有标准 SMTP，直接接入 Gmail 更省事。&lt;/p&gt;
&lt;p&gt;这套方案也不是“完全私密邮箱”。从信任模型上，你仍然要信任 Cloudflare、Cloud Mail 的部署环境，以及 Resend 的发信服务。普通注册、通知、项目测试没有太大问题，但高度敏感的邮件不建议放在这类免费组合方案里。&lt;/p&gt;
&lt;p&gt;最后，域名邮箱不一定能通过所有平台的验证。以 OpenAI 这类风控更严格的服务为例，免费域名、低价域名、新注册域名或信誉较弱的自定义域名邮箱，都可能被拒绝。&lt;/p&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&amp;lt;a href=&quot;https://doc.skymail.ink/guide/dashboard.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;Cloud Mail 官方文档&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;li&gt;&amp;lt;a href=&quot;https://resend.com/pricing&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;Resend 价格与免费额度&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;li&gt;&amp;lt;a href=&quot;https://resend.com/docs/send-with-smtp&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;Resend SMTP 文档&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;li&gt;&amp;lt;a href=&quot;https://resend.com/docs/dashboard/domains/dmarc&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;Resend DMARC 文档&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;li&gt;&amp;lt;a href=&quot;https://resend.com/docs/knowledge-base/account-quotas-and-limits&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;Resend 账号额度说明&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;li&gt;&amp;lt;a href=&quot;https://developers.cloudflare.com/email-routing/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;Cloudflare Email Routing 文档&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;li&gt;&amp;lt;a href=&quot;https://developers.cloudflare.com/email-service/api/send-emails/rest-api/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;Cloudflare Email Service REST API 文档&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;li&gt;&amp;lt;a href=&quot;https://support.google.com/mail/answer/22370&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&amp;gt;Gmail：使用其他地址发信&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>我的博客开始了</title><link>https://starshadow.cc/posts/hello-fuwari/</link><guid isPermaLink="true">https://starshadow.cc/posts/hello-fuwari/</guid><description>这是我用 Fuwari 创建的第一篇文章，用来确认写作和发布流程已经跑通。</description><pubDate>Wed, 06 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这是这个博客的第一篇文章。&lt;/p&gt;
&lt;p&gt;现在它还很简单，但已经具备一个个人博客最重要的能力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以在本地预览&lt;/li&gt;
&lt;li&gt;可以用 Markdown 写文章&lt;/li&gt;
&lt;li&gt;可以提交到 GitHub&lt;/li&gt;
&lt;li&gt;可以通过 Cloudflare Pages 自动发布&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以后我会把学到的东西、踩过的坑、项目记录和一些生活想法慢慢写在这里。&lt;/p&gt;
</content:encoded></item></channel></rss>