<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Cloudflare on Masutaka&#39;s ChangeLog Memo</title>
    <link>https://masutaka.net/en/tags/Cloudflare/</link>
    <description>Recent content in Cloudflare on Masutaka&#39;s ChangeLog Memo</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Sun, 08 Nov 2026 13:28:57 +0900</lastBuildDate>
    <atom:link href="https://masutaka.net/en/tags/Cloudflare/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Reviewing My nginx and Cloudflare Settings Cut Cloud Run Data Transfer Costs by 96%</title>
      <link>https://masutaka.net/en/2026-11-08-1/</link>
      <pubDate>Sun, 08 Nov 2026 13:28:57 +0900</pubDate>
      <guid>https://masutaka.net/en/2026-11-08-1/</guid>
      <description>&lt;p&gt;In my previous post, &lt;a href=&#34;https://masutaka.net/en/2026-11-03-1/&#34;&gt;[2026-11-03-1]&lt;/a&gt;
, I reworked how I deploy to Cloud Run. This is a follow-up.&lt;/p&gt;
&lt;p&gt;As the title says, reviewing my nginx and Cloudflare settings cut my Cloud Run data transfer costs by 96%. Combined with the Artifact Registry savings from the previous post, my Google Cloud bill, which had been over ¥1,000 a month for about a year, came to ¥72 in October.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In my previous post, <a href="/en/2026-11-03-1/">[2026-11-03-1]</a>
, I reworked how I deploy to Cloud Run. This is a follow-up.</p>
<p>As the title says, reviewing my nginx and Cloudflare settings cut my Cloud Run data transfer costs by 96%. Combined with the Artifact Registry savings from the previous post, my Google Cloud bill, which had been over ¥1,000 a month for about a year, came to ¥72 in October.</p>
<h2 id="why-i-looked-at-the-bill">Why I Looked at the Bill</h2>
<p>Following on from that work, I checked my Google Cloud bill.</p>
<p>This blog is just nginx on Cloud Run serving static files, with Cloudflare in front as a CDN. Cloud Run data transfer costs depend on how much data Cloud Run sends out.</p>
<p>I knew the bill had been creeping up, but I didn&rsquo;t know why.</p>
<p><img alt="Google Cloud costs from April 2024 to August 2026" loading="lazy" src="/images/google-cloud-billing-2024-04-2026-08-en.png"></p>
<p>Looking at the breakdown, nearly all of it came from two items: Cloud Run Data Transfer (blue in the graph) and Artifact Registry Storage (orange).</p>
<p>Artifact Registry accounted for almost half of the bill, but I knew it would go down because I&rsquo;d set a cleanup policy in the previous post.</p>
<p>That left Data Transfer, which was ¥556 in September.</p>
<p>I took <code>httpRequest.responseSize</code> from the Cloud Run request logs (<code>run.googleapis.com/requests</code>) and totaled it by path for the last 24 hours:</p>
<table>
	<thead>
			<tr>
					<th>Path</th>
					<th style="text-align: right">Transfer</th>
					<th style="text-align: right">Share</th>
					<th style="text-align: right">Requests</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>/</code></td>
					<td style="text-align: right">0.20 GiB</td>
					<td style="text-align: right">6.1%</td>
					<td style="text-align: right">9,067</td>
			</tr>
			<tr>
					<td><code>/index.xml</code></td>
					<td style="text-align: right">3.02 GiB</td>
					<td style="text-align: right">92.5%</td>
					<td style="text-align: right">1,522</td>
			</tr>
			<tr>
					<td>Other</td>
					<td style="text-align: right">~0.04 GiB</td>
					<td style="text-align: right">1.4%</td>
					<td style="text-align: right">~3,200</td>
			</tr>
	</tbody>
