Articles

Rails recipes, tutorials, and development logs.

Filtered by tag: cloudflare
Clear filter
September 02, 2026

The Production-Ready Rails Setup: Hatchbox, Cloudflare, and Edge Optimization

Getting a Rails app running on a clean VPS via Hatchbox takes about ten minutes. But getting that app to run blazingly fast, handle traffic surges without spiking CPU, and eliminate common SSL and caching traps requires an intentional partnership between Hatchbox and Cloudflare. Too often, developers slap Cloudflare in front of Rails in default "Orange Cloud" (proxy) mode and run into edge redirection loops, cache poisoning on session cookies, or bloated Puma workers serving redirect loops for media files. Here is the blueprint for pairing Hatchbox with Cloudflare, along with the exact edge rules, proxy configurations, and caching tweaks needed to extract maximum performance from your server. 1. The SSL Handshake & Redirect FoundationHatchbox provisions Caddy out of the box to manage local SSL via Let’s Encrypt. When you place Cloudflare’s proxy in front of Caddy, you introduce two layers of TLS. • Set SSL to Full (Strict): In Cloudflare under SSL/TLS -> Overview, choose Full (Strict). Never use "Flexible." Flexible forces Cloudflare to speak to Caddy over unencrypted HTTP (port 80). Caddy will issue a 301 back to HTTPS, causing an infinite redirect loop (ERR_TOO_MANY_REDIRECTS). Strict ensures end-to-end encrypted validation against Caddy's certificates. • Apex to WWW (or WWW to Apex) at the Edge: Never make Puma or Rack handle domain rewrites. Offload it entirely to Cloudflare using Redirect Rules (under Rules $\rightarrow$ Redirect Rules): • The Prerequisite DNS Record: Cloudflare's edge rules only evaluate if traffic actually reaches Cloudflare. Ensure you have a proxied (Orange Cloud) record for www in your DNS settings—either a CNAME pointing www to @ (your apex domain), or an A record pointing to your server's IP. If this record is missing or gray-clouded, browsers will throw NXDOMAIN before the rule ever executes. • Rule: If Hostname equals "[www.yourdomain.com](https://www.yourdomain.com)" • Action: Dynamic 301 redirect to concat("[https://yourdomain.com](https://yourdomain.com)", http.request.uri.path) • This resolves the canonical redirect at the edge in single-digit milliseconds, saving your VPS from ever handling a useless handshake. 2. Taming Active Storage with Proxy Mode & Edge Cache RulesBy default, Active Storage uses redirect mode: a client requests an asset, your Rails process boots, generates a short-lived signed S3 or bucket URL, and issues a 302 redirect. If you have 50 avatars or attachments on a page, your Puma threads spend all their bandwidth negotiating signed redirects. Enable Proxy Mode in RailsBy default, Active Storage runs in redirect mode: when a client requests an attachment, Puma generates a signed, expiring 302 redirect URL to your storage provider (such as S3 or Backblaze B2). If a page renders 30 images, your Rails server spends compute generating 30 unique signed redirects on every single page load. Instead of hunting down every view to manually swap in rails_storage_proxy_path, tell Rails to route all Active Storage attachments through proxy mode by default. Add this single line to config/environments/production.rb: # config/environments/production.rb config.active_storage.resolve_model_to_route = :rails_storage_proxyNow, standard view calls automatically generate proxy endpoints: <%# Automatically resolves to /rails/active_storage/blobs/proxy/... %> <%= image_tag @article.featured_image %>When proxy mode is active, Rails streams the file directly through your domain and emits long-term, immutable caching directives on binary responses: cache-control: max-age=3155695200, public, immutableThis hands Cloudflare the exact headers needed to cache assets at the edge for months without any manual TTL overrides. Cache Active Storage at the EdgeBecause proxy paths (/rails/active_storage/blobs/proxy/* and /rails/active_storage/representations/proxy/*) serve immutable file contents, you want Cloudflare to cache them permanently so Puma never sees a repeated request. In Cloudflare, go to Caching -> Cache Rules and create an Active Storage Edge Cache rule: • Matching Expression: (http.request.method in {"GET" "HEAD"} and starts_with(http.request.uri.path, "/rails/active_storage/"))• Settings: • Cache Eligibility: Eligible for cache • Edge TTL: Use cache-control header if present, bypass cache if not (or "cache request with Cloudflare's default TTL if not") • Browser TTL: Respect origin TTL Once primed, your VPS CPU stays near 0% when pages with dozens of user-uploaded files load. Why This Works• Zero Compute on Repeat Hits: The first request pulls through Puma, Caddy, and Cloudflare to prime the cache. Every subsequent visit from anywhere in the world serves a sub-20ms edge HIT. • Permanent Browser Caching: Because Browser TTL respects origin headers, client browsers store the file locally across visits (immutable), eliminating unnecessary re-downloads. • No Direct Upload Conflicts: POST requests to direct upload endpoints bypass the rule cleanly. Verifying Edge Performance in DevToolsTo confirm Cloudflare and Hatchbox are communicating correctly: 1. Open Chrome DevTools (Cmd + Option + I or Ctrl + Shift + I) and click the Network tab. 2. Uncheck Disable cache so you can observe real-world browser caching. 3. Reload your page, click on any Active Storage image request, and open the Headers panel: • cf-cache-status: HIT: Cloudflare served the asset from its edge without touching your VPS. • cache-control: max-age=..., public, immutable: Cloudflare preserved your Rails cache headers. • via: 1.1 Caddy: Shows that Hatchbox’s native Caddy reverse proxy handled the upstream origin response when the cache was originally primed. • age: [seconds]: Shows how long the asset has been cached at the edge. 3. Asset Pipeline Edge Offloading (/assets/*)Modern Rails (whether using Propshaft, Sprockets, Vite, or Tailwind CLI) produces fingerprint-digested assets (e.g., application-d893f412.css). Caddy serves these statically from /public/assets, but Cloudflare should absorb 100% of this traffic globally: • Create a Cache Rule for Assets: • Expression: http.request.uri.path starts_with "/assets/" • Cache Eligibility: Eligible for cache • Edge TTL: Override origin -> 1 year • Browser Cache TTL: Override origin -> 1 year Because Rails digests asset filenames whenever file contents change, aggressive 1-year caching will never show stale CSS/JS after a Hatchbox deployment. 4. Protecting Dynamic Rails Routes from Cache LeaksCloudflare's default cache level respects origin Cache-Control headers, but human error in custom controllers can accidentally broadcast private sessions if you enable global caching rules. Ensure you explicitly safeguard authenticated paths by creating a Bypass Rule: • Expression: (http.request.uri.path starts_with "/admin") or (http.cookie contains "_your_app_session")• Action: Bypass cache This ensures that any user carrying a logged-in Rails session cookie always hits the origin Puma worker directly, eliminating edge-caching bugs for logged-in sessions. 5. Caddy & Rails Real IP RestorationBecause every request hits Cloudflare before reaching Hatchbox, your Rails production logs and request.remote_ip calls will show Cloudflare IP addresses by default unless restored. In your Rails configuration: # config/environments/production.rb config.action_dispatch.trusted_proxies = ActionDispatch::RemoteIp::TRUSTED_PROXIES + [ # Trust local reverse proxy (Caddy) IPAddr.new("127.0.0.1"), IPAddr.new("::1") ]Caddy forwards the X-Forwarded-For header automatically, allowing Rails to accurately track user rate-limits, audit logs, and security geolocation. The ResultVector Unoptimized Default Hatchbox + Cloudflare Edge Stack Asset Delivery Puma/Caddy disk I/O on every cold visit Sub-20ms edge HIT globally Active Storage Puma handles 302 redirects per file load Cached at CDN; 0 Rails worker overhead Domain Redirects Full TCP/TLS trip to VPS server Edge 301 resolved before origin ping Compute Cost High memory/Puma thread exhaustion Small $6–$12 VPS comfortably scales By moving static assets, media caching, and redirect logic entirely to Cloudflare's edge, Hatchbox only has to do what Rails does best: render dynamic business logic and process background jobs.