<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Cloudflared · 仁才德</title>
    <link>https://jared.lynskey.co.nz/zh-cn/tags/cloudflared/</link>
    <description>关于软件工程与研发团队领导力的短文——绩效评估、招聘与团队扩张、CI/CD、DevOps 监控、智能体 AI。写于首尔。</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <copyright>© 2026 仁才德</copyright>
    <lastBuildDate>Wed, 11 Nov 2026 00:00:00 +1300</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/zh-cn/tags/cloudflared/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>笔记本就是服务器：用 Cloudflare Tunnel 把副业项目放上公网</title>
      <link>https://jared.lynskey.co.nz/zh-cn/posts/2026/self-host-behind-cloudflare-tunnel/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/zh-cn/posts/2026/self-host-behind-cloudflare-tunnel/</guid>
      <pubDate>Wed, 11 Nov 2026 00:00:00 +1300</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>Cloudflare</category>
      <category>Cloudflare Tunnel</category>
      <category>cloudflared</category>
      <category>Self-Hosting</category>
      <category>Expose Localhost</category>
      <category>Docker Compose</category>
      <category>Django</category>
      <category>Expo</category>
      <category>OTA Updates</category>
      <category>Security</category>
      <category>Dev Costs</category>
      <description>Cloudflare Tunnel 是什么、安全性如何、要花多少钱？不开任何端口，让自己机器上的服务拿到真正的 HTTPS 主机名，再用 ingress 白名单决定公网能访问哪些路径，附 Docker Compose 配置。</description>
      <content:encoded><![CDATA[<p>我的每个想法都是这样开始的：在笔记本上跑起来了，能用，然后想让别人也看看。有很长一段时间，下一步总是一张云账单：一台机器、一个数据库、一个 bucket、一个指过去的域名，再为一个只有三个人会看的东西按月付费。对副业项目，或者一个正经项目的第一个月来说，这个顺序是反的：还不知道会不会有生产环境，就先为生产环境掏钱了。</p>
<p>这篇文章讲的是另一种顺序。服务继续跑在开发它的那台机器上，由 Cloudflare Tunnel 给它一个公开的 HTTPS 主机名。笔记本上没有任何东西对互联网监听：不做端口转发，不碰路由器设置，也不暴露 IP 地址。是机器主动连出去到 Cloudflare，Cloudflare 再把请求顺着这条连接送回来。Tunnel 本身免费，TLS 证书、DDoS 防护和缓存则随你多半已经有的 Cloudflare zone 一起附送。</p>
<p>例子是真实的。我的<a href="https://jared.lynskey.co.nz/zh-cn/posts/2026/2026-07-22-my-current-stack/" >一个产品</a> 跑在 Fly.io 上，它的移动应用从后端获取 JavaScript 的 OTA 更新（自托管的 Expo Updates）。我想在自己的 Mac 上再跑一份更新服务器，给开发构建用，生产环境原封不动。下面的输出都来自那个下午，包括没跑通的那部分。</p>

<h2 class="relative group">Cloudflare Tunnel 到底是什么
    <div id="cloudflare-tunnel-到底是什么" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#cloudflare-tunnel-%e5%88%b0%e5%ba%95%e6%98%af%e4%bb%80%e4%b9%88" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>普通的服务器是等别人连进来的。要从互联网访问一台笔记本，你得在路由器上开端口、转发到笔记本，还得指望运营商给你一个公网 IPv4 地址（很多地方都不给，我这里也是）。然后，那个端口上监听的所有东西，整天都暴露在互联网上每一个扫描器面前。</p>
<p>Tunnel 把方向反过来。<code>cloudflared</code> 跑在你的应用旁边，主动向 Cloudflare 的边缘建立出站连接，一共四条，连到附近的两个数据中心。你 zone 里的一条 DNS 记录把主机名指向这个 tunnel。当请求打到这个主机名时，Cloudflare 终止 TLS，把请求顺着其中一条连接转发下来，<code>cloudflared</code> 再按你配置里写的交给对应的本地服务。</p>
<figure class="diagram">
  <div role="img" aria-label="请求经 HTTPS 到达 Cloudflare，经过它的规则，到达 tunnel 端点，再顺着 cloudflared 从你机器上建立的出站连接下来；cloudflared 按主机名和路径把它交给 API、edge，或者返回 404"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 520" aria-hidden="true" focusable="false">
<defs><marker id="tu-ah" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="d-head" d="M0,0 L10,5 L0,10 z"/></marker></defs>
<text class="d-title" x="160" y="60" text-anchor="middle" font-size="30">互联网</text>
<text class="d-title" x="560" y="60" text-anchor="middle" font-size="30">Cloudflare</text>
<text class="d-title" x="1020" y="60" text-anchor="middle" font-size="30">你的机器</text>
<rect class="d-box" x="40" y="200" width="240" height="100" rx="14"/>
<text class="d-label" x="160" y="245" text-anchor="middle" font-size="24">手机 / 浏览器</text>
<text class="d-sub" x="160" y="278" text-anchor="middle" font-size="18">ota.example.com</text>
<rect class="d-panel" x="400" y="110" width="320" height="300" rx="30"/>
<rect class="d-box" x="430" y="140" width="260" height="80" rx="14"/>
<text class="d-label" x="560" y="188" text-anchor="middle" font-size="22">TLS、重定向规则、WAF</text>
<rect class="d-box" x="430" y="290" width="260" height="80" rx="14"/>
<text class="d-label" x="560" y="338" text-anchor="middle" font-size="22">Tunnel 端点</text>
<rect class="d-group" x="820" y="110" width="400" height="360" rx="30"/>
<rect class="d-box" x="850" y="290" width="160" height="80" rx="14"/>
<text class="d-label" x="930" y="338" text-anchor="middle" font-size="22">cloudflared</text>
<rect class="d-box" x="1050" y="160" width="140" height="70" rx="14"/>
<text class="d-label" x="1120" y="202" text-anchor="middle" font-size="20">api:8000</text>
<rect class="d-box" x="1050" y="290" width="140" height="70" rx="14"/>
<text class="d-label" x="1120" y="332" text-anchor="middle" font-size="20">edge:8081</text>
<rect class="d-box" x="1050" y="380" width="140" height="70" rx="14"/>
<text class="d-label" x="1120" y="422" text-anchor="middle" font-size="20">404</text>
<path class="d-line" d="M280,250 L428,190" marker-end="url(#tu-ah)"/>
<text class="d-sub" x="350" y="200" text-anchor="middle" font-size="18">HTTPS</text>
<path class="d-line" d="M560,220 L560,288" marker-end="url(#tu-ah)"/>
<path class="d-line" d="M848,330 L692,330" marker-end="url(#tu-ah)"/>
<text class="d-sub" x="770" y="315" text-anchor="middle" font-size="16">出站 QUIC</text>
<text class="d-sub" x="770" y="360" text-anchor="middle" font-size="16">（不开端口）</text>
<path class="d-line" d="M1010,315 L1048,200" marker-end="url(#tu-ah)"/>
<path class="d-line" d="M1010,330 L1048,325" marker-end="url(#tu-ah)"/>
<path class="d-line" d="M1010,345 L1048,410" marker-end="url(#tu-ah)"/>
<text class="d-sub" x="1020" y="500" text-anchor="middle" font-size="18">ingress 规则：主机名 + 路径白名单</text>
</svg>
</div><figcaption>机器只会往外连。Cloudflare 的 zone 规则在 tunnel 之前执行；ingress 规则决定什么能被访问到。</figcaption>
</figure>

<p>从安全角度看，关键在于路由器上没有任何入站规则，你的 IP 地址也从不出现在 DNS 里。有人扫描你家的网络连接，什么也找不到，因为上面根本没有东西在监听。</p>

<h2 class="relative group">从头到尾的搭建
    <div id="从头到尾的搭建" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e4%bb%8e%e5%a4%b4%e5%88%b0%e5%b0%be%e7%9a%84%e6%90%ad%e5%bb%ba" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>你需要一个托管在 Cloudflare 上的域名（免费套餐就够），以及装好 <code>cloudflared</code>（Mac 上 <code>brew install cloudflared</code>）。</p>
<p><strong>1. 登录一次。</strong> 这会打开浏览器，你选好 zone，它会保存一张证书，让这台机器可以在那个 zone 里创建 tunnel 和 DNS 记录：</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cloudflared tunnel login</span></span></code></pre></div></div>
<p><strong>2. 创建 tunnel。</strong> 这个项目的所有东西我都放在单独的文件夹里，因为凭据文件就是 tunnel 的身份：谁拿到它，谁就能替你的主机名提供流量。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p ~/.cloudflared/myapp <span class="o">&amp;&amp;</span> <span class="nb">cd</span> ~/.cloudflared/myapp
</span></span><span class="line"><span class="cl">cloudflared tunnel create --credentials-file ./creds.json myapp-local
</span></span><span class="line"><span class="cl">chmod <span class="m">600</span> creds.json</span></span></code></pre></div></div>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Tunnel credentials written to ./creds.json. Keep this file secret. To revoke these credentials, delete the tunnel.
</span></span><span class="line"><span class="cl">Created tunnel myapp-local with id 1ade030c-…</span></span></code></pre></div></div>
<p><strong>3. 把主机名指向它。</strong> 每个主机名一条命令，创建一条指向 <code>&lt;tunnel-id&gt;.cfargotunnel.com</code> 的代理 CNAME：</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cloudflared tunnel route dns myapp-local ota.example.com
</span></span><span class="line"><span class="cl">cloudflared tunnel route dns myapp-local checkin-local.example.com</span></span></code></pre></div></div>
<p><strong>4. 写 ingress 规则。</strong> 这是对安全最要紧的部分，所以下面单独一节来讲。</p>
<p><strong>5. 跑起来。</strong> 我的应用本来就跑在 Docker Compose 下，所以 <code>cloudflared</code> 只是同一个网络里多出来的一个容器。它按服务名访问应用，应用根本不需要在宿主机上发布端口：</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># compose.tunnel.yml, used alongside the project&#39;s docker-compose.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cloudflared</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">cloudflare/cloudflared:2026.10.7</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l">tunnel --config /etc/cloudflared/config.yml run</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${HOME}/.cloudflared/myapp:/etc/cloudflared:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">api, edge]</span></span></span></code></pre></div></div>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose -f docker-compose.yml -f ~/.cloudflared/myapp/compose.tunnel.yml up -d cloudflared</span></span></code></pre></div></div>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">INF Registered tunnel connection connIndex=0 … location=icn06 protocol=quic
</span></span><span class="line"><span class="cl">INF Registered tunnel connection connIndex=1 … location=icn07 protocol=quic
</span></span><span class="line"><span class="cl">INF Registered tunnel connection connIndex=2 … location=icn07 protocol=quic
</span></span><span class="line"><span class="cl">INF Registered tunnel connection connIndex=3 … location=icn06 protocol=quic</span></span></code></pre></div></div>
<p>四条连接连到首尔，笔记本就从外向内地上了互联网。<code>restart: unless-stopped</code> 让它在重启后、Docker 一启动就自动恢复。</p>

<h2 class="relative group">暴露路径，而不是服务器
    <div id="暴露路径而不是服务器" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%9a%b4%e9%9c%b2%e8%b7%af%e5%be%84%e8%80%8c%e4%b8%8d%e6%98%af%e6%9c%8d%e5%8a%a1%e5%99%a8" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>这是我差点犯的错，我猜也是最常见的一个。最省事的配置只有一行：<code>hostname → http://localhost:8000</code>。这会把<strong>整个开发服务器</strong>都发布出去。</p>
<p>开发服务器不是为这个设计的。我的开着 <code>DEBUG=True</code>，任何错误都会显示完整的 traceback 和配置。它还关掉了认证（<code>AUTH_MODE=disabled</code>），任何 bearer token 都会让你以测试用户身份登录。这在 <code>localhost</code> 上完全合理，放到公网主机名上就是灾难。你的多半也有类似的东西：用默认密码的管理后台、灌测试数据的端点、debug toolbar，或者因为只有你自己在调用、所以信任调用方的 API。</p>
<p>所以 tunnel 配置是一份白名单。<code>cloudflared</code> 按顺序拿每个请求去匹配 ingress 规则，按主机名，也按路径上的正则表达式。第一条匹配的生效，最后一条兜住其余所有请求：</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">tunnel</span><span class="p">:</span><span class="w"> </span><span class="l">myapp-local</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">credentials-file</span><span class="p">:</span><span class="w"> </span><span class="l">/etc/cloudflared/creds.json</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">ingress</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># OTA: the two read-only endpoints the phone calls, and the files they link.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">ota.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">^/api/expo-updates/(manifest|status)/$</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http://api:8000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">ota.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">^/media/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http://api:8000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Device check-ins, authenticated by each device&#39;s own key.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">checkin-local.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">^/c/$</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http://edge:8081</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Everything else, including /admin/ and the publish endpoint: 404 at the edge.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http_status:404</span></span></span></code></pre></div></div>
<p>发布端点就在同一台服务器上、同一个前缀下（<code>/api/expo-updates/import-update/</code>），但它不在列表里。管理后台和 API 的其余部分也不在。这些请求根本出不了 Cloudflare。</p>
<p>你不必盲信自己的正则；<code>cloudflared</code> 会告诉你一个 URL 匹配的是哪条规则：</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cloudflared tunnel --config config.yml ingress rule https://ota.example.com/admin/</span></span></code></pre></div></div>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">https://ota.example.com/api/expo-updates/manifest/        service: http://api:8000
</span></span><span class="line"><span class="cl">https://ota.example.com/api/expo-updates/import-update/   service: http_status:404
</span></span><span class="line"><span class="cl">https://ota.example.com/admin/                            service: http_status:404
</span></span><span class="line"><span class="cl">https://ota.example.com/media/expo-updates/x.js           service: http://api:8000
</span></span><span class="line"><span class="cl">https://checkin-local.example.com/c/                      service: http://edge:8081
</span></span><span class="line"><span class="cl">https://checkin-local.example.com/health                  service: http_status:404</span></span></code></pre></div></div>
<p>我会把这个检查写进脚本，每次改配置都跑一遍。只要几秒钟，而一条比你本意匹配得更多的正则，正是那种只会从别人那里得知的错误。</p>
<p>还有两条经验法则：</p>
<ul>
<li><strong>暴露出去的东西应该自己负责认证。</strong> OTA 端点提供的是公开的只读数据。设备 check-in 用每台设备自己的密钥签名。两者的安全都不依赖网络。如果你想给别人看一个没有登录的页面，就在那个主机名前面放 <a href="https://developers.cloudflare.com/cloudflare-one/policies/access/"  target="_blank" rel="noreferrer">Cloudflare Access</a>（50 个用户以内免费），而不是自己去做登录。</li>
<li><strong>别把 tunnel 当成开发流程里常驻后台的一部分。</strong> 有意识地启动它，只为你想要的那个主机名，用完就停。<code>docker compose stop cloudflared</code> 一条命令就把一切从互联网上撤下来。</li>
</ul>

<h2 class="relative group">吃掉我主机名的那个重定向
    <div id="吃掉我主机名的那个重定向" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%90%83%e6%8e%89%e6%88%91%e4%b8%bb%e6%9c%ba%e5%90%8d%e7%9a%84%e9%82%a3%e4%b8%aa%e9%87%8d%e5%ae%9a%e5%90%91" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>Tunnel 连上了，规则也验证过了，我从外部试了一下：</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">https://ota.example.com/api/expo-updates/status/?…   301
</span></span><span class="line"><span class="cl">https://ota.example.com/api/expo-updates/manifest/   301
</span></span><span class="line"><span class="cl">https://ota.example.com/admin/                       301
</span></span><span class="line"><span class="cl">https://checkin-local.example.com/c/                 301</span></span></code></pre></div></div>
<p>每条路径都返回同一个 301，包括那些 tunnel 本该回 404 的。这说明请求根本没到 <code>cloudflared</code>。<code>Location</code> 头写的是 <code>https://www.example.com/</code>，再加上 <code>server: cloudflare</code> 头，事情就清楚了：是 Cloudflare 自己在应答。</p>
<p>这是我自己挖的坑。几个月前我加了一条 zone 级的重定向规则，“其他任何子域名 → www”，用来当<a href="https://jared.lynskey.co.nz/zh-cn/posts/2026/2026-09-09-scale-to-zero-504s/#the-spike-was-a-red-herring" >那些遍历编造出来的子域名的扫描器的黑洞</a>。重定向规则在 Cloudflare 的请求流水线里执行，早于请求被发往源站，而 tunnel 不过是一个源站。<a href="https://jared.lynskey.co.nz/zh-cn/posts/2026/2026-09-19-ephemeral-dev-environments-on-fly/" >同一条规则也拦住过我的预览环境</a>，让 Workers 根本见不到请求。修法和上次一样：在规则表达式里再加一个例外。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">… and not http.host in {&#34;ota.example.com&#34; &#34;checkin-local.example.com&#34;}</span></span></code></pre></div></div>
<p>如果你的 tunnel 主机名返回的是 Cloudflare 的重定向或错误页面，而不是你的应用，那在调试 tunnel 之前，先检查这个 zone 的 <strong>Rules → Redirect Rules</strong>、Page Rules 和所有 WAF 自定义规则。<code>cloudflared</code> 会记录它转发的每个请求，所以日志为空就说明请求根本没走到那一步。</p>

<h2 class="relative group">它做不到的事
    <div id="它做不到的事" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%ae%83%e5%81%9a%e4%b8%8d%e5%88%b0%e7%9a%84%e4%ba%8b" aria-label="锚点">#</a>
    </span>
    
</h2>
<ul>
<li><strong>来自公网的 UDP。</strong> 公开的 tunnel 主机名只承载 HTTP(S)（外加 WebSocket 和 gRPC）。我产品的设备平时通过 UDP 上的 CoAP 做 check-in，这走不了 tunnel；连本地副本的设备会退回到 HTTPS check-in，每次要多花几秒钟。如果你的想法是游戏服务器或 VoIP，tunnel 就不是合适的工具。</li>
<li><strong>在线率。</strong> 这是你的笔记本。合上盖子，站点就挂了；Cloudflare 会显示一个 530 错误页。拿来做演示、十个人的内测或者一份 staging 副本都没问题，但不适合任何有人依赖的东西。</li>
<li><strong>编译进构建里的东西。</strong> 移动应用的更新 URL 是原生配置，所以只有用 tunnel URL 构建出来的包才会用它。我的商店构建依然连着生产环境，这也正是这套方案敢拿来试的原因。</li>
</ul>

<h2 class="relative group">成本
    <div id="成本" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e6%88%90%e6%9c%ac" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>免费套餐的 Cloudflare zone、tunnel、它的证书和 DNS 记录：0 美元。机器本来就开着。唯一真正的成本是，现在你自己就是运维值班团队。</p>
<p>它帮你省下的，是从“在我机器上能跑”到“值得花钱托管”之间的那个阶段。当天下午你就能把链接发给测试、设计师或者第一个客户。如果没人在意，你什么也没损失。如果有人在意，迁到真正的主机上也只是一处改动：把 CNAME 指到别处。</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2026/self-host-behind-cloudflare-tunnel/featured.jpg" medium="image" type="image/jpeg" />
    </item>
  </channel>
</rss>