</table>
<p>Over 90% of the transfer was <code>/index.xml</code>, the feed for RSS readers. It contained the full text of every post and weighed 5.4 MB, adding up to about 3 GiB a day.</p>
<h2 id="nginx-gzip-compression-was-turned-off">nginx gzip Compression Was Turned Off</h2>
<p>Embarrassingly, I hadn&rsquo;t noticed that nginx gzip compression was disabled.</p>
<p>I&rsquo;d copied my nginx config from the official nginx Docker image years ago, and <code>#gzip  on;</code> had been commented out ever since.</p>
<p>Cloudflare was compressing responses to browsers, so nothing looked wrong from the outside. The uncompressed part was between Cloud Run and Cloudflare:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Cloud Run (nginx) --(uncompressed)--&gt; Cloudflare --(gzip)--&gt; Browser
</span></span></code></pre></div><p>That uncompressed part is what gets billed as Cloud Run Data Transfer.</p>
<p>I enabled gzip compression like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">gzip</span>  <span style="color:#66d9ef">on</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">gzip_comp_level</span>  <span style="color:#ae81ff">6</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">gzip_proxied</span>  <span style="color:#e6db74">any</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">gzip_vary</span>  <span style="color:#66d9ef">on</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">gzip_types</span>  <span style="color:#e6db74">text/plain</span> <span style="color:#e6db74">text/css</span> <span style="color:#e6db74">text/xml</span> <span style="color:#e6db74">application/javascript</span> <span style="color:#e6db74">application/json</span> <span style="color:#e6db74">image/svg+xml</span>;
</span></span></code></pre></div><p>I set <code>gzip_proxied any</code> so that responses are still compressed when a proxy along the way adds a <code>Via</code> header. By default, nginx doesn&rsquo;t compress responses to requests that have a <code>Via</code> header.</p>
<p><code>/index.xml</code> went from 5.4 MB to 1.27 MB.</p>
<p>Later, for an unrelated reason, I limited <code>/index.xml</code> to the latest 100 posts. I&rsquo;d been including every post out of old habit, but decided it wasn&rsquo;t necessary in the first place. Today <code>/index.xml</code> is about 1 MB, or about 245 KB with gzip.</p>
<p>Incidentally, this reminded me of the 2001 scare over the possible shutdown of 2channel (2ch), a huge Japanese message board (<a href="https://ja.wikipedia.org/wiki/2%e3%81%a1%e3%82%83%e3%82%93%e3%81%ad%e3%82%8b%e3%81%a85%e3%81%a1%e3%82%83%e3%82%93%e3%81%ad%e3%82%8b%e3%81%ae%e6%ad%b4%e5%8f%b2" title="Wikipedia (ja) - 2ちゃんねると5ちゃんねるの歴史" target="_blank" rel="noopener">the August crisis</a>
).</p>
<p>In August 2001, growing data transfer made the site hard to keep running, and 2ch faced shutdown. Regulars on its UNIX board <a href="https://kako.5ch.io/test/read.cgi/unix/998765295/" target="_blank" rel="noopener">improved read.cgi</a>
 to reduce the transfer, and the improvements included gzip compression.</p>
<p>I never expected to still be talking about cutting data transfer with gzip 25 years later ( ´∀｀)</p>
<h2 id="caching-html-on-cloudflare-too">Caching HTML on Cloudflare Too</h2>
<p>Cloudflare&rsquo;s CDN had actually been enabled ever since I moved my DNS in <a href="/2024-04-13-1/">[2024-04-13-1]</a>
. When Cloudflare has a cached copy, it can respond without going to Cloud Run every time, which reduces data transfer.</p>
<p>However, only static files such as images were being cached. HTML and <code>/index.xml</code> were fetched from Cloud Run on every request.</p>
<p>Cloud Run, the origin, doesn&rsquo;t return <code>Cache-Control</code>, so Cloudflare&rsquo;s default behavior applied before this change. Edge TTL is how long Cloudflare keeps the cache, and Browser TTL is how long the browser keeps it. Here&rsquo;s the before and after:</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Before (Cloudflare defaults)</th>
					<th>After (Cache Rules)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Target</td>
					<td>Static files determined by file extension (images, CSS, JS, etc.) and <code>robots.txt</code></td>
					<td>Everything</td>
			</tr>
			<tr>
					<td>HTML, <code>/index.xml</code>, <code>/llms.txt</code>, sitemap</td>
					<td>Not cached</td>
					<td>Cached</td>
			</tr>
			<tr>
					<td>Edge TTL</td>
					<td>Default for each status code (120 minutes for 200)</td>
					<td>1 day</td>
			</tr>
			<tr>
					<td>Browser TTL</td>
					<td>4 hours</td>
					<td>None (bypass, so browsers don&rsquo;t keep a copy)</td>
			</tr>
	</tbody>
