Does Ruby on Rails Work With Cloudflare R2?

Fully CompatibleLast verified: 2026-02-26

Ruby on Rails works seamlessly with Cloudflare R2 using the aws-sdk-s3 gem, treating R2 as an S3-compatible storage backend with zero egress fees.

Quick Facts

Compatibility
full
Setup Difficulty
Easy
Official Integration
No — community maintained
Confidence
high
Minimum Versions
Ruby on Rails: 6.0

How Ruby on Rails Works With Cloudflare R2

Rails integrates with Cloudflare R2 through ActiveStorage, which abstracts storage backends behind a consistent API. Since R2 is S3-compatible, you configure it using the aws-sdk-s3 gem by pointing credentials and endpoints to your R2 account. The developer experience is identical to using AWS S3—you attach files to models, generate signed URLs, and handle uploads the same way. Under the hood, Rails makes HTTP requests to R2's S3 API endpoint instead of Amazon's, but the gem handles all the authentication and protocol details. The major architectural benefit is cost: R2 charges zero egress fees, making it ideal for applications serving files to users globally or requiring frequent downloads. Setup takes minutes and requires only environment variables for credentials and bucket configuration.

Best Use Cases

Media-heavy SaaS applications where egress costs would be prohibitive with AWS S3
User-uploaded content (avatars, documents) requiring global distribution without transfer costs
Backup and archive storage for Rails applications with high read volume
Static asset CDN integration leveraging Cloudflare's network without S3 bandwidth charges

Rails ActiveStorage with R2

bash
bundle add aws-sdk-s3
ruby
# config/storage.yml
cloudflare_r2:
  service: S3
  access_key_id: <%= ENV['R2_ACCESS_KEY_ID'] %>
  secret_access_key: <%= ENV['R2_SECRET_ACCESS_KEY'] %>
  region: auto
  bucket: <%= ENV['R2_BUCKET'] %>
  endpoint: <%= ENV['R2_ENDPOINT'] %>
  force_path_style: true

# config/environments/production.rb
config.active_storage.service = :cloudflare_r2

# In your Rails model
class User < ApplicationRecord
  has_one_attached :avatar
end

# Usage
user = User.find(1)
user.avatar.attach(io: File.open('photo.jpg'), filename: 'photo.jpg')
user.avatar.blob.service_url(expires_in: 1.hour)

Known Issues & Gotchas

warning

R2 API has minor compatibility gaps with S3 (e.g., some ACL operations, conditional request headers behave differently)

Fix: Stick to standard S3 operations in aws-sdk-s3. Avoid advanced ACL configurations and test edge cases in staging. Most Rails use cases won't hit these limits.

warning

Signed URL expiration defaults differ; R2 URLs expire after 1 hour by default while S3 allows longer durations

Fix: Explicitly set expires_in parameter when generating signed URLs: `object.presigned_url(:get_object, expires_in: 3600)`

info

Regional endpoint configuration is required; R2 doesn't auto-detect like S3 does

Fix: Always set `endpoint` and `region` in storage.yml or credentials. Use format: `https://<account-id>.r2.cloudflarestorage.com`

Alternatives

  • AWS S3 + CloudFront (more mature but higher egress costs without CloudFront optimization)
  • Backblaze B2 with Rails (cheaper storage, requires custom integration, less native support)
  • Google Cloud Storage with Rails (strong integration, pay-per-use model, egress fees apply)

Resources

Related Compatibility Guides

Explore more compatibility guides