
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title><![CDATA[ The Cloudflare Blog ]]></title>
        <description><![CDATA[ Get the latest news on how products at Cloudflare are built, technologies used, and join the teams helping to build a better Internet. ]]></description>
        <link>https://blog.cloudflare.com</link>
        <atom:link href="https://blog.cloudflare.com/" rel="self" type="application/rss+xml"/>
        <language>en-us</language>
        <image>
            <url>https://blog.cloudflare.com/favicon.png</url>
            <title>The Cloudflare Blog</title>
            <link>https://blog.cloudflare.com</link>
        </image>
        <lastBuildDate>Fri, 17 Apr 2026 23:02:42 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Shared Dictionaries: compression that keeps up with the agentic web]]></title>
            <link>https://blog.cloudflare.com/shared-dictionaries/</link>
            <pubDate>Fri, 17 Apr 2026 13:02:00 GMT</pubDate>
            <description><![CDATA[ Today, we’re excited to give you a sneak peek of our support for shared compression dictionaries, show you how it improves page load times, and reveal when you’ll be able to try the beta yourself. 
 ]]></description>
            <content:encoded><![CDATA[ <p>Web pages have grown 6-9% <a href="https://almanac.httparchive.org/en/2024/page-weight#fig-15"><u>heavier</u></a> every year for the past decade, spurred by the web becoming more framework-driven, interactive, and media-rich. Nothing about that trajectory is changing. What <i>is</i> changing is how often those pages get rebuilt and how many clients request them. Both are skyrocketing because of agents. </p><p>Shared dictionaries shrink asset transfers from servers to browsers so pages <b>load faster with less bloat on the wire,</b> especially for returning users or visitors on a slow connection. Instead of re-downloading entire JavaScript bundles after every deploy, the browser tells the server what it already has cached, and the server only sends the file diffs. </p><p><b>Today, we’re excited to give you a sneak peek of our support for shared compression dictionaries,</b> show you what we’ve seen in early testing, and reveal when you’ll be able to try the beta yourself (hint: it’s April 30, 2026!). </p>
    <div>
      <h2>The problem: more shipping = less caching</h2>
      <a href="#the-problem-more-shipping-less-caching">
        
      </a>
    </div>
    <p>Agentic crawlers, browsers, and other tools hit endpoints repeatedly, fetching full pages, often to extract a fragment of information. Agentic actors represented just under 10% of total requests across Cloudflare's network during March 2026, up ~60% year-over-year. </p><p>Every page shipped is heavier than last year and read more often by machines than ever before. But agents aren’t just consuming the web, they’re helping to build it. AI-assisted development <b>means teams ship faster. </b>Increasing the frequency of deploys, experiments, and iterations is great for product velocity, but terrible for caching.</p><p>As agents push a one-line fix, the bundler re-chunks, filenames change, and every user on earth could re-download the entire application. Not because the code is meaningfully any different, but because the browser/client has no way to know specifically what changed. It sees a new URL and starts from zero. Traditional compression helps with the size of each download, but it can't help with the redundancy. It doesn't know the client already has 95% of the file cached. So every deploy, across every user, across every bot, sends redundant bytes again and again. Ship ten small changes a day, and you've effectively opted out of caching. This wastes bandwidth and CPU in a web where hardware is quickly becoming the bottleneck.</p><p><b>In order to scale with more requests hitting heavier pages that are re-deployed more often, compression has to get smarter. </b></p>
    <div>
      <h2>What are shared dictionaries?</h2>
      <a href="#what-are-shared-dictionaries">
        
      </a>
    </div>
    <p>A compression dictionary is a shared reference between server and client that works like a cheat sheet. Instead of compressing a response from scratch, the server says "you already know this part of the file because you’ve cached it before" and only sends what's new. The client holds the same reference and uses it to reconstruct the full response during decompression. The more the dictionary can reference content in the file, the smaller the compressed output that is transferred to the client.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2g2NBi1d4eqLAgksGetult/ea05b48519edfa8cccde6d6531700ce1/image5.png" />
          </figure><p>This principle of compressing against what's already known is how modern compression algorithms pull ahead of their predecessors. Brotli ships with a built-in dictionary of common web patterns like HTML attributes and common phrases; Zstandard is purpose-built for custom dictionaries: you can feed it representative content samples, and it generates an optimized dictionary for the kind of content you serve. Gzip has neither; it must build dictionaries by finding patterns in real-time as it’s compressing. These “traditional compression” algorithms are already <a href="https://developers.cloudflare.com/speed/optimization/content/compression/"><u>available</u></a> on Cloudflare today. </p><p>Shared dictionaries take this principle a step further: the previously cached version of the resource<b> becomes the dictionary.</b> Remember the deploy problem where a team ships a one-line fix and every user re-downloads the full bundle? With shared dictionaries, the browser already has the old version cached. The server compresses against it, sending only the diff. That 500KB bundle with a one-line change becomes only a few kilobytes on the wire. At 100K daily users and 10 deploys a day, that's the difference between 500GB of transfer and a few hundred megabytes.</p>
    <div>
      <h3>Delta compression</h3>
      <a href="#delta-compression">
        
      </a>
    </div>
    <p>Delta compression is what turns the version the browser already has into the dictionary. The protocol looks to when the server first serves a resource, it attaches a <code>Use-As-Dictionary</code> response header, telling the browser to essentially hold onto the file because it’ll be useful later. On the next request for that resource, the browser sends an <code>Available-Dictionary</code> header back, telling the server, "here's what I've got." The server then proceeds to compress the new version against the old one and sends only the diff. No separate dictionary file needed. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6Hyxl74lzTdGdTczYBe3LF/4b66760c34378508e774814eba7b3c8f/image3.png" />
          </figure><p>This is where the payoff lands for real applications. Versioned JS bundles, CSS files, framework updates, and anything that changes incrementally between releases. The browser has app.bundle.v1.js cached already and the developer makes an update and deploys app.bundle.v2.js. Delta compression only sends the diff between these versions. Every subsequent version after is also just a diff. Version three compresses against version two. Version 47 compresses against version 46. The savings don't reset, they persist across the entire release history.</p><p>There's also active discussion in the community about custom and dynamic dictionaries <a href="https://dev.to/carlosmateom/beyond-static-resources-delta-compression-for-dynamic-html-3hn4"><u>for non-static </u></a>content. That's future work, but the implications are significant. We'll save that for another post.</p>
    <div>
      <h2>So why the wait?</h2>
      <a href="#so-why-the-wait">
        
      </a>
    </div>
    <p>If shared dictionaries are so powerful, why doesn't everyone use them already?</p><p>Because the last time they were tried, the implementation couldn't survive contact with the open web. </p><p><a href="https://en.wikipedia.org/wiki/SDCH"><u>Google shipped</u></a> Shared Dictionary Compression for HTTP (SDCH) in Chrome in 2008. It worked well with some early adopters reporting double-digit improvements in page load times. But SDCH accumulated problems faster than anyone was able to fix them.</p><p>The most memorable was a class of compression side-channel attacks (<a href="https://en.wikipedia.org/wiki/CRIME"><u>CRIME</u></a>, <a href="https://en.wikipedia.org/wiki/BREACH"><u>BREACH</u></a>). Researchers showed that if an attacker could inject content alongside something sensitive that gets compressed (like a session cookie, token, etc.) the size of the compressed output could leak information about the secret. The attacker could guess a byte at a time, watch whether the asset size shrank, and repeat until they extracted the whole secret. </p><p>But security wasn't the only problem, or even the main reason why adoption didn’t happen. SDCH surfaced a few architectural problems like violating the <a href="https://groups.google.com/a/chromium.org/g/blink-dev/c/nQl0ORHy7sw/m/S8BoYHQyAgAJ"><u>Same-Origin Policy</u></a> (which ironically is partially why it performed so well). Its cross-origin dictionary model <a href="https://groups.google.com/a/chromium.org/g/blink-dev/c/nQl0ORHy7sw/m/BROFrwM2AgAJ"><u>couldn't be reconciled with CORS</u></a>, and it lacked some specification regarding interactions with things like the Cache API. After a while it became clear that adoption wasn’t ready, so in 2017 Chrome (the only browser supporting at the time) <a href="https://groups.google.com/a/chromium.org/g/blink-dev/c/nQl0ORHy7sw"><u>unshipped</u></a> it. </p><p>Getting the web community to pick up the baton took a decade, but it was worth it.</p><p>The modern standard, <a href="https://datatracker.ietf.org/doc/rfc9842/"><u>RFC 9842: Compression Dictionary Transport</u></a>, closes key design gaps that made SDCH untenable. For example, it enforces that an advertised dictionary is only usable on responses from the same-origin, preventing many conditions that made side-channel compression attacks possible. </p><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Compression_dictionary_transport"><u>Chrome and Edge have shipped support</u></a> with <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1882979"><u>Firefox</u></a> working to follow. The standard is moving toward broad adoption, but complete cross-browser support is still catching up.</p><p>The RFC mitigates the security problems but dictionary transport has always been complex to implement. An origin may have to generate dictionaries, serve them with the right headers, check every request for an <code>Available-Dictionary</code> match, delta-compress the response on the fly, and fall back gracefully when a client doesn't have a dictionary. Caching gets complex too. Responses vary on both encoding and dictionary hash, so every dictionary version creates a separate cache variant. Mid-deploy, you have clients with the old dictionary, clients with the new one, and clients with none. Your cache is storing separate copies for each. Hit rates drop, storage climbs, and the dictionaries themselves have to stay fresh under normal HTTP caching rules.</p><p>This complexity is a coordination problem. And exactly the kind of thing that belongs at the edge. A CDN already sits in front of every request, already manages compression, and already handles cache variants (<i>watch this space for a soon-to-come announcement blog</i>).</p>
    <div>
      <h2>How Cloudflare is building shared dictionary support </h2>
      <a href="#how-cloudflare-is-building-shared-dictionary-support">
        
      </a>
    </div>
    <p>Shared dictionary compression touches every layer of the stack between the browser and the origin. We've seen strong customer interest: some people have already built their own implementations like RFC author <b>Patrick Meenan</b>'s <a href="https://github.com/pmeenan/dictionary-worker"><u>dictionary-worker</u></a>, which runs the full dictionary lifecycle inside a Cloudflare Worker using WASM-compiled Zstandard (as an example).  We want to make this accessible to everyone and as easy as possible to implement. So we’re rolling it out across the platform in three phases, starting with the plumbing.</p><p><b>Phase 1</b>: Passthrough support is currently in active development. Cloudflare forwards the headers and encodings that shared dictionaries require like <code>Use-As-Dictionary</code>, <code>Available-Dictionary</code>, and the <code>dcb</code> and <code>dcz</code> content encodings, without stripping, modifying, or recompressing them. The Cache keys are extended to vary on <code>Available-Dictionary</code> and <code>Accept-Encoding</code> so dictionary-compressed responses are cached correctly. This phase serves customers who manage their own dictionaries at the origin.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2iQek3TT8FRu3VikQIdxDh/11f522c791e10c751f72ac8dcf293ac2/image2.png" />
          </figure><p>We plan to have an open beta of Phase 1 ready by <b>April 30, 2026</b>. To <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Compression_dictionary_transport"><u>use it</u></a>, you'll need to be on a Cloudflare zone with the feature enabled, have an origin that serves dictionary-compressed responses with the correct headers (<code>Use-As-Dictionary</code>, Content-Encoding: <code>dcb</code> or <code>dcz</code>), and your visitors need to be on a browser that advertises <code>dcb/dcz</code> in <code>Accept-Encoding</code> and sends <code>Available-Dictionary</code>. Today, that means Chrome 130+ and Edge 130+, with Firefox support in progress.</p><p>Keep your eyes fixed on the <a href="https://developers.cloudflare.com/changelog/"><u>changelog</u></a> for when this becomes available and more documentation for how to use it. </p><p>We’ve already started testing passthrough internally. In a controlled test, we deployed two js bundles in sequence. They were nearly identical except for a few localized changes between the versions representing successive deploys of the same web application. Uncompressed, the asset is 272KB. Gzip brought that down to 92.1KB, a solid 66% reduction. With shared dictionary compression over DCZ, using the previous version as the dictionary, that same asset dropped to 2.6KB. <b>That's a 97% reduction over the already compressed asset</b>. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4I6emVSFDBqjTBBgjIRWo3/ec4e224fdc5f8873f4758a1d6773a7c1/image7.png" />
          </figure><p>In the same lab test, we measured two timing milestones from the client: time to first byte (TTFB) and full download completion. The TTFB results are interesting for what they don't show. On a cache miss (where DCZ has to compress against the dictionary at the origin) TTFB is only about 20ms slower than gzip. The overhead is near-negligible for transmission.</p><p>The download times are where the difference is. On a cache miss, DCZ completed in 31ms versus 166ms for gzip (an 81% improvement). On a cache hit, 16ms versus 143ms (89% improvement). The response is so much smaller that even when you pay a slight penalty at the start, you finish far ahead.</p><p><i>Initial lab results simulating minimal JS bundle diffs, results will vary based on the actual delta between the dictionary and the asset.</i></p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5sNxMTZlNPMcojhiVc8J9l/b47c7241038c537b90c9c344a6904061/image8.png" />
          </figure><p><b>Phase 2</b>: This is where Cloudflare starts doing the work for you. Instead of handling dictionary headers, compression, and fallback logic on the origin, in this phase you tell Cloudflare which assets should be used as dictionaries via a rule and we manage the rest for you. We inject the Use-As-Dictionary headers, store the dictionary bytes, delta-compress new versions against old ones, and serve the right variant to each client. Your origin serves normal responses. Any dictionary complexity moves off your infrastructure and onto ours.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1DnDwxeA5IHNLhyChs1bI8/747f87388132a3c97a3d8ae8392e3ebf/image1.png" />
          </figure>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3iFPkpibGAoixY6qqZNSKK/2ac29345dfb4782c74f4082ec1c6c9f4/image4.png" />
          </figure><p>To demonstrate this, we've built a live demo to show what this looks like in practice. <b>Try it here: </b><a href="https://canicompress.com/"><b><u>Can I Compress (with Dictionaries)?</u></b></a><b> </b></p><p>The demo deploys a new ~94KB JavaScript bundle every minute, meant to mimic a typical production single page application bundle. The bulk of the code is static between deploys; only a small configuration block changes each time, which also mirrors real-world deploys where most of the bundle is unchanged framework and library code. When the first version loads, Cloudflare's edge stores it as a dictionary. When the next deploy arrives, the browser sends the hash of the version it already has, and the edge delta-compresses the new bundle against it. The result: 94KB compresses to roughly <b>159 bytes.</b> That's a <b>99.5% reduction over gzip, </b>because the only thing on the wire is the actual diff.</p><p>The demo site includes walkthroughs so you can verify the compression ratios on your own via curl or your browser.</p><p><b>Phase 3</b>: The dictionary is automatically generated on behalf of the website. Instead of customers specifying which assets to use as dictionaries, Cloudflare identifies them automatically. Our network already sees every version of every resource that flows through it, which includes millions of sites, billions of requests, and every new deploy. The idea is that when the network observes a URL pattern where successive responses share most of their content but differ by hash, it has a strong signal that the resource is versioned and a candidate for delta compression. It stores the previous version as a dictionary and compresses subsequent versions against it. No customer configuration. No maintenance.</p><p>This is a simple idea, but is genuinely hard. Safely generating dictionaries that avoid revealing private data and identifying traffic for which dictionaries will offer the most benefit are real engineering problems. But Cloudflare has the right pieces: we see the traffic patterns across the entire network, we already manage the cache layer where dictionaries need to live, and our <a href="https://blog.cloudflare.com/the-rum-diaries-enabling-web-analytics-by-default/"><u>RUM beacon</u></a> to clients can help give us a validation loop to confirm that a dictionary actually improves compression before we commit to serving it. The combination of traffic visibility, edge storage, and synthetic testing is what makes automatic generation feasible, though there are still many pieces to figure out.</p><p>The performance and bandwidth benefits of phase 3 are the crux of our motivation. This is what makes shared dictionaries accessible to everyone using Cloudflare, including the millions of zones that would never have had the engineering time to implement custom dictionaries manually. </p>
    <div>
      <h2>The bigger picture</h2>
      <a href="#the-bigger-picture">
        
      </a>
    </div>
    <p>For most of the web's history, compression was stateless. Every response was compressed as if the client had never seen anything before. Shared dictionaries change that: they give compression a memory.</p><p>That matters more now than it would have five years ago. Agentic coding tools are compressing the interval between deploys, while also driving a growing share of the traffic that consumes them. While today AI tools can produce massive diffs, agents are gaining more context and becoming surgical in their code changes. This, coupled with more frequent releases and more automated clients means more redundant bytes on every request. Delta compression helps both sides of that equation by reducing the number of bytes per transfer, and the number of transfers that need to happen at all.</p><p>Shared Dictionaries took decades to standardize. Cloudflare is helping to build the infrastructure to make it work for every client that touches your site, human or not. Phase 1 beta opens <b>April 30</b>, and we’re excited for you to try it.</p><p>_____</p><p><sup> 1Bots = </sup><a href="https://radar.cloudflare.com/bots?dateRange=28d"><sup><u>~31.3% </u></sup></a><sup>of all HTTP requests. AI = ~</sup><a href="https://radar.cloudflare.com/explorer?dataSet=bots&amp;groupBy=bot_category"><sup><u>29-30%</u></sup></a><sup> of all Bot traffic (March 2026). </sup></p> ]]></content:encoded>
            <category><![CDATA[Agents Week]]></category>
            <category><![CDATA[Agents]]></category>
            <category><![CDATA[Compression]]></category>
            <category><![CDATA[Pingora]]></category>
            <category><![CDATA[Speed]]></category>
            <category><![CDATA[AI]]></category>
            <guid isPermaLink="false">1vrgbarIDanwhi6j2m6oNM</guid>
            <dc:creator>Alex Krivit</dc:creator>
            <dc:creator>Edward Wang</dc:creator>
            <dc:creator>Sid Chunduri</dc:creator>
        </item>
        <item>
            <title><![CDATA[Fixing request smuggling vulnerabilities in Pingora OSS deployments]]></title>
            <link>https://blog.cloudflare.com/pingora-oss-smuggling-vulnerabilities/</link>
            <pubDate>Mon, 09 Mar 2026 14:00:00 GMT</pubDate>
            <description><![CDATA[ Today we’re disclosing request smuggling vulnerabilities when our open source Pingora service is deployed as an ingress proxy and how we’ve fixed them in Pingora 0.8.0.  ]]></description>
            <content:encoded><![CDATA[ <p>In December 2025, Cloudflare received reports of HTTP/1.x request smuggling vulnerabilities in the <a href="https://github.com/cloudflare/pingora"><u>Pingora open source</u></a> framework when Pingora is used to build an ingress proxy. Today we are discussing how these vulnerabilities work and how we patched them in <a href="https://github.com/cloudflare/pingora/releases/tag/0.8.0"><u>Pingora 0.8.0</u></a>.</p><p>The vulnerabilities are <a href="https://www.cve.org/CVERecord?id=CVE-2026-2833"><u>CVE-2026-2833</u></a>, <a href="https://www.cve.org/CVERecord?id=CVE-2026-2835"><u>CVE-2026-2835</u></a>, and <a href="https://www.cve.org/CVERecord?id=CVE-2026-2836"><u>CVE-2026-2836</u></a>. These issues were responsibly reported to us by Rajat Raghav (xclow3n) through our <a href="https://www.cloudflare.com/disclosure/"><u>Bug Bounty Program</u></a>.</p><p><b>Cloudflare’s CDN and customer traffic were not affected</b>, our investigation found. <b>No action is needed for Cloudflare customers, and no impact was detected.</b> </p><p>Due to the architecture of Cloudflare’s network, these vulnerabilities could not be exploited: Pingora is not used as an ingress proxy in Cloudflare’s CDN.</p><p>However, these issues impact standalone Pingora deployments exposed to the Internet, and may enable an attacker to:</p><ul><li><p>Bypass Pingora proxy-layer security controls</p></li><li><p>Desync HTTP request/responses with backends for cross-user hijacking attacks (session or credential theft)</p></li><li><p>Poison Pingora proxy-layer caches retrieving content from shared backends</p></li></ul><p>We have released <a href="https://github.com/cloudflare/pingora/releases/tag/0.8.0"><u>Pingora 0.8.0</u></a> with fixes and hardening. While Cloudflare customers were not affected, we strongly recommend users of the Pingora framework to <b>upgrade as soon as possible.</b></p>
    <div>
      <h2>What was the vulnerability?</h2>
      <a href="#what-was-the-vulnerability">
        
      </a>
    </div>
    <p>The reports described a few different HTTP/1 attack payloads that could cause desync attacks. Such requests could cause the proxy and backend to disagree about where the request body ends, allowing a second request to be “smuggled” past proxy‑layer checks. The researcher provided a proof-of-concept to validate how a basic Pingora reverse proxy misinterpreted request body lengths and forwarded those requests to server backends such as Node/Express or uvicorn.</p><p>Upon receiving the reports, our engineering team immediately investigated and validated that, as the reporter also confirmed, the Cloudflare CDN itself was not vulnerable. However, the team did also validate that vulnerabilities exist when Pingora acts as the ingress proxy to shared backends.</p><p>By design, the Pingora framework <a href="https://blog.cloudflare.com/how-we-built-pingora-the-proxy-that-connects-cloudflare-to-the-internet/#design-decisions"><u>does allow</u></a> edge case HTTP requests or responses that are not strictly RFC compliant, because we must accept this sort of traffic for customers with legacy HTTP stacks. But this leniency has limits to avoid exposing Cloudflare itself to vulnerabilities.</p><p>In this case, Pingora had non-RFC-compliant interpretations of request bodies within its HTTP/1 stack that allowed these desync attacks to exist. Pingora deployments within Cloudflare are not directly exposed to ingress traffic, and we found that production traffic that arrived at Pingora services were not subject to these misinterpretations. Thus, the attacks were not exploitable on Cloudflare traffic itself, unlike a <a href="https://blog.cloudflare.com/resolving-a-request-smuggling-vulnerability-in-pingora/"><u>previous Pingora smuggling vulnerability</u></a> disclosed in May 2025.</p><p>We’ll explain, case-by-case, how these attack payloads worked.</p>
    <div>
      <h3>1. Premature upgrade without 101 handshake</h3>
      <a href="#1-premature-upgrade-without-101-handshake">
        
      </a>
    </div>
    <p>The first report showed that a request with an <code>Upgrade</code> header value would cause Pingora to pass through subsequent bytes on the HTTP connection immediately, before the backend had accepted an upgrade (by returning <code>101 Switching Protocols</code>). The attacker could thus pipeline a second HTTP request after the upgrade request on the same connection:</p>
            <pre><code>GET / HTTP/1.1
Host: example.com
Upgrade: foo


GET /admin HTTP/1.1
Host: example.com</code></pre>
            <p>Pingora would parse only the initial request, then treat the remaining buffered bytes as the “upgraded” stream and forward them directly to the backend in a “passthrough” mode <a href="https://github.com/cloudflare/pingora/blob/ef017ceb01962063addbacdab2a4fd2700039db5/pingora-core/src/protocols/http/v1/server.rs#L797"><u>due to the Upgrade header</u></a> (until the response <a href="https://github.com/cloudflare/pingora/blob/ef017ceb01962063addbacdab2a4fd2700039db5/pingora-core/src/protocols/http/v1/server.rs#L523"><u>was received</u></a>).</p><p>This is not at all how the HTTP/1.1 Upgrade process per <a href="https://datatracker.ietf.org/doc/html/rfc9110#field.upgrade"><u>RFC 9110</u></a> is intended to work. The subsequent bytes should <i>only</i> be interpreted as part of an upgraded stream if a <code>101 Switching Protocols</code> header is received, and if a <code>200 OK</code> response is received instead, the subsequent bytes should continue to be interpreted as HTTP.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2IYHyGkABpNA0e09wiiGpY/4f51ea330c2d266260f6361dd9d64d79/image4.png" />
          </figure><p><sup><i>An attacker that sends an Upgrade request, then pipelines a partial HTTP request may cause a desync attack. Pingora will incorrectly interpret both as the same upgraded request, even if the backend server declines the upgrade with a 200.</i></sup></p><p>Via the improper pass-through, a Pingora deployment that received a non-101 response could still forward the second partial HTTP request to the upstream as-is, bypassing any Pingora user‑defined ACL-handling or WAF logic, and poison the connection to the upstream so that a subsequent request from a different user could improperly receive the <code>/admin</code> response.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/oIwatu6gaMoJHCCs95sFN/8ea94ee8f04be6f7f00474168b382180/image3.png" />
          </figure><p><sup><i>After the attack payload, Pingora and the backend server are now “desynced.” The backend server will wait until it thinks the rest of the partial /attack request header that Pingora forwarded is complete. When Pingora forwards a different user’s request, the two headers are combined from the backend server’s perspective, and the attacker has now poisoned the other user’s response.</i></sup></p><p>We’ve since <a href="https://github.com/cloudflare/pingora/commit/824bdeefc61e121cc8861de1b35e8e8f39026ecd"><u>patched</u></a> Pingora to switch the interpretation of subsequent bytes only once the upstream responds with <code>101 Switching Protocols</code>.</p><p>We verified Cloudflare was <b>not affected</b> for two reasons:</p><ol><li><p>The ingress CDN proxies do not have this improper behavior.</p></li><li><p>The clients to our internal Pingora services do not attempt to <a href="https://en.wikipedia.org/wiki/HTTP_pipelining"><u>pipeline</u></a> HTTP/1 requests. Furthermore, the Pingora service these clients talk directly with disables keep-alive on these <code>Upgrade</code> requests by injecting a <code>Connection: close</code> header; this prevents additional requests that would be sent — and subsequently smuggled — over the same connection.</p></li></ol>
    <div>
      <h3>2. HTTP/1.0, close-delimiting, and transfer-encoding</h3>
      <a href="#2-http-1-0-close-delimiting-and-transfer-encoding">
        
      </a>
    </div>
    <p>The reporter also demonstrated what <i>appeared</i> to be a more classic “CL.TE” desync-type attack, where the Pingora proxy would use Content-Length as framing while the backend would use Transfer-Encoding as framing:</p>
            <pre><code>GET / HTTP/1.0
Host: example.com
Connection: keep-alive
Transfer-Encoding: identity, chunked
Content-Length: 29

0

GET /admin HTTP/1.1
X:
</code></pre>
            <p>In the reporter’s example, Pingora would treat all subsequent bytes after the first GET / request header as part of that request’s body, but the node.js backend server would interpret the body as chunked and ending at the zero-length chunk. There are actually a few things going on here:</p><ol><li><p>Pingora’s chunked encoding recognition was quite barebones (only checking for whether <code>Transfer-Encoding</code> was “<a href="https://github.com/cloudflare/pingora/blob/9ac75d0356f449d26097e08bf49af14de6271727/pingora-core/src/protocols/http/v1/common.rs#L146"><u>chunked</u></a>”) and assumed that there could only be one encoding or <code>Transfer-Encoding</code> header. But the RFC only <a href="https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.4.1"><u>mandates</u></a> that the <i>final</i> encoding must be <code>chunked</code> to apply chunked framing. So per RFC, this request should have a chunked message body (if it were not HTTP/1.0 — more on that below).</p></li><li><p>Pingora was <i>also </i>not actually using the <code>Content-Length</code> (because the Transfer-Encoding overrode the Content-Length <a href="https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.3"><u>per RFC</u></a>). Because of the unrecognized Transfer-Encoding and the HTTP/1.0 version, the request body was <a href="https://github.com/cloudflare/pingora/blob/ef017ceb01962063addbacdab2a4fd2700039db5/pingora-core/src/protocols/http/v1/server.rs#L817"><u>instead treated as close-delimited</u></a> (which means that the response body’s end is marked by closure of the underlying transport connection). An absence of framing headers would also trigger the same misinterpretation on HTTP/1.0. Although response bodies are allowed to be close-delimited, request bodies are <i>never</i> close-delimited. In fact, this clarification is now explicitly called out as a separate note in <a href="https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-4.1"><u>RFC 9112</u></a>.</p></li><li><p>This is an HTTP/1.0 request that <a href="https://datatracker.ietf.org/doc/html/rfc9112#appendix-C.2.3-1"><u>did not define</u></a> Transfer-Encoding. The RFC <a href="https://datatracker.ietf.org/doc/html/rfc9112#section-6.1-16">mandates</a> that HTTP/1.0 requests containing Transfer-Encoding must “treat the message as if the framing is faulty” and close the connection. Parsers such as the ones in nginx and hyper just reject these requests to avoid ambiguous framing.</p></li></ol>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1jLbMNafmF96toxAPxj2Cm/8561b96a56dc0fc654476e33d0f34888/image2.png" />
          </figure><p><sup><i>When an attacker pipelines a partial HTTP request header after the HTTP/1.0 + Transfer-Encoding request, Pingora would incorrectly interpret that partial header as part of the same request, rather than as a distinct request. This enables the same kind of desync attack as described in the premature Upgrade example.</i></sup></p><p>This spoke to a more fundamental misreading of the RFC particularly in terms of response vs. request message framing. We’ve since fixed the improper <a href="https://github.com/cloudflare/pingora/commit/7f7166d62fa916b9f11b2eb8f9e3c4999e8b9023"><u>multiple Transfer-Encoding parsing</u></a>, adhere strictly to the request length guidelines such that HTTP request bodies can <a href="https://github.com/cloudflare/pingora/commit/40c3c1e9a43a86b38adeab8da7a2f6eba68b83ad"><u>never be considered close-delimited</u></a>, and reject <a href="https://github.com/cloudflare/pingora/commit/fc904c0d2c679be522de84729ec73f0bd344963d"><u>invalid Content-Length</u></a> and <a href="https://github.com/cloudflare/pingora/commit/87e2e2fb37edf9be33e3b1d04726293ae6bf2052"><u>HTTP/1.0 + Transfer-Encoding</u></a> request messages. Further protections we’ve added include <a href="https://github.com/cloudflare/pingora/commit/d3d2cf5ef4eca1e5d327fe282ec4b4ee474350c6"><u>rejecting</u></a> <a href="https://datatracker.ietf.org/doc/html/rfc9110#name-connect"><u>CONNECT</u></a> requests by default because the HTTP proxy logic doesn’t currently treat CONNECT as special for the purposes of CONNECT upgrade proxying, and these requests have special <a href="https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.2"><u>message framing rules</u></a>. (Note that incoming CONNECT requests are <a href="https://developers.cloudflare.com/fundamentals/concepts/traffic-flow-cloudflare/#cloudflares-network"><u>rejected</u></a> by the Cloudflare CDN.)</p><p>When we investigated and instrumented our services internally, we found no requests arriving at our Pingora services that would have been misinterpreted. We found that downstream proxy layers in the CDN would forward as HTTP/1.1 only, reject ambiguous framing such as invalid Content-Length, and only forward a single <code>Transfer-Encoding: chunked</code> header for chunked requests.</p>
    <div>
      <h3>3. Cache key construction</h3>
      <a href="#3-cache-key-construction">
        
      </a>
    </div>
    <p>The researcher also reported one other cache poisoning vulnerability regarding default <code>CacheKey</code> construction. The <a href="https://github.com/cloudflare/pingora/blob/ef017ceb01962063addbacdab2a4fd2700039db5/pingora-cache/src/key.rs#L218"><u>naive default implementation</u></a> factored in only the URI path (without other factors such as host header or upstream server HTTP scheme), which meant different hosts using the same HTTP path could collide and poison each other’s cache.</p><p>This would affect users of the alpha proxy caching feature who chose to use the default <code>CacheKey</code> implementation. We have since <a href="https://github.com/cloudflare/pingora/commit/257b59ada28ed6cac039f67d0b71f414efa0ab6e"><u>removed that default</u></a>, because while using something like HTTP scheme + host + URI makes sense for many applications, we want users to be careful when constructing their cache keys for themselves. If their proxy logic will conditionally adjust the URI or method on the upstream request, for example, that logic likely also must be factored into the cache key scheme to avoid poisoning.</p><p>Internally, Cloudflare’s <a href="https://developers.cloudflare.com/cache/how-to/cache-keys/"><u>default cache key</u></a> uses a number of factors to prevent cache key poisoning, and never made use of the previously provided default.</p>
    <div>
      <h2>Recommendation</h2>
      <a href="#recommendation">
        
      </a>
    </div>
    <p>If you use Pingora as a proxy, upgrade to <a href="https://github.com/cloudflare/pingora/releases/tag/0.8.0"><u>Pingora 0.8.0</u></a> at your earliest convenience.</p><p>We apologize for the impact this vulnerability may have had on Pingora users. As Pingora earns its place as critical Internet infrastructure beyond Cloudflare, we believe it’s important for the framework to promote use of strict RFC compliance by default and will continue this effort. Very few users of the framework should have to deal with the same “wild Internet” that Cloudflare does. Our intention is that stricter adherence to the latest RFC standards by default will harden security for Pingora users and move the Internet as a whole toward best practices.</p>
    <div>
      <h2>Disclosure and response timeline</h2>
      <a href="#disclosure-and-response-timeline">
        
      </a>
    </div>
    <p>- 2025‑12‑02: Upgrade‑based smuggling reported via bug bounty.</p><p>- 2026‑01‑13: Transfer‑Encoding / HTTP/1.0 parsing issues reported.</p><p>- 2026-01-18: Default cache key construction issue reported.</p><p>- 2026‑01‑29 to 2026‑02‑13: Fixes validated with the reporter. Work on more RFC-compliance checks continues.</p><p>- 2026-02-25: Cache key default removal and additional RFC checks validated with researcher.</p><p>- 2026‑03-02: Pingora 0.8.0 released.</p><p>- 2026-03-04: CVE advisories published.</p>
    <div>
      <h2>Acknowledgements</h2>
      <a href="#acknowledgements">
        
      </a>
    </div>
    <p>We thank Rajat Raghav (xclow3n) for the report, detailed reproductions, and verification of the fixes through our bug bounty program. Please see the researcher's<a href="https://xclow3n.github.io/post/6"> corresponding blog post</a> for more information.</p><p>We would also extend a heartfelt thank you to the Pingora open source community for their active engagement, issue reports, and contributions to the framework. You truly help us build a better Internet.</p> ]]></content:encoded>
            <category><![CDATA[Pingora]]></category>
            <category><![CDATA[Application Security]]></category>
            <category><![CDATA[Open Source]]></category>
            <category><![CDATA[Security]]></category>
            <guid isPermaLink="false">1b0iJgL57wbfiLHXhEjuwR</guid>
            <dc:creator>Edward Wang</dc:creator>
            <dc:creator>Fei Deng</dc:creator>
            <dc:creator>Andrew Hauck</dc:creator>
        </item>
        <item>
            <title><![CDATA[Resolving a request smuggling vulnerability in Pingora]]></title>
            <link>https://blog.cloudflare.com/resolving-a-request-smuggling-vulnerability-in-pingora/</link>
            <pubDate>Thu, 22 May 2025 13:00:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare patched a vulnerability (CVE-2025-4366) in the Pingora OSS framework, which exposed users of the framework and Cloudflare CDN’s free tier to potential request smuggling attacks. ]]></description>
            <content:encoded><![CDATA[ <p>On April 11, 2025 09:20 UTC, Cloudflare was notified via its <a href="https://www.cloudflare.com/disclosure/"><u>Bug Bounty Program</u></a> of a request smuggling vulnerability (<a href="https://www.cve.org/cverecord?id=CVE-2025-4366"><u>CVE-2025-4366</u></a>) in the <a href="https://github.com/cloudflare/pingora/tree/main"><u>Pingora OSS framework</u></a> discovered by a security researcher experimenting to find exploits using Cloudflare’s Content Delivery Network (CDN) free tier which serves some cached assets via Pingora.</p><p>Customers using the free tier of Cloudflare’s CDN or users of the caching functionality provided in the open source <a href="https://github.com/cloudflare/pingora/tree/main/pingora-proxy"><u>pingora-proxy</u></a> and <a href="https://github.com/cloudflare/pingora/tree/main/pingora-cache"><u>pingora-cache</u></a> crates could have been exposed.  Cloudflare’s investigation revealed no evidence that the vulnerability was being exploited, and was able to mitigate the vulnerability by April 12, 2025 06:44 UTC within 22 hours after being notified.</p>
    <div>
      <h2>What was the vulnerability?</h2>
      <a href="#what-was-the-vulnerability">
        
      </a>
    </div>
    <p>The bug bounty report detailed that an attacker could potentially exploit an HTTP/1.1 request smuggling vulnerability on Cloudflare’s CDN service. The reporter noted that via this exploit, they were able to cause visitors to Cloudflare sites to make subsequent requests to their own server and observe which URLs the visitor was originally attempting to access.</p><p>We treat any potential request smuggling or caching issue with extreme urgency.  After our security team escalated the vulnerability, we began investigating immediately, took steps to disable traffic to vulnerable components, and deployed a patch. 
</p><p>We are sharing the details of the vulnerability, how we resolved it, and what we can learn from the action. No action is needed from Cloudflare customers, but if you are using the Pingora OSS framework, we strongly urge you to upgrade to a version of Pingora <a href="https://github.com/cloudflare/pingora/releases/tag/0.5.0"><u>0.5.0</u></a> or later.</p>
    <div>
      <h2>What is request smuggling?</h2>
      <a href="#what-is-request-smuggling">
        
      </a>
    </div>
    <p>Request smuggling is a type of attack where an attacker can exploit inconsistencies in the way different systems parse HTTP requests. For example, when a client sends an HTTP request to an application server, it typically passes through multiple components such as load balancers, reverse proxies, etc., each of which has to parse the HTTP request independently. If two of the components the request passes through interpret the HTTP request differently, an attacker can craft a request that one component sees as complete, but the other continues to parse into a second, malicious request made on the same connection.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4Zo8gcyLwmR2liZIUetcGe/d0647a83dc2bc1e676ee2b61f14c3964/image2.png" />
          </figure>
    <div>
      <h2>Request smuggling vulnerability in Pingora</h2>
      <a href="#request-smuggling-vulnerability-in-pingora">
        
      </a>
    </div>
    <p>In the case of Pingora, the reported request smuggling vulnerability was made possible due to a HTTP/1.1 parsing bug when caching was enabled.</p><p>The pingora-cache crate adds an HTTP caching layer to a Pingora proxy, allowing content to be cached on a configured storage backend to help improve response times, and reduce bandwidth and load on backend servers.</p><p>HTTP/1.1 supports “<a href="https://www.rfc-editor.org/rfc/rfc9112.html#section-9.3"><u>persistent connections</u></a>”, such that one TCP connection can be reused for multiple HTTP requests, instead of needing to establish a connection for each request. However, only one request can be processed on a connection at a time (with rare exceptions such as <a href="https://www.rfc-editor.org/rfc/rfc9112.html#section-9.3.2"><u>HTTP/1.1 pipelining</u></a>). The RFC notes that each request must have a “<a href="https://www.rfc-editor.org/rfc/rfc9112.html#section-9.3-7"><u>self-defined message length</u></a>” for its body, as indicated by headers such as <code>Content-Length</code> or <code>Transfer-Encoding</code> to determine where one request ends and another begins.</p><p>Pingora generally handles requests on HTTP/1.1 connections in an RFC-compliant manner, either ensuring the downstream request body is properly consumed or declining to reuse the connection if it encounters an error. After the bug was filed, we discovered that when caching was enabled, this logic was skipped on cache hits (i.e. when the service’s cache backend can serve the response without making an additional upstream request).</p><p>This meant on a cache hit request, after the response was sent downstream, any unread request body left in the HTTP/1.1 connection could act as a vector for request smuggling. When formed into a valid (but incomplete) header, the request body could “poison” the subsequent request. The following example is a spec-compliant HTTP/1.1 request which exhibits this behavior:</p>
            <pre><code>GET /attack/foo.jpg HTTP/1.1
Host: example.com
&lt;other headers…&gt;
content-length: 79

GET / HTTP/1.1
Host: attacker.example.com
Bogus: foo</code></pre>
            <p>Let’s say there is a different request to <code>victim.example.com</code> that will be sent after this one on the reused HTTP/1.1 connection to a Pingora reverse proxy. The bug means that a Pingora service may not respect the <code>Content-Length</code> header and instead misinterpret the smuggled request as the beginning of the next request:</p>
            <pre><code>GET /attack/foo.jpg HTTP/1.1
Host: example.com
&lt;other headers…&gt;
content-length: 79

GET / HTTP/1.1 // &lt;- “smuggled” body start, interpreted as next request
Host: attacker.example.com
Bogus: fooGET /victim/main.css HTTP/1.1 // &lt;- actual next valid req start
Host: victim.example.com
&lt;other headers…&gt;</code></pre>
            <p>Thus, the smuggled request could inject headers and its URL into a subsequent valid request sent on the same connection to a Pingora reverse proxy service.</p>
    <div>
      <h2>CDN request smuggling and hijacking</h2>
      <a href="#cdn-request-smuggling-and-hijacking">
        
      </a>
    </div>
    <p>On April 11, 2025, Cloudflare was in the process of rolling out a Pingora proxy component with caching support enabled to a subset of CDN free plan traffic. This component was vulnerable to this request smuggling attack, which could enable modifying request headers and/or URL sent to customer origins.</p><p>As previously noted, the security researcher reported that they were also able to cause visitors to Cloudflare sites to make subsequent requests to their own malicious origin and observe which site URLs the visitor was originally attempting to access. During our investigation, Cloudflare found that certain origin servers would be susceptible to this secondary attack effect. The smuggled request in the example above would be sent to the correct origin IP address per customer configuration, but some origin servers would respond to the rewritten attacker <code>Host</code> header with a 301 redirect. Continuing from the prior example:</p>
            <pre><code>GET / HTTP/1.1 // &lt;- “smuggled” body start, interpreted as next request
Host: attacker.example.com
Bogus: fooGET /victim/main.css HTTP/1.1 // &lt;- actual next valid req start
Host: victim.example.com
&lt;other headers…&gt;

HTTP/1.1 301 Moved Permanently // &lt;- susceptible victim origin response
Location: https://attacker.example.com/
&lt;other headers…&gt;</code></pre>
            <p>When the client browser followed the redirect, it would trigger this attack by sending a request to the attacker hostname, along with a Referrer header indicating which URL was originally visited, making it possible to load a malicious asset and observe what traffic a visitor was trying to access.</p>
            <pre><code>GET / HTTP/1.1 // &lt;- redirect-following request
Host: attacker.example.com
Referrer: https://victim.example.com/victim/main.css
&lt;other headers…&gt;</code></pre>
            <p>Upon verifying the Pingora proxy component was susceptible, the team immediately disabled CDN traffic to the vulnerable component on 2025-04-12 06:44 UTC to stop possible exploitation. By 2025-04-19 01:56 UTC and prior to re-enablement of any traffic to the vulnerable component, a patch fix to the component was released, and any assets cached on the component’s backend were invalidated in case of possible cache poisoning as a result of the injected headers.</p>
    <div>
      <h2>Remediation and next steps</h2>
      <a href="#remediation-and-next-steps">
        
      </a>
    </div>
    <p>If you are using the caching functionality in the Pingora framework, you should update to the latest version of <a href="https://github.com/cloudflare/pingora/releases/tag/0.5.0"><u>0.5.0.</u></a> If you are a Cloudflare customer with a free plan, you do not need to do anything, as we have already applied the patch for this vulnerability.</p>
    <div>
      <h2>Timeline</h2>
      <a href="#timeline">
        
      </a>
    </div>
    <p><i>All timestamps are in UTC.</i></p><ul><li><p>2025-04-11 09:20 – Cloudflare is notified of a CDN request smuggling vulnerability via the Bug Bounty Program.</p></li><li><p>2025-04-11 17:16 to 2025-04-12 03:28 – Cloudflare confirms vulnerability is reproducible and investigates which component(s) require necessary changes to mitigate.</p></li><li><p>2025-04-12 04:25 – Cloudflare isolates issue to roll out of a Pingora proxy component with caching enabled and prepares release to disable traffic to this component.</p></li><li><p>2025-04-12 06:44 – Rollout to disable traffic complete, vulnerability mitigated.</p></li></ul>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>We would like to sincerely thank <a href="https://www.linkedin.com/in/james-kettle-albinowax/"><u>James Kettle</u></a> &amp; <a href="https://www.linkedin.com/in/wannes-verwimp/"><u>Wannes Verwimp</u></a>, who responsibly disclosed this issue via our <a href="https://www.cloudflare.com/en-gb/disclosure/"><u>Cloudflare Bug Bounty Program</u></a>, allowing us to identify and mitigate the vulnerability. We welcome further submissions from our community of researchers to continually improve the security of all of our products and open source projects.</p><p>Whether you are a customer of Cloudflare or just a user of our Pingora framework, or both, we know that the trust you place in us is critical to how you connect your properties to the rest of the Internet. Security is a core part of that trust and for that reason we treat these kinds of reports and the actions that follow with serious urgency. We are confident about this patch and the additional safeguards that have been implemented, but we know that these kinds of issues can be concerning. Thank you for your continued trust in our platform. We remain committed to building with security as our top priority and responding swiftly and transparently whenever issues arise.</p> ]]></content:encoded>
            <category><![CDATA[Pingora]]></category>
            <category><![CDATA[CDN]]></category>
            <category><![CDATA[Security]]></category>
            <category><![CDATA[CVE]]></category>
            <category><![CDATA[Bug Bounty]]></category>
            <guid isPermaLink="false">W02DuD98fCm1sYwa3gNH8</guid>
            <dc:creator>Edward Wang</dc:creator>
            <dc:creator>Andrew Hauck</dc:creator>
            <dc:creator>Aki Shugaeva</dc:creator>
        </item>
        <item>
            <title><![CDATA[Open sourcing Pingora: our Rust framework for building programmable network services]]></title>
            <link>https://blog.cloudflare.com/pingora-open-source/</link>
            <pubDate>Wed, 28 Feb 2024 15:00:11 GMT</pubDate>
            <description><![CDATA[ Pingora, our framework for building programmable and memory-safe network services, is now open source. Get started using Pingora today ]]></description>
            <content:encoded><![CDATA[ <p></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7aQBSJRQlM3b1ZRdvJycqY/72f9bd908abc139faba41716074d69d5/Rock-crab-pingora-open-source-mascot.png" />
            
            </figure><p>Today, we are proud to open source Pingora, the Rust framework we have been using to build services that power a significant portion of the traffic on Cloudflare. Pingora is <a href="https://github.com/cloudflare/pingora">released</a> under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License version 2.0</a>.</p><p>As mentioned in our previous blog post, <a href="/how-we-built-pingora-the-proxy-that-connects-cloudflare-to-the-internet">Pingora</a> is a Rust async multithreaded framework that assists us in constructing HTTP proxy services. Since our last blog post, Pingora has handled nearly a quadrillion Internet requests across our global network.</p><p>We are open sourcing Pingora to help build a better and more secure Internet beyond our own infrastructure. We want to provide tools, ideas, and inspiration to our customers, users, and others to build their own Internet infrastructure using a memory safe framework. Having such a framework is especially crucial given the increasing awareness of the importance of memory safety across the <a href="https://www.theregister.com/2022/09/20/rust_microsoft_c/">industry</a> and the <a href="https://www.whitehouse.gov/oncd/briefing-room/2024/02/26/press-release-technical-report/">US government</a>. Under this common goal, we are collaborating with the <a href="https://www.abetterinternet.org/">Internet Security Research Group</a> (ISRG) <a href="https://www.memorysafety.org/blog/introducing-river">Prossimo project</a> to help advance the adoption of Pingora in the Internet’s most critical infrastructure.</p><p>In our <a href="/how-we-built-pingora-the-proxy-that-connects-cloudflare-to-the-internet">previous blog post</a>, we discussed why and how we built Pingora. In this one, we will talk about why and how you might use Pingora.</p><p>Pingora provides building blocks for not only proxies but also clients and servers. Along with these components, we also provide a few utility libraries that implement common logic such as <a href="/how-pingora-keeps-count/">event counting</a>, error handling, and caching.</p>
    <div>
      <h3>What’s in the box</h3>
      <a href="#whats-in-the-box">
        
      </a>
    </div>
    <p>Pingora provides libraries and APIs to build services on top of HTTP/1 and HTTP/2, TLS, or just TCP/UDS. As a proxy, it supports HTTP/1 and HTTP/2 end-to-end, gRPC, and websocket proxying. (HTTP/3 support is on the roadmap.) It also comes with customizable load balancing and failover strategies. For compliance and security, it supports both the commonly used OpenSSL and BoringSSL libraries, which come with FIPS compliance and <a href="https://pq.cloudflareresearch.com/">post-quantum crypto</a>.</p><p>Besides providing these features, Pingora provides filters and callbacks to allow its users to fully customize how the service should process, transform and forward the requests. These APIs will be especially familiar to OpenResty and NGINX users, as many map intuitively onto OpenResty's "*_by_lua" callbacks.</p><p>Operationally, Pingora provides zero downtime graceful restarts to upgrade itself without dropping a single incoming request. Syslog, Prometheus, Sentry, OpenTelemetry and other must-have observability tools are also easily integrated with Pingora as well.</p>
    <div>
      <h3>Who can benefit from Pingora</h3>
      <a href="#who-can-benefit-from-pingora">
        
      </a>
    </div>
    <p>You should consider Pingora if:</p><p><b>Security is your top priority:</b> Pingora is a more memory safe alternative for services that are written in C/C++. While some might argue about memory safety among programming languages, from our practical experience, we find ourselves way less likely to make coding mistakes that lead to memory safety issues. Besides, as we spend less time struggling with these issues, we are more productive implementing new features.</p><p><b>Your service is performance-sensitive:</b> Pingora is fast and efficient. As explained in our previous blog post, we saved a lot of CPU and memory resources thanks to Pingora’s multi-threaded architecture. The saving in time and resources could be compelling for workloads that are sensitive to the cost and/or the speed of the system.</p><p><b>Your service requires extensive customization:</b> The APIs that the Pingora proxy framework provides are highly programmable. For users who wish to build a customized and advanced gateway or load balancer, Pingora provides powerful yet simple ways to implement it. We provide examples in the next section.</p>
    <div>
      <h2>Let’s build a load balancer</h2>
      <a href="#lets-build-a-load-balancer">
        
      </a>
    </div>
    <p>Let's explore Pingora's programmable API by building a simple load balancer. The load balancer will select between <a href="https://1.1.1.1/">https://1.1.1.1/</a> and <a href="https://1.0.0.1/">https://1.0.0.1/</a> to be the upstream in a round-robin fashion.</p><p>First let’s create a blank HTTP proxy.</p>
            <pre><code>pub struct LB();

#[async_trait]
impl ProxyHttp for LB {
    async fn upstream_peer(...) -&gt; Result&lt;Box&lt;HttpPeer&gt;&gt; {
        todo!()
    }
}</code></pre>
            <p>Any object that implements the <code>ProxyHttp</code> trait (similar to the concept of an interface in C++ or Java) is an HTTP proxy. The only required method there is <code>upstream_peer()</code>, which is called for every request. This function should return an <code>HttpPeer</code> which contains the origin IP to connect to and how to connect to it.</p><p>Next let’s implement the round-robin selection. The Pingora framework already provides the <code>LoadBalancer</code> with common selection algorithms such as round robin and hashing, so let’s just use it. If the use case requires more sophisticated or customized server selection logic, users can simply implement it themselves in this function.</p>
            <pre><code>pub struct LB(Arc&lt;LoadBalancer&lt;RoundRobin&gt;&gt;);

#[async_trait]
impl ProxyHttp for LB {
    async fn upstream_peer(...) -&gt; Result&lt;Box&lt;HttpPeer&gt;&gt; {
        let upstream = self.0
            .select(b"", 256) // hash doesn't matter for round robin
            .unwrap();

        // Set SNI to one.one.one.one
        let peer = Box::new(HttpPeer::new(upstream, true, "one.one.one.one".to_string()));
        Ok(peer)
    }
}</code></pre>
            <p>Since we are connecting to an HTTPS server, the SNI also needs to be set. Certificates, timeouts, and other connection options can also be set here in the HttpPeer object if needed.</p><p>Finally, let's put the service in action. In this example we hardcode the origin server IPs. In real life workloads, the origin server IPs can also be discovered dynamically when the <code>upstream_peer()</code> is called or in the background. After the service is created, we just tell the LB service to listen to 127.0.0.1:6188. In the end we created a Pingora server, and the server will be the process which runs the load balancing service.</p>
            <pre><code>fn main() {
    let mut upstreams = LoadBalancer::try_from_iter(["1.1.1.1:443", "1.0.0.1:443"]).unwrap();

    let mut lb = pingora_proxy::http_proxy_service(&amp;my_server.configuration, LB(upstreams));
    lb.add_tcp("127.0.0.1:6188");

    let mut my_server = Server::new(None).unwrap();
    my_server.add_service(lb);
    my_server.run_forever();
}</code></pre>
            <p>Let’s try it out:</p>
            <pre><code>curl 127.0.0.1:6188 -svo /dev/null
&gt; GET / HTTP/1.1
&gt; Host: 127.0.0.1:6188
&gt; User-Agent: curl/7.88.1
&gt; Accept: */*
&gt; 
&lt; HTTP/1.1 403 Forbidden</code></pre>
            <p>We can see that the proxy is working, but the origin server rejects us with a 403. This is because our service simply proxies the Host header, 127.0.0.1:6188, set by curl, which upsets the origin server. How do we make the proxy correct that? This can simply be done by adding another filter called <code>upstream_request_filter</code>. This filter runs on every request after the origin server is connected and before any HTTP request is sent. We can add, remove or change http request headers in this filter.</p>
            <pre><code>async fn upstream_request_filter(…, upstream_request: &amp;mut RequestHeader, …) -&gt; Result&lt;()&gt; {
    upstream_request.insert_header("Host", "one.one.one.one")
}</code></pre>
            <p>Let’s try again:</p>
            <pre><code>curl 127.0.0.1:6188 -svo /dev/null
&lt; HTTP/1.1 200 OK</code></pre>
            <p>This time it works! The complete example can be found <a href="https://github.com/cloudflare/pingora/blob/main/pingora-proxy/examples/load_balancer.rs">here</a>.</p><p>Below is a very simple diagram of how this request flows through the callback and filter we used in this example. The Pingora proxy framework currently provides <a href="https://github.com/cloudflare/pingora/blob/main/docs/user_guide/phase.md">more filters</a> and callbacks at different stages of a request to allow users to modify, reject, route and/or log the request (and response).</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/WvHfhONcYEEL4kPRwGzKp/f2456d177e727063a49265eea831b8af/Flow_Diagram.png" />
            
            </figure><p>Behind the scenes, the Pingora proxy framework takes care of connection pooling, TLS handshakes, reading, writing, parsing requests and any other common proxy tasks so that users can focus on logic that matters to them.</p>
    <div>
      <h2>Open source, present and future</h2>
      <a href="#open-source-present-and-future">
        
      </a>
    </div>
    <p>Pingora is a library and toolset, not an executable binary. In other words, Pingora is the engine that powers a car, not the car itself. Although Pingora is production-ready for industry use, we understand a lot of folks want a batteries-included, ready-to-go web service with low or no-code config options. Building that application on top of Pingora will be the focus of our collaboration with the ISRG to expand Pingora's reach. Stay tuned for future announcements on that project.</p><p>Other caveats to keep in mind:</p><ul><li><p><b>Today, API stability is not guaranteed.</b> Although we will try to minimize how often we make breaking changes, we still reserve the right to add, remove, or change components such as request and response filters as the library evolves, especially during this pre-1.0 period.</p></li><li><p><b>Support for non-Unix based operating systems is not currently on the roadmap.</b> We have no immediate plans to support these systems, though this could change in the future.</p></li></ul>
    <div>
      <h2>How to contribute</h2>
      <a href="#how-to-contribute">
        
      </a>
    </div>
    <p>Feel free to raise bug reports, documentation issues, or feature requests in our GitHub <a href="https://github.com/cloudflare/pingora/issues">issue tracker</a>. Before opening a pull request, we strongly suggest you take a look at our <a href="https://github.com/cloudflare/pingora/blob/main/.github/CONTRIBUTING.md">contribution guide</a>.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>In this blog we announced the open source of our Pingora framework. We showed that Internet entities and infrastructure can benefit from Pingora’s security, performance and customizability. We also demonstrated how easy it is to use Pingora and how customizable it is.</p><p>Whether you're building production web services or experimenting with network technologies we hope you find value in Pingora. It's been a long journey, but sharing this project with the open source community has been a goal from the start. We'd like to thank the Rust community as Pingora is built with many great open-sourced Rust crates. Moving to a memory safe Internet may feel like an impossible journey, but it's one we hope you join us on.</p> ]]></content:encoded>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Rust]]></category>
            <category><![CDATA[Open Source]]></category>
            <category><![CDATA[Performance]]></category>
            <category><![CDATA[Pingora]]></category>
            <guid isPermaLink="false">38GzyqaZUYTF8fJFAiwpfx</guid>
            <dc:creator>Yuchen Wu</dc:creator>
            <dc:creator>Edward Wang</dc:creator>
            <dc:creator>Andrew Hauck</dc:creator>
        </item>
        <item>
            <title><![CDATA[Early Hints update: How Cloudflare, Google, and Shopify are working together to build a faster Internet for everyone]]></title>
            <link>https://blog.cloudflare.com/early-hints-performance/</link>
            <pubDate>Thu, 23 Jun 2022 15:59:08 GMT</pubDate>
            <description><![CDATA[ During a time of uncertainty due to the global pandemic, a time when everyone was more online than ever before, Cloudflare, Google, and Shopify all came together to build and test Early Hints ]]></description>
            <content:encoded><![CDATA[ <p></p><p>A few months ago, we wrote a <a href="/early-hints/">post</a> focused on a product we were building that could vastly improve page load performance. That product, known as Early Hints, has seen wide adoption since that original post. In early benchmarking experiments with Early Hints, we saw performance improvements that were as high as 30%.</p><p>Now, with over 100,000 customers using Early Hints on Cloudflare, we are excited to talk about how much Early Hints have improved page loads for our customers in production, how customers can get the most out of Early Hints, and provide an update on the next iteration of Early Hints we’re building.</p>
    <div>
      <h3>What Are Early Hints again?</h3>
      <a href="#what-are-early-hints-again">
        
      </a>
    </div>
    <p>As a reminder, the browser you’re using right now to read this page needed instructions for what to render and what resources (like images, fonts, and scripts) need to be fetched from somewhere else in order to complete the loading of this (or any given) web page. When you decide you want to see a page, your browser sends a request to a server and the instructions for what to load come from the server’s response. These responses are generally composed of a multitude of <a href="https://developer.chrome.com/docs/devtools/resources/#browse">resources</a> that tell the browser what content to load and how to display it to the user. The servers sending these instructions to your browser often need time to gather up all of the resources in order to compile the whole webpage. This period is known as “server think time.” Traditionally, during the “server think time” the browser would sit waiting until the server has finished gathering all the required resources and is able to return the full response.</p><p>Early Hints was designed to take advantage of this “server think time” to send instructions to the browser to begin loading readily-available resources <i>while</i> the server finishes compiling the full response. Concretely, the server sends two responses: the first to instruct the browser on what it can begin loading right away, and the second is the full response with the remaining information. By sending these hints to a browser before the full response is prepared, the browser can figure out what it needs to do to load the webpage faster for the end user.</p><p>Early Hints uses the <a href="https://datatracker.ietf.org/doc/html/rfc8297">HTTP status code 103</a> as the first response to the client. The “hints” are HTTP headers attached to the 103 response that are likely to appear in the final response, indicating (with the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link">Link</a> header) resources the browser should begin loading while the server prepares the final response. Sending hints on which assets to expect before the entire response is compiled allows the browser to use this “think time” (when it would otherwise have been sitting idle) to fetch needed assets, prepare parts of the displayed page, and otherwise get ready for the full response to be returned.</p><p>Early Hints on Cloudflare accomplishes performance improvements in three ways:</p><ul><li><p>By sending a response where resources are directed to be <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload">preloaded</a> by the browser. Preloaded resources direct the browser to begin loading the specified resources as they will be needed soon to load the full page. For example, if the browser needs to fetch a font resource from a third party, that fetch can happen before the full response is returned, so the font is already waiting to be used on the page when the full response returns from the server.</p></li><li><p>By using <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preconnect">preconnect</a> to initiate a connection to places where content will be returned from an origin server. For example, if a Shopify storefront needs content from a Shopify origin to finish loading the page, preconnect will warm up the connection which improves the performance for when the origin returns the content.</p></li><li><p>By caching and emitting Early Hints on Cloudflare, we make an efficient use of the full waiting period - not just server think time - which includes transit latency to the origin. Cloudflare sits within 50 milliseconds of 95% of the Internet-connected population globally. So while a request is routed to an origin and the final response is being compiled, Cloudflare can send an Early Hint from much closer and the browser can begin loading.</p></li></ul>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5X6KUywKFKIT4njcqG6oDx/6f2dba740fc457b264f93e5a0f84a0cb/image1-26.png" />
            
            </figure><p>Early Hints is like multitasking across the Internet - at the same time the origin is compiling resources for the final response and making calls to databases or other servers, the browser is already beginning to load assets for the end user.</p>
    <div>
      <h3>What’s new with Early Hints?</h3>
      <a href="#whats-new-with-early-hints">
        
      </a>
    </div>
    <p>While developing Early Hints, we’ve been fortunate to work with Google and Shopify to collect data on the performance impact. Chrome provided web developers with <a href="https://developer.chrome.com/en/docs/web-platform/origin-trials/">experimental access</a> to both <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload">preload</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preconnect">preconnect</a> support for Link headers in Early Hints. Shopify worked with us to guide the development by providing test frameworks which were invaluable to getting real performance data.</p><p>Today is a big day for Early Hints. Google <a href="https://developer.chrome.com/blog/early-hints/">announced</a> that Early Hints is available in Chrome version 103 with support for preload and preconnect to start. Previously, Early Hints was available via an <a href="https://developer.chrome.com/en/docs/web-platform/origin-trials">origin trial</a> so that Chrome could measure the full performance benefit (A/B test). Now that the data has been collected and analyzed, and we’ve been able to prove a substantial improvement to page load, we’re excited that Chrome’s full support of Early Hints will mean that many more requests will see the performance benefits.</p><p>That's not the only big news coming out about Early Hints. Shopify battle-tested Cloudflare’s implementation of Early Hints during <a href="https://blog.cloudflare.com/the-truth-about-black-friday-and-cyber-monday/">Black Friday/Cyber Monday</a> 2021 and is sharing the performance benefits they saw during the busiest shopping time of the year:</p><blockquote><p>Today, HTTP 103 Early Hints ships with Chrome 103!</p><p>Why is this important for <a href="https://twitter.com/hashtag/webperf?src=hash&amp;ref_src=twsrc%5Etfw">#webperf</a>? How did <a href="https://twitter.com/Shopify?ref_src=twsrc%5Etfw">@Shopify</a> help make all merchant sites faster? (LCP over 500ms faster at p50!) ?</p><p>Hint: A little collaboration w/ <a href="https://twitter.com/Cloudflare?ref_src=twsrc%5Etfw">@cloudflare</a> &amp; <a href="https://twitter.com/googlechrome?ref_src=twsrc%5Etfw">@googlechrome</a> <a href="https://t.co/Dz7BD4Jplp">pic.twitter.com/Dz7BD4Jplp</a></p><p>— Colin Bendell (@colinbendell) <a href="https://twitter.com/colinbendell/status/1539322190541295616?ref_src=twsrc%5Etfw">June 21, 2022</a></p></blockquote><p>While talking to the audience at <a href="https://www.cloudflare.com/connect2022/">Cloudflare Connect London</a> last week, Colin Bendell, Director, Performance Engineering at Shopify summarized it best: "<i>when a buyer visits a website, if that first page that (they) experience is just 10% faster, on average there is a 7% increase in conversion</i>". The beauty of Early Hints is you can get that sort of speedup easily, and with Early Hints that can be one click away.</p><p>You can see a portion of his talk here:</p><div></div>
<p></p><p>The headline here is that during a time of vast uncertainty due to the global pandemic, a time when everyone was more online than ever before, when people needed their Internet to be reliably fast — Cloudflare, Google, and Shopify all came together to build and test Early Hints so that the whole Internet would be a faster, better, and more efficient place.</p><p>So how much did Early Hints improve performance of customers’ websites?</p>
    <div>
      <h3>Performance Improvement with Early Hints</h3>
      <a href="#performance-improvement-with-early-hints">
        
      </a>
    </div>
    <p>In our simple tests back in September, we were able to accelerate the <a href="https://web.dev/lcp/#what-is-lcp">Largest Contentful Paint (LCP)</a> by 20-30%. Granted, this result was on an artificial page with mostly large images where Early Hints impact could be maximized. As for Shopify, we also knew their storefronts were <a href="/early-hints/#how-can-we-speed-up-slow-dynamic-page-loads">particularly good candidates</a> for Early Hints. Each mom-and-pop.shop page depends on many assets served from cdn.shopify.com - speeding up a preconnect to that host should meaningfully accelerate loading those assets.</p><p>But what about other zones? We expected most origins already using Link preload and preconnect headers to see at least modest improvements if they turned on Early Hints. We wanted to assess performance impact for other uses of Early Hints beyond Shopify’s.</p><p>However, <a href="https://www.cloudflare.com/application-services/solutions/app-performance-monitoring/">getting good data on web page performance impact</a> can be tricky. Not every 103 response from Cloudflare will result in a subsequent request through our network. Some hints tell the browser to preload assets on important third-party origins, for example. And not every Cloudflare zone may have <a href="/introducing-browser-insights/">Browser Insights</a> enabled to gather Real User Monitoring data.</p><p>Ultimately, we decided to do some lab testing with <a href="https://www.webpagetest.org/">WebPageTest</a> of a sample of the most popular websites (top 1,000 by request volume) using Early Hints on their URLs with preload and preconnect Link headers. WebPageTest (which we’ve <a href="/workers-and-webpagetest/">written about in the past</a>) is an excellent tool to visualize and collect metrics on web page performance across a variety of device and connectivity settings.</p>
    <div>
      <h3>Lab Testing</h3>
      <a href="#lab-testing">
        
      </a>
    </div>
    <p>In our earlier blog post, we were mainly focused on Largest Contentful Paint (LCP), which is the time at which the browser renders the largest visible image or text block, relative to the start of the page load. Here we’ll focus on improvements not only to LCP, but also <a href="https://web.dev/fcp">FCP (First Contentful Paint)</a>, which is the time at which the browser first renders visible content relative to the start of the page load.</p><p>We compared test runs with Early Hints support off and on (in Chrome), across four different simulated environments: desktop with a cable connection (5Mbps download / 28ms <a href="https://www.cloudflare.com/learning/cdn/glossary/round-trip-time-rtt/">RTT</a>), mobile with 3G (1.6Mbps / 300ms RTT), mobile with low-latency 3G (1.6Mbps / 150ms RTT) and mobile with 4G (9Mbps / 170ms RTT). After running the tests, we cleaned the data to remove URLs with no visual completeness metrics or less than five DOM elements. (These usually indicated document fragments vs. a page a user might actually navigate to.) This gave us a final sample population of a little more than 750 URLs, each from distinct zones.</p><p>In the box plots below, we’re comparing FCP and LCP percentiles between the timing data control runs (no Early Hints) and the runs with Early Hints enabled. Our sample population represents a variety of zones, some of which load relatively quickly and some far slower, thus the long whiskers and string of outlier points climbing the y-axis. The y-axis is constrained to the max p99 of the dataset, to ensure 99% of the data are reflected in the graph while still letting us focus on the p25 / p50 / p75 differences.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1j17ZcUiVpUfYsuGE2INtM/42a00c9d5f429f2dbc712bf749ab9069/image2-26.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7h5iGHo8JCg7a6tYeBxaiV/80c501e9d52cb7deacaa43034b277fe9/image6-10.png" />
            
            </figure><p>The relative shift in the box plot quantiles suggest we should expect modest benefits for Early Hints for the majority of web pages. By comparing FCP / LCP percentage improvement of the web pages from their respective baselines, we can quantify what those median and p75 improvements would look like:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1MNoOPalsR0oildpZutzx6/fd9fbd6b10f9521a6466045a17c41012/image8-7.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5BlauSorivIGRF5fZiu5Oy/45a0544dc24dab19fd23f2f238ce9f74/image4-14.png" />
            
            </figure><p>A couple observations:</p><ul><li><p>From the p50 values, we see that for 50% of web pages on desktop, Early Hints improved FCP by more than 9.47% and LCP by more than 6.03%. For the p75, or the upper 25%, FCP improved by more than 20.4% and LCP by more than 15.97%.</p></li><li><p>The sizable improvements in First Contentful Paint suggest many hints are for <a href="https://web.dev/render-blocking-resources/">render-blocking assets</a> (such as critical but dynamic stylesheets and scripts that can’t be embedded in the HTML document itself).</p></li><li><p>We see a greater percentage impact on desktop over cable and on mobile over 4G. In theory, the impact of Early Hints is bounded by the load time of the linked asset (i.e. ideally we could preload the entire asset before the browser requires it), so we might expect the FCP / LCP reduction to increase in step with latency. Instead, it appears to be the other way around. There could be many variables at play here - for example, the extra bandwidth the 4G connection provides seems to be more influential than the decreased latency between the two 3G connection settings. Likely that wider bandwidth pipe is especially helpful for URLs we observed that preloaded larger assets such as JS bundles or font files. We also found examples of pages that performed consistently worse on lower-grade connections (see our note on “over-hinting” below).</p></li><li><p>Quite a few sample zones cached their HTML pages on Cloudflare (~15% of the sample). For CDN cache hits, we’d expect Early Hints to be less influential on the final result (because the “server think time” is drastically shorter). Filtering them out from the sample, however, yielded almost identical relative improvement metrics.</p></li></ul><p>The relative distributions between control and Early Hints runs, as well as the per-site baseline improvements, show us Early Hints can be broadly beneficial for use cases beyond Shopify’s. As suggested by the p75+ values, we also still find plenty of case studies showing a more substantial potential impact to LCP (and FCP) like the one we observed from our artificial test case, as indicated from these WebPageTest waterfall diagrams:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4SXT10k2QdHrLaBfVW2ygc/c40ba4f5f31aa48aa5c59658382dc699/image3-18.png" />
            
            </figure><p>These diagrams show the network and rendering activity on the same web page (which, bucking the trend, had some of its best results over mobile – 3G settings, shown here) for its first ten resources. Compare the WebPageTest waterfall view above (with Early Hints disabled) with the waterfall below (Early Hints enabled). The first green vertical line in each indicates First Contentful Paint. The page configures Link preload headers for a few JS / CSS assets, as well as a handful of key images. When Early Hints is on, those assets (numbered 2 through 9 below) get a significant head start from the preload hints. In this case, FCP and LCP improved by 33%!</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2zORzzS6Qvh3eX4KEvUW0P/854ed6d6fca1e26e5389e7736cf0b1e4/image5-9.png" />
            
            </figure>
    <div>
      <h3>Early Hints Best Practices and Strategies for Better Performance</h3>
      <a href="#early-hints-best-practices-and-strategies-for-better-performance">
        
      </a>
    </div>
    <p>The effect of Early Hints can vary widely on a case-by-case basis. We noticed particularly successful zones had one or more of the following:</p><ul><li><p>Preconnect Link headers to important third-party origins (e.g. an origin hosting the pages’ assets, or Google Fonts).</p></li><li><p>Preload Link headers for a handful of critical render-blocking resources.</p></li><li><p>Scripts and stylesheets split into chunks, enumerated in preload Links.</p></li><li><p>A preload Link for the LCP asset, e.g. the featured image on a blog post.</p></li></ul><p>It’s quite possible these strategies are already familiar to you if you work on web performance! Essentially the <a href="https://web.dev/uses-rel-preload/">best</a> <a href="https://web.dev/uses-rel-preconnect/">practices</a> that apply to using Link headers or  elements in the HTML  also apply to Early Hints. That is to say: if your web page is already using preload or preconnect Link headers, using Early Hints should amplify those benefits.</p><p>A cautionary note here: while it may be safer to aggressively send assets in Early Hints versus <a href="https://web.dev/performance-http2/">Server Push</a> (as the hints won’t arbitrarily send browser-cached content the way <a href="/early-hints/#didn-t-server-push-try-to-solve-this-problem">Server Push might</a>), it is still possible to _over-_hint non-critical assets and saturate network bandwidth in a similar manner to <a href="https://docs.google.com/document/d/1K0NykTXBbbbTlv60t5MyJvXjqKGsCVNYHyLEXIxYMv0/edit">overpushing</a>. For example, one page in our sample listed well over 50 images in its 103 response (but not one of its render-blocking JS scripts). It saw improvements over cable, but was consistently worse off in the higher latency, lower bandwidth mobile connection settings.</p><p>Google has great guidelines for configuring Link headers at your origin in their <a href="https://developer.chrome.com/blog/early-hints/">blog post</a>. As for emitting these Links as Early Hints, Cloudflare can take care of that for you!</p>
    <div>
      <h3>How to enable on Cloudflare</h3>
      <a href="#how-to-enable-on-cloudflare">
        
      </a>
    </div>
    <ul><li><p>To enable Early Hints on Cloudflare, simply sign in to your account and select the domain you’d like to enable it on.</p></li><li><p>Navigate to the <b>Speed Tab</b> of the dashboard.</p></li><li><p>Enable Early Hints.</p></li></ul><p>Enabling Early Hints means that we will harvest the preload and preconnect Link headers from your origin responses, cache them, and send them as 103 Early Hints for subsequent requests so that future visitors will be able to gain an even greater performance benefit.</p><p>For more information about our Early Hints feature, please refer to our <a href="/early-hints/">announcement post</a> or our <a href="https://developers.cloudflare.com/cache/about/early-hints/">documentation</a>.</p>
    <div>
      <h3>Smart Early Hints update</h3>
      <a href="#smart-early-hints-update">
        
      </a>
    </div>
    <p>In our <a href="/early-hints/">original blog post</a>, we also mentioned our intention to ship a product improvement to Early Hints that would generate the 103 on your behalf.</p><p>Smart Early Hints will generate Early Hints even when there isn’t a Link header present in the origin response from which we can harvest a 103. The goal is to be a no-code/configuration experience with massive improvements to page load. Smart Early Hints will infer what assets can be preloaded or <a href="https://web.dev/priority-hints/">prioritized</a> in different ways by analyzing responses coming from our customer’s origins. It will be your one-button web performance guru completely dedicated to making sure your site is loading as fast as possible.</p><p>This work is still under development, but we look forward to getting it built before the end of the year.</p>
    <div>
      <h3>Try it out!</h3>
      <a href="#try-it-out">
        
      </a>
    </div>
    <p>The promise Early Hints holds has only started to be explored, and we’re excited to continue to build products and features and make the web performance reliably fast.</p><p>We’ll continue to update you along our journey as we develop Early Hints and look forward to your <a href="https://community.cloudflare.com/">feedback</a> (special thanks to the Cloudflare Community members who have already been invaluable) as we move to bring Early Hints to everyone.</p> ]]></content:encoded>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Early Hints]]></category>
            <category><![CDATA[Partners]]></category>
            <guid isPermaLink="false">5T0MhC5gTkcWdAymdWAovY</guid>
            <dc:creator>Alex Krivit</dc:creator>
            <dc:creator>Edward Wang</dc:creator>
        </item>
        <item>
            <title><![CDATA[Third Time’s the Cache, No More]]></title>
            <link>https://blog.cloudflare.com/third-times-the-cache-no-more/</link>
            <pubDate>Fri, 19 Mar 2021 12:00:00 GMT</pubDate>
            <description><![CDATA[ Up until now, we wouldn’t cache requests with query strings until we saw them three times. We trace that behavior back to 2010, examine why we might have needed it, and show ourselves why we don’t need it anymore. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Caching is a big part of how Cloudflare CDN makes the Internet faster and more reliable. When a visitor to a customer’s website requests an asset, we retrieve it from the customer’s origin server. After that first request, in many cases we <i>cache</i> that asset. Whenever anyone requests it again, we can serve it from one of our data centers close to them, dramatically speeding up load times.</p><p>Did you notice the small caveat? We cache after the <i>first</i> request in <i>many</i> cases, not all. One notable exception since 2010 up until now: requests with <i>query strings</i>. When a request came with a query string (think <a href="https://example.com/image.jpg?width=500">https://example.com/image.jpg?width=500</a>; the <code>?width=500</code> is the query string), we needed to see it a whole <i>three</i> times before we would cache it on our <a href="https://support.cloudflare.com/hc/en-us/articles/200168256-Understand-Cloudflare-Caching-Level">default cache level</a>. Weird!</p><p>This is a short tale of that strange exception, why we thought we needed it, and how, more than ten years later, we showed ourselves that we didn’t.</p>
    <div>
      <h3>Two MISSes too many</h3>
      <a href="#two-misses-too-many">
        
      </a>
    </div>
    <p>To see the exception in action, here’s a command we ran a couple weeks ago. It requests an image hosted on <code>example.com</code> five times and prints each response’s <a href="https://support.cloudflare.com/hc/en-us/articles/200172516-Understanding-Cloudflare-s-CDN#h_bd959d6a-39c0-4786-9bcd-6e6504dcdb97">CF-Cache-Status header</a>. That header tells us what the cache did while serving the request.</p>
            <pre><code>❯ for i in {1..5}; do curl -svo /dev/null example.com/image.jpg 2&gt;&amp;1 | grep -e 'CF-Cache-Status'; sleep 3; done
&lt; CF-Cache-Status: MISS
&lt; CF-Cache-Status: HIT
&lt; CF-Cache-Status: HIT
&lt; CF-Cache-Status: HIT
&lt; CF-Cache-Status: HIT</code></pre>
            <p>The MISS means that we couldn’t find the asset in the cache and had to retrieve it from the origin server. On the HITs, we served the asset from cache.</p><p>Now, just by adding a query string to the same request (<code>?query=val</code>):</p>
            <pre><code>❯ for i in {1..5}; do curl -svo /dev/null example.com/image.jpg\?query\=val 2&gt;&amp;1 | grep -e 'CF-Cache-Status'; sleep 3; done
&lt; CF-Cache-Status: MISS
&lt; CF-Cache-Status: MISS
&lt; CF-Cache-Status: MISS
&lt; CF-Cache-Status: HIT
&lt; CF-Cache-Status: HIT</code></pre>
            <p>There they are - three MISSes, meaning two extra trips to the customer origin!</p><p>We traced this surprising behavior back to a git commit from Cloudflare’s earliest days in 2010. Since then, it has spawned chat threads, customer escalations, and even an internal Solutions Engineering blog post that christened it the “third time’s the charm” quirk.</p><p>It would be much less confusing if we could make the query string behavior align with the others, but why was the quirk here to begin with?</p>
    <div>
      <h3>Unpopular queries</h3>
      <a href="#unpopular-queries">
        
      </a>
    </div>
    <p>From an engineering perspective, forcing three MISSes for query strings can make sense as a way to protect ourselves from unnecessary disk writes.</p><p>That’s because for many requests with query strings in the URL, we may never see that URL requested again.</p><ol><li><p>Some query strings simply pass along data to the origin server that may not actually result in a different response from the base URL, for example, a visitor’s browser metadata. We end up caching the same response behind many different, potentially unique, cache keys.</p></li><li><p>Some query strings are <i>intended</i> to bypass caches. These “cache busting” requests append randomized query strings in order to force pulling from the origin, a strategy that some <a href="https://support.google.com/campaignmanager/answer/2837435?hl=en#zippy=%2Chow-does-cache-busting-work">ad managers use to count impressions</a>.</p></li></ol><p>Unpopular requests are not a new problem for us. Previously, <a href="/why-we-started-putting-unpopular-assets-in-memory/">we wrote about how we use an in-memory transient cache</a> to store requests we only ever see once (dubbed “one-hit-wonders”) so that they are never written to disk. This transient cache, enabled for just a subset of traffic, reduces our disk writes by 20-25%.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/57yHAVDQQJWmKXvIkZirLm/24ee2c3198f5af8b54a07c8e545ec9f8/image2-15.png" />
            
            </figure><p>Was this behavior from 2010 giving us similar benefits? Since then, our network has grown from 10,000 Internet properties to 25 million. It was time to re-evaluate. Just how much would our disk writes increase if we cached on the first request? How much better would cache hit ratios be?</p>
    <div>
      <h3>Hypotheses: querying the cache</h3>
      <a href="#hypotheses-querying-the-cache">
        
      </a>
    </div>
    <p>Good news: our metrics showed only around 3.5% of requests use the default cache level with query strings. Thus, we shouldn’t expect more than a few percentage points difference in disk writes. Less good: <a href="https://www.cloudflare.com/learning/cdn/what-is-a-cache-hit-ratio/">cache hit rate</a> shouldn’t increase much either.</p><p>On the other hand, we also found that a significant portion of these requests are images, which could potentially take up lots of disk space and hike up cache eviction rates. Enough napkin math - we needed to start assessing real world data.</p>
    <div>
      <h3>A/B testing: caching the queries</h3>
      <a href="#a-b-testing-caching-the-queries">
        
      </a>
    </div>
    <p>We were able to validate our hypotheses using an <a href="https://en.wikipedia.org/wiki/A/B_testing">A/B test</a>, where half the machines in a data center use the old behavior and half do not. This enabled us to control for Internet traffic fluctuations over time and get more precise impact numbers.</p><p>Other considerations: we limited the test to one data center handling a small slice of our traffic to minimize the impact of any negative side effects. We also disabled transient cache, which would nullify some of the I/O costs we expected to observe. By turning transient cache off for the experiment, we should see the upper bound of the disk write increase.</p><p>We ran this test for a couple days and, as hypothesized, found a minor but acceptable increase in disk writes per request to the tune of 2.5%.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2QFiZGM6WpUd9OYZ135xLb/1a57482bbecd6e08f93d4102b7b0359a/image3-17.png" />
            
            </figure><p>What’s more, we saw a modest hit rate increase of around +3% on average for our Enterprise customers. More hits meant fewer trips to origin servers and thus bandwidth savings for customers: in this case, a -5% decrease in total bytes served from origin.</p><p>Note that we saw hit rate increases for certain customers across <i>all</i> plan types. Those with the biggest boost had a variety of query strings across different visitor populations (e.g. appending query strings such as <code>?resize=100px:*&amp;output-quality=60</code> <a href="https://developers.cloudflare.com/images/resizing-with-workers">to optimize serving images for different screen sizes</a>). A few additional HITs for many different, but still popular, cache keys really added up.</p>
    <div>
      <h3>Revisiting old assumptions</h3>
      <a href="#revisiting-old-assumptions">
        
      </a>
    </div>
    <p>What the aggregate hit rate increase implied was that for a significant portion of customers, our old query string behavior was too defensive. We were overestimating the proportion of query string requests that were one-hit-wonders.</p><p>Given that these requests are a relatively small percentage of total traffic, we didn’t expect to see massive hit rate improvements. In fact, if the assumptions about many query string requests being one-hit-wonders were correct, we should hardly be seeing any difference at all. (One-hit-wonders are actually “one MISS wonders” in the context of hit rate.) Instead, we saw hit rate increases proportional to the affected traffic percentage. This, along with the minor I/O cost increases, signaled that the benefits of removing the behavior would outweigh the benefits of keeping it.</p><p>Using a data-driven approach, we determined our caution around caching query string requests wasn’t nearly as effective at preventing disk writes of unpopular requests compared to our newer efforts, such as transient cache. It was a good reminder for us to re-examine past assumptions from time-to-time and see if they still held.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/29s2kKkqUOMWDmlFHY5qp8/60a7d113cde89a6e197d4536bd911e7b/image4-15.png" />
            
            </figure><p>Post-experiment, we gradually rolled out the change to verify that our cost expectations were met. At this time, we’re happy to report that we’ve left the “third time’s the charm” quirk behind for the history books. Long live “cache at first sight.”</p> ]]></content:encoded>
            <category><![CDATA[Cache]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Performance]]></category>
            <guid isPermaLink="false">5cQYvM7O47Nb1C1zoxwG4Q</guid>
            <dc:creator>Edward Wang</dc:creator>
        </item>
    </channel>
</rss>