</table>
<p>The whole cache gets purged on every deploy. Cache Rules are managed in Terraform with <code>cloudflare_ruleset</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-hcl" data-lang="hcl"><span style="display:flex;"><span><span style="color:#66d9ef">resource</span> <span style="color:#e6db74">&#34;cloudflare_ruleset&#34; &#34;cache&#34;</span> {
</span></span><span style="display:flex;"><span>  zone_id <span style="color:#f92672">=</span> <span style="color:#66d9ef">data</span>.<span style="color:#66d9ef">cloudflare_zone</span>.<span style="color:#66d9ef">main</span>.<span style="color:#66d9ef">zone_id</span>
</span></span><span style="display:flex;"><span>  name    <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;default&#34;</span>
</span></span><span style="display:flex;"><span>  kind    <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;zone&#34;</span>
</span></span><span style="display:flex;"><span>  phase   <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;http_request_cache_settings&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  rules <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      ref         <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;cache_everything&#34;</span>
</span></span><span style="display:flex;"><span>      description <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Cache everything including HTML for 1 day. deploy.yml purges the cache on every deploy&#34;</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">      # Exclude uptime monitoring so it checks the origin rather than the cache
</span></span></span><span style="display:flex;"><span>      expression <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;not (http.user_agent contains \&#34;GoogleStackdriverMonitoring-UptimeChecks\&#34; or http.user_agent contains \&#34;UptimeRobot\&#34;)&#34;</span>
</span></span><span style="display:flex;"><span>      action     <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;set_cache_settings&#34;</span>
</span></span><span style="display:flex;"><span>      action_parameters <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>        cache <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>        edge_ttl <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>          mode    <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;override_origin&#34;</span>
</span></span><span style="display:flex;"><span>          default <span style="color:#f92672">=</span> <span style="color:#ae81ff">86400</span>
</span></span><span style="display:flex;"><span>        }<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">        # A purge can&#39;t clear browser caches, so don&#39;t let browsers keep a copy
</span></span></span><span style="display:flex;"><span>        browser_ttl <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>          mode <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;bypass&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>I set Edge TTL to 1 day. Since the cache is purged on every deploy, it could just as well be a week or a year. But if a purge fails, stale content stays up for the entire TTL. With 1 day, the worst case fixes itself by the next day.</p>
<p>The shorter TTL means Cloudflare comes back to Cloud Run to revalidate. In the actual logs, revalidation after expiry returned 304 (Not Modified), so the data transfer was negligible.</p>
<p>I bypassed Browser TTL because a purge can&rsquo;t clear browser caches. Browsers no longer keep images either, but those are served from Cloudflare, so Cloud Run&rsquo;s data transfer doesn&rsquo;t go up.</p>
<p>I excluded uptime monitoring (Cloud Monitoring uptime checks and UptimeRobot) from caching by User-Agent. What I want to monitor is whether Cloud Run is alive, not the cache.</p>
<p>For the purge, the Deploy workflow calls the Purge Everything API after deploying to Cloud Run.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Purge Cloudflare cache</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          curl -sSf -X POST &#34;https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache&#34; \
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            -H &#34;Authorization: Bearer ${CLOUDFLARE_PURGE_TOKEN}&#34; \
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            -H &#34;Content-Type: application/json&#34; \
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            --data &#39;{&#34;purge_everything&#34;:true}&#39; \
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            | jq -e .success</span>
</span></span></code></pre></div><h2 id="how-much-did-it-go-down">How Much Did It Go Down?</h2>
<p><img alt="Google Cloud costs from January to October 2026" loading="lazy" src="/images/google-cloud-billing-2026-01-10-en.png"></p>
<p>The graph says it all. From over ¥1,000 a month down to ¥65 in October (excl. tax; ¥72 with tax). This post is about the blue Data Transfer; the orange Artifact Registry Storage went down thanks to the previous post.</p>
<p>First, I compared daily data transfer using the Cloud Run request logs:</p>
<table>
	<thead>
			<tr>
					<th>Path</th>
					<th style="text-align: right">Before</th>
					<th style="text-align: right">After</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>/</code></td>
					<td style="text-align: right">0.20 GiB</td>
					<td style="text-align: right">0.065 GiB</td>
			</tr>
			<tr>
					<td><code>/index.xml</code></td>
					<td style="text-align: right">3.02 GiB</td>
					<td style="text-align: right">0.015 GiB</td>
			</tr>
			<tr>
					<td>Other</td>
					<td style="text-align: right">~0.04 GiB</td>
					<td style="text-align: right">~0.039 GiB</td>
			</tr>
			<tr>
					<td>Total</td>
					<td style="text-align: right">3.26 GiB</td>
					<td style="text-align: right">0.119 GiB</td>
			</tr>
	</tbody>
