Improve caching for Rails proxied assets on Cloudflare

I recently ported the Jammed Rails app to Cloudflare, and noticed that the ActiveStorage assets were not being cached properly. I did some digging, and found this to be because Cloudflare issues BYPASS cf-cache-status headers for assets, due to Rails issuing a cookie with the assets. I couldn’t find any Rails help or documentation on this, so I thought I’d write it up.

Proxy Rails assets to origin

I have images uploaded to the Rails app using ActiveStorage, and I want to cache them on Cloudflare. The assets in Rails are setup using the proxy method, so that they are served from Cloudflare, using the Rails app as origin, instead of simply redirecting to the source file (e.g. S3). This is done by adding the following to config/application.rb:

config.active_storage.resolve_model_to_route  = :rails_storage_proxy

Now, when I visit the site, I can see that the assets are being served proxied and not redirected, but they are still not being cached by the CDN. Cloudflare is issuing a cf-cache-status: BYPASS header, which means that the asset is not being cached, and is always being served directly from the origin.

This is because Rails is issuing a set-cookie header with the assets, and Cloudflare is not caching assets with cookies. This is expected behaviour, and is documented in the Cloudflare docs as: “Cloudflare also sets BYPASS when your origin web server sends cookies in the response header.”.

Adding the cache rule

To force Cloudflare to cache these assets, we need add the cache rule.

I went to the Cloudflare dashboard, and selected the domain I wanted to add the cache rule to. I then went to the “Caching” tab, and selected “Cache Rules” from the sidebar. This is luckily available to all plan levels, and not just to my Pro account.

Once here, I then clicked “Add Cache Rule” and added the following:

Image showing an Cloudflare cache rule

The key here is to match the URL pattern, and to set the cache level for our Rails proxied assets.


Forcing the edge cache

Even with the cache rule in place, the assets were still not being cached. I found that I needed to force the edge cache to store, by setting the edge TTL:

Force the cloudflare edge to cache

Given that the assets are not changing, I set the TTL to 1 year, which is the maximum allowed, but maybe start with 1 hour or 1 day, and then increase it once you are happy that it is working.

Image network headers showing a cloudflare CDN cache hit

Rails generates a new URL for each asset and each variant, so the cache rule will match each time, and the edge cache will be forced to store the asset. The long TTL will mean that the edge cache will be used for a long time, and the origin will not be hit for the same asset.

Andy Callaghan makes Jammed - booking software for music rehearsal studios and recording studios

This post is licensed under CC BY 4.0 by the author.