</table>
<p>That&rsquo;s about a 96% reduction. Requests to <code>/index.xml</code> dropped from 1,522 to 30, with Cloudflare now answering most of them.</p>
<p><code>/</code> still shows up because uptime monitoring is excluded from caching. Even so, thanks to gzip, its data transfer fell to a third.</p>
<p>Next, the bills for September and October:</p>
<table>
	<thead>
			<tr>
					<th>Item</th>
					<th style="text-align: right">September</th>
					<th style="text-align: right">October</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Cloud Run Data Transfer</td>
					<td style="text-align: right">¥556 (83.01 GiB)</td>
					<td style="text-align: right">¥24 (3.66 GiB)</td>
			</tr>
			<tr>
					<td>Artifact Registry Storage</td>
					<td style="text-align: right">¥442 (28.24 GiB/month)</td>
					<td style="text-align: right">¥41 (3.10 GiB/month)</td>
			</tr>
			<tr>
					<td>Cloud Storage</td>
					<td style="text-align: right">¥20</td>
					<td style="text-align: right">¥0</td>
			</tr>
			<tr>
					<td>Subtotal</td>
					<td style="text-align: right">¥1,018</td>
					<td style="text-align: right">¥65</td>
			</tr>
			<tr>
					<td>Tax</td>
					<td style="text-align: right">¥102</td>
					<td style="text-align: right">¥7</td>
			</tr>
			<tr>
					<td>Total</td>
					<td style="text-align: right">¥1,120</td>
					<td style="text-align: right">¥72</td>
			</tr>
	</tbody>
</table>
<p>October&rsquo;s figures also include a small effect from limiting the feed to 100 posts, but the log comparison above was made before that change.</p>
<p>Of the ¥953 reduction, the ¥532 in Data Transfer came from the changes in this post. Of the rest, ¥401 was Artifact Registry, thanks to the cleanup policy from my previous post, and the remaining ¥20 was Cloud Storage.</p>
<p>By the way, Cloudflare&rsquo;s CDN is on the Free plan, so that stays at ¥0.</p>
<h2 id="conclusion">Conclusion</h2>
<p>nginx gzip compression I hadn&rsquo;t noticed was off, and a Cloudflare cache that only covered static files. Fixing these two things cut my Cloud Run data transfer costs by 96%. Together with the Artifact Registry cleanup from the previous post, my Google Cloud bill, which had been over ¥1,000 a month for about a year, is now ¥72.</p>
<p>Both turned out to be simple once I understood them. If you&rsquo;re using Cloud Run, try totaling your request logs by path once. You might find something unexpected.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://nginx.org/en/docs/http/ngx_http_gzip_module.html" target="_blank" rel="noopener">Module ngx_http_gzip_module</a>
</li>
<li><a href="https://developers.cloudflare.com/cache/concepts/default-cache-behavior/" target="_blank" rel="noopener">Default cache behavior | Cloudflare</a>
</li>
<li><a href="https://developers.cloudflare.com/cache/how-to/cache-rules/" target="_blank" rel="noopener">Cache Rules | Cloudflare</a>
</li>
<li><a href="https://developers.cloudflare.com/cache/how-to/purge-cache/purge-everything/" target="_blank" rel="noopener">Purge everything | Cloudflare</a>
</li>
</ul>
]]></content:encoded>
    </item>
  </channel>
</rss>
