CrackedRuby CrackedRuby

Overview

Content Delivery Networks (CDNs) consist of distributed servers that cache and deliver static content from locations geographically closer to end users. CDNs reduce latency by serving assets from edge servers near the request origin rather than requiring every request to travel to the origin server. This architecture significantly improves page load times, reduces bandwidth costs, and increases application availability.

A CDN operates by maintaining copies of static content—images, stylesheets, JavaScript files, videos—across multiple Points of Presence (PoPs) worldwide. When a user requests a resource, the CDN routes the request to the nearest edge server. If the edge server has a cached copy, it serves the content directly. If not, the edge server retrieves the content from the origin server, caches it for future requests, and delivers it to the user.

Modern web applications rely heavily on CDNs to deliver assets efficiently. A typical web page loads dozens of resources including images, stylesheets, JavaScript bundles, and fonts. Without a CDN, each resource request travels to the origin server, creating latency proportional to geographic distance. With a CDN, most requests terminate at nearby edge servers, reducing latency from hundreds of milliseconds to tens of milliseconds.

# Rails application serving assets directly (without CDN)
# config/environments/production.rb
config.asset_host = 'https://example.com'
# Assets served from: https://example.com/assets/application-abc123.css

# Rails application using CDN
config.asset_host = 'https://d3fgh82klm.cloudfront.net'
# Assets served from: https://d3fgh82klm.cloudfront.net/assets/application-abc123.css

CDNs also provide additional capabilities beyond simple caching: DDoS protection, SSL/TLS termination, image optimization, edge computing, and real-time analytics. These features transform CDNs from simple caching layers into comprehensive content delivery and security platforms.

Key Principles

CDNs operate on several foundational principles that determine their effectiveness and behavior. Understanding these principles allows developers to configure CDNs appropriately and troubleshoot performance issues.

Geographic Distribution forms the core of CDN architecture. CDN providers maintain PoPs in major cities and internet exchange points worldwide. The number and location of PoPs directly affects CDN performance—more PoPs in diverse locations provide better geographic coverage. When a user requests content, DNS resolution or anycast routing directs the request to the optimal edge server based on factors including geographic proximity, server load, and network conditions.

Cache Hit Ratio measures CDN effectiveness. A cache hit occurs when an edge server fulfills a request from its cache without contacting the origin server. A cache miss requires the edge server to fetch content from the origin. High cache hit ratios (90-95%+) indicate effective caching, while low ratios suggest configuration problems or uncacheable content. Cache hit ratio directly impacts both performance and bandwidth costs—cache hits are fast and free, while cache misses add latency and origin bandwidth consumption.

Time To Live (TTL) controls how long edge servers cache content before revalidating with the origin. CDNs determine TTL from multiple sources: explicit cache headers (Cache-Control, Expires), CDN configuration rules, and default settings. Longer TTLs improve cache hit ratios but increase the risk of serving stale content. Shorter TTLs ensure freshness but increase cache misses and origin load. Different content types require different TTL strategies—static assets with fingerprinted names can use maximum TTLs (1 year), while dynamic content requires shorter TTLs or no caching.

# Rails asset pipeline generates fingerprinted filenames
# Original: application.css
# Compiled: application-8f3d9e2b1c4a5f6e7d8c9b0a1e2f3d4c.css

# Long TTL for fingerprinted assets (1 year)
Cache-Control: public, max-age=31536000, immutable

# Short TTL for HTML pages (5 minutes)
Cache-Control: public, max-age=300

Cache Invalidation removes cached content before its TTL expires. CDN providers support several invalidation mechanisms including purging specific files, purging by tag or pattern, and versioning. Invalidation becomes necessary when content changes—deploying code updates, correcting errors, or updating dynamic content. However, invalidation has limitations: it may not propagate instantly to all edge servers, and excessive invalidation undermines caching benefits.

Origin Shield adds an intermediate caching layer between edge servers and the origin. When enabled, edge cache misses contact the shield cache instead of the origin directly. The shield cache collapses concurrent requests for the same resource, reducing origin load. This architecture benefits origins with limited capacity or expensive computation but adds a cache layer that might increase latency for cache misses.

Edge Computing executes code at CDN edge servers rather than the origin. Edge functions transform requests and responses, enabling dynamic behavior without origin involvement. Examples include resizing images on demand, redirecting users based on location, A/B testing, and authentication. Edge computing reduces origin load and latency for dynamic operations.

Ruby Implementation

Ruby web applications integrate with CDNs primarily through the Rails asset pipeline and cloud storage services. The asset pipeline compiles and fingerprints assets, while CDN integration configures where browsers load these assets.

Rails provides built-in CDN support through the asset_host configuration. Setting asset_host changes asset URLs in views to point to the CDN hostname instead of the application domain. The asset pipeline already generates fingerprinted asset names, making them ideal for long-term CDN caching.

# config/environments/production.rb
Rails.application.configure do
  # Configure CDN hostname for assets
  config.asset_host = 'https://cdn.example.com'
  
  # Enable asset compilation and digesting
  config.assets.compile = false
  config.assets.digest = true
  
  # Configure public file server for non-asset static files
  config.public_file_server.enabled = true
  config.public_file_server.headers = {
    'Cache-Control' => 'public, max-age=31536000'
  }
end

The asset_host configuration accepts strings, procs, or objects responding to call. Using a proc enables domain sharding—spreading assets across multiple subdomains to bypass browser connection limits:

# Domain sharding across 4 subdomains
config.asset_host = Proc.new { |source|
  "https://cdn#{source.hash % 4}.example.com"
}

# Generated URLs:
# <img src="https://cdn2.example.com/assets/logo-abc123.png">
# <link href="https://cdn0.example.com/assets/application-def456.css">

For applications storing assets in cloud storage like S3, the asset_sync gem synchronizes compiled assets after deployment. This gem uploads assets to S3 during the deployment process, where CloudFront or another CDN serves them:

# Gemfile
gem 'asset_sync'

# config/initializers/asset_sync.rb
AssetSync.configure do |config|
  config.fog_provider = 'AWS'
  config.aws_access_key_id = ENV['AWS_ACCESS_KEY_ID']
  config.aws_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
  config.fog_directory = ENV['AWS_BUCKET']
  config.fog_region = 'us-east-1'
  
  # Only sync in production
  config.enabled = Rails.env.production?
  
  # Delete old assets not in manifest
  config.existing_remote_files = 'delete'
  
  # Gzip assets before uploading
  config.gzip_compression = true
end

# After deployment
# rake assets:precompile
# Assets automatically sync to S3

Direct CDN API integration uses provider-specific Ruby SDKs. The AWS SDK enables CloudFront distribution management, cache invalidation, and signed URL generation:

require 'aws-sdk-cloudfront'

class CdnManager
  def initialize
    @client = Aws::CloudFront::Client.new(
      region: 'us-east-1',
      credentials: Aws::Credentials.new(
        ENV['AWS_ACCESS_KEY_ID'],
        ENV['AWS_SECRET_ACCESS_KEY']
      )
    )
    @distribution_id = ENV['CLOUDFRONT_DISTRIBUTION_ID']
  end
  
  def invalidate_paths(paths)
    @client.create_invalidation({
      distribution_id: @distribution_id,
      invalidation_batch: {
        paths: {
          quantity: paths.size,
          items: paths
        },
        caller_reference: Time.now.to_i.to_s
      }
    })
  end
  
  def generate_signed_url(path, expires_in: 3600)
    signer = Aws::CloudFront::UrlSigner.new(
      key_pair_id: ENV['CLOUDFRONT_KEY_PAIR_ID'],
      private_key: ENV['CLOUDFRONT_PRIVATE_KEY']
    )
    
    signer.signed_url(
      "https://#{ENV['CLOUDFRONT_DOMAIN']}/#{path}",
      expires: Time.now + expires_in
    )
  end
end

# Usage
cdn = CdnManager.new

# Invalidate specific files after deployment
cdn.invalidate_paths(['/assets/*', '/index.html'])

# Generate temporary signed URL for private content
url = cdn.generate_signed_url('private/video.mp4', expires_in: 1.hour)
# => "https://d111111abcdef8.cloudfront.net/private/video.mp4?Expires=...&Signature=...&Key-Pair-Id=..."

The Cloudflare gem provides similar functionality for Cloudflare CDN:

require 'cloudflare'

class CloudflareManager
  def initialize
    @client = Cloudflare.connect(
      key: ENV['CLOUDFLARE_API_KEY'],
      email: ENV['CLOUDFLARE_EMAIL']
    )
    @zone = @client.zones.find_by_name(ENV['DOMAIN'])
  end
  
  def purge_cache(files: nil, tags: nil)
    if files
      @zone.purge_cache(files: files)
    elsif tags
      @zone.purge_cache(tags: tags)
    else
      @zone.purge_cache(purge_everything: true)
    end
  end
  
  def update_cache_settings(level:)
    @zone.settings.update(
      cache_level: level # 'aggressive', 'basic', 'simplified'
    )
  end
end

# Usage
cf = CloudflareManager.new

# Purge specific files
cf.purge_cache(files: [
  'https://example.com/assets/application.css',
  'https://example.com/assets/application.js'
])

# Purge by cache tags
cf.purge_cache(tags: ['v2.0', 'homepage'])

# Purge entire cache
cf.purge_cache

Practical Examples

CloudFront with S3 Origin for Rails Assets demonstrates the most common CDN setup for Rails applications. This configuration stores precompiled assets in S3 and serves them through CloudFront, combining reliable storage with global distribution.

First, create an S3 bucket and configure it for website hosting:

# lib/tasks/cdn_setup.rake
namespace :cdn do
  desc "Setup S3 bucket for CDN assets"
  task setup_s3: :environment do
    require 'aws-sdk-s3'
    
    s3 = Aws::S3::Client.new(region: 'us-east-1')
    bucket = ENV['ASSETS_BUCKET']
    
    # Create bucket
    s3.create_bucket(bucket: bucket)
    
    # Configure CORS for cross-origin requests
    s3.put_bucket_cors({
      bucket: bucket,
      cors_configuration: {
        cors_rules: [{
          allowed_headers: ['*'],
          allowed_methods: ['GET', 'HEAD'],
          allowed_origins: ['*'],
          max_age_seconds: 3600
        }]
      }
    })
    
    # Set bucket policy for public read access
    s3.put_bucket_policy({
      bucket: bucket,
      policy: {
        Version: '2012-10-17',
        Statement: [{
          Sid: 'PublicReadGetObject',
          Effect: 'Allow',
          Principal: '*',
          Action: 's3:GetObject',
          Resource: "arn:aws:s3:::#{bucket}/*"
        }]
      }.to_json
    })
  end
end

Configure Rails to use the CDN and sync assets after precompilation:

# config/environments/production.rb
Rails.application.configure do
  config.asset_host = "https://#{ENV['CLOUDFRONT_DOMAIN']}"
  config.action_controller.asset_host = "https://#{ENV['CLOUDFRONT_DOMAIN']}"
end

# config/initializers/asset_sync.rb
AssetSync.configure do |config|
  config.fog_provider = 'AWS'
  config.fog_directory = ENV['ASSETS_BUCKET']
  config.fog_region = 'us-east-1'
  config.aws_access_key_id = ENV['AWS_ACCESS_KEY_ID']
  config.aws_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
  
  config.gzip_compression = true
  config.manifest = true
  config.existing_remote_files = 'delete'
  
  # Set far-future cache headers
  config.cache_asset_regexps = [/.*/]
  config.default_cache_control = 'public, max-age=31536000, immutable'
end

Dynamic Content Caching with Edge Side Includes shows how to cache page fragments while keeping user-specific content dynamic. This approach caches the page shell at the CDN while fetching personalized content from the origin.

# app/controllers/concerns/cdn_caching.rb
module CdnCaching
  extend ActiveSupport::Concern
  
  def set_cdn_cache(duration, tags: [])
    response.headers['Cache-Control'] = "public, max-age=#{duration}, s-maxage=#{duration}"
    response.headers['Surrogate-Key'] = tags.join(' ') if tags.any?
  end
  
  def set_cdn_vary(*headers)
    response.headers['Vary'] = headers.join(', ')
  end
  
  def bypass_cdn_cache
    response.headers['Cache-Control'] = 'private, no-cache, no-store'
  end
end

# app/controllers/products_controller.rb
class ProductsController < ApplicationController
  include CdnCaching
  
  def show
    @product = Product.find(params[:id])
    
    # Cache product page for 1 hour, tag for invalidation
    set_cdn_cache(1.hour, tags: [
      "product-#{@product.id}",
      "category-#{@product.category_id}"
    ])
    
    # Vary cache by Accept-Encoding for compression
    set_cdn_vary('Accept-Encoding')
  end
  
  def user_specific
    @recommendations = current_user.recommended_products
    
    # Don't cache user-specific pages
    bypass_cdn_cache
  end
end

# app/views/products/show.html.erb
<div class="product-page">
  <!-- Cached by CDN -->
  <h1><%= @product.name %></h1>
  <div class="description"><%= @product.description %></div>
  
  <!-- User-specific content fetched via AJAX -->
  <div id="user-actions" data-product-id="<%= @product.id %>">
    Loading...
  </div>
</div>

<script>
  // Fetch user-specific content after page load
  fetch(`/api/products/${productId}/user-actions`, {
    credentials: 'include'
  })
  .then(response => response.json())
  .then(data => {
    document.getElementById('user-actions').innerHTML = data.html;
  });
</script>

Cache Invalidation Strategy implements a comprehensive approach to keeping CDN content fresh after deployments and content updates:

# app/services/cdn_invalidation_service.rb
class CdnInvalidationService
  def initialize(provider: :cloudfront)
    @provider = provider
    @client = build_client
  end
  
  def invalidate_deployment
    # Invalidate all assets and key pages after deployment
    paths = [
      '/assets/*',
      '/packs/*',
      '/index.html',
      '/sitemap.xml'
    ]
    
    invalidate_paths(paths)
  end
  
  def invalidate_product(product)
    # Invalidate product-specific pages
    paths = [
      "/products/#{product.id}",
      "/products/#{product.slug}",
      "/api/products/#{product.id}.json"
    ]
    
    invalidate_paths(paths)
    purge_tags(["product-#{product.id}"])
  end
  
  def invalidate_category(category)
    # Invalidate category and related pages
    paths = [
      "/categories/#{category.id}",
      "/categories/#{category.slug}"
    ]
    
    invalidate_paths(paths)
    purge_tags(["category-#{category.id}"])
  end
  
  private
  
  def build_client
    case @provider
    when :cloudfront
      require 'aws-sdk-cloudfront'
      Aws::CloudFront::Client.new
    when :cloudflare
      require 'cloudflare'
      Cloudflare.connect(
        key: ENV['CLOUDFLARE_API_KEY'],
        email: ENV['CLOUDFLARE_EMAIL']
      )
    end
  end
  
  def invalidate_paths(paths)
    case @provider
    when :cloudfront
      @client.create_invalidation({
        distribution_id: ENV['CLOUDFRONT_DISTRIBUTION_ID'],
        invalidation_batch: {
          paths: { quantity: paths.size, items: paths },
          caller_reference: Time.now.to_i.to_s
        }
      })
    when :cloudflare
      zone = @client.zones.find_by_name(ENV['DOMAIN'])
      full_urls = paths.map { |p| "https://#{ENV['DOMAIN']}#{p}" }
      zone.purge_cache(files: full_urls)
    end
  end
  
  def purge_tags(tags)
    return unless @provider == :cloudflare
    
    zone = @client.zones.find_by_name(ENV['DOMAIN'])
    zone.purge_cache(tags: tags)
  end
end

# Use in callbacks
class Product < ApplicationRecord
  after_save :invalidate_cdn_cache
  
  private
  
  def invalidate_cdn_cache
    return unless Rails.env.production?
    
    CdnInvalidationService.new.invalidate_product(self)
  end
end

Image Optimization with CDN Transformations leverages CDN image processing to serve appropriately sized images without storing multiple versions:

# app/helpers/cdn_image_helper.rb
module CdnImageHelper
  def cdn_image_url(path, transformations = {})
    base_url = "https://#{ENV['CLOUDFLARE_DOMAIN']}"
    
    if transformations.any?
      params = build_transformation_params(transformations)
      "#{base_url}/cdn-cgi/image/#{params}/#{path}"
    else
      "#{base_url}/#{path}"
    end
  end
  
  private
  
  def build_transformation_params(transformations)
    params = []
    params << "width=#{transformations[:width]}" if transformations[:width]
    params << "height=#{transformations[:height]}" if transformations[:height]
    params << "quality=#{transformations[:quality] || 85}"
    params << "format=auto" # Serve WebP to supporting browsers
    params.join(',')
  end
end

# Usage in views
<%= image_tag cdn_image_url('products/laptop.jpg', width: 800, quality: 90) %>
# => <img src="https://cdn.example.com/cdn-cgi/image/width=800,quality=90,format=auto/products/laptop.jpg">

# Responsive images with srcset
<img src="<%= cdn_image_url('hero.jpg', width: 400) %>"
     srcset="<%= cdn_image_url('hero.jpg', width: 400) %> 400w,
             <%= cdn_image_url('hero.jpg', width: 800) %> 800w,
             <%= cdn_image_url('hero.jpg', width: 1200) %> 1200w"
     sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px">

Performance Considerations

CDN performance depends on configuration choices, content characteristics, and usage patterns. Optimizing these factors maximizes cache hit ratios and minimizes latency.

Cache Hit Ratio Optimization begins with appropriate TTL configuration. Static assets with versioned filenames—Rails asset pipeline output—should use maximum TTLs (1 year) since new content uses different URLs. HTML pages require shorter TTLs to ensure freshness but still benefit from CDN caching. API responses vary: slowly changing data like product catalogs can use moderate TTLs (5-15 minutes), while user-specific data requires bypass or very short TTLs.

# app/controllers/concerns/cache_control.rb
module CacheControl
  CACHE_DURATIONS = {
    static_asset: 1.year,
    html_page: 5.minutes,
    api_public: 15.minutes,
    api_user: 0,
    sitemap: 1.day,
    feed: 1.hour
  }
  
  def set_cache_headers(type, revalidate: false)
    duration = CACHE_DURATIONS[type]
    
    if duration.zero?
      response.headers['Cache-Control'] = 'private, no-cache'
    else
      directives = ['public', "max-age=#{duration.to_i}"]
      directives << 'must-revalidate' if revalidate
      response.headers['Cache-Control'] = directives.join(', ')
    end
  end
end

Compression significantly reduces bandwidth and improves load times. CDNs typically handle compression automatically, but origin servers must send appropriate headers. Brotli compression provides better compression than gzip for text content and is supported by modern CDNs.

# config/application.rb
module MyApp
  class Application < Rails::Application
    # Enable Rack::Deflater for compression
    config.middleware.use Rack::Deflater
    
    # Configure compression for specific content types
    config.middleware.insert_before Rack::Deflater, Rack::Brotli
  end
end

# Custom middleware for compression control
class CompressionControl
  COMPRESSIBLE_TYPES = %w[
    text/html
    text/css
    text/plain
    text/javascript
    application/javascript
    application/json
    application/xml
    image/svg+xml
  ]
  
  def initialize(app)
    @app = app
  end
  
  def call(env)
    status, headers, body = @app.call(env)
    
    content_type = headers['Content-Type']&.split(';')&.first
    
    if COMPRESSIBLE_TYPES.include?(content_type)
      headers['Vary'] = [headers['Vary'], 'Accept-Encoding'].compact.join(', ')
    end
    
    [status, headers, body]
  end
end

Connection Optimization through HTTP/2 and HTTP/3 improves performance by multiplexing requests over a single connection. Modern CDNs support these protocols automatically, but origin servers should also support them. HTTP/2 eliminates the need for domain sharding, as it removes the per-domain connection limit.

Prefetching and Preconnecting hints browsers to establish connections early, reducing latency for CDN requests:

<!-- app/views/layouts/application.html.erb -->
<head>
  <!-- Preconnect to CDN domain -->
  <link rel="preconnect" href="https://<%= ENV['CDN_DOMAIN'] %>">
  <link rel="dns-prefetch" href="https://<%= ENV['CDN_DOMAIN'] %>">
  
  <!-- Preload critical assets -->
  <link rel="preload" as="style" href="<%= asset_path('application.css') %>">
  <link rel="preload" as="script" href="<%= asset_path('application.js') %>">
  <link rel="preload" as="font" type="font/woff2" 
        href="<%= asset_path('fonts/primary.woff2') %>" crossorigin>
</head>

Monitoring Cache Performance tracks CDN effectiveness through metrics including cache hit ratio, origin request volume, bandwidth savings, and P95 latency. Most CDN providers offer analytics dashboards, but applications can also track metrics:

# app/services/cdn_metrics_service.rb
class CdnMetricsService
  def initialize
    @cloudwatch = Aws::CloudWatch::Client.new(region: 'us-east-1')
    @distribution_id = ENV['CLOUDFRONT_DISTRIBUTION_ID']
  end
  
  def get_cache_metrics(start_time:, end_time:)
    metrics = {}
    
    # Requests metric
    metrics[:requests] = get_metric_statistics(
      metric_name: 'Requests',
      start_time: start_time,
      end_time: end_time,
      statistic: 'Sum'
    )
    
    # Bytes downloaded
    metrics[:bytes_downloaded] = get_metric_statistics(
      metric_name: 'BytesDownloaded',
      start_time: start_time,
      end_time: end_time,
      statistic: 'Sum'
    )
    
    # Cache hit rate (calculated from CacheHitRate metric)
    metrics[:cache_hit_rate] = get_metric_statistics(
      metric_name: 'CacheHitRate',
      start_time: start_time,
      end_time: end_time,
      statistic: 'Average'
    )
    
    metrics
  end
  
  private
  
  def get_metric_statistics(metric_name:, start_time:, end_time:, statistic:)
    @cloudwatch.get_metric_statistics({
      namespace: 'AWS/CloudFront',
      metric_name: metric_name,
      dimensions: [{
        name: 'DistributionId',
        value: @distribution_id
      }],
      start_time: start_time,
      end_time: end_time,
      period: 3600,
      statistics: [statistic]
    }).datapoints
  end
end

Security Implications

CDNs introduce security considerations spanning content protection, origin isolation, and access control. Understanding these aspects ensures secure CDN implementation.

Origin Protection prevents direct access to origin servers, forcing all traffic through the CDN. Without origin protection, attackers can bypass CDN security features and overwhelm the origin with requests. CloudFront supports origin access identity for S3 origins and custom headers for HTTP origins:

# app/middleware/cloudfront_verification.rb
class CloudfrontVerification
  def initialize(app)
    @app = app
    @shared_secret = ENV['CLOUDFRONT_SHARED_SECRET']
  end
  
  def call(env)
    request = Rack::Request.new(env)
    
    # Verify request came from CloudFront
    unless valid_cloudfront_request?(request)
      return [403, {'Content-Type' => 'text/plain'}, ['Forbidden']]
    end
    
    @app.call(env)
  end
  
  private
  
  def valid_cloudfront_request?(request)
    # Check custom header set in CloudFront origin settings
    request.get_header('HTTP_X_CUSTOM_SECRET') == @shared_secret
  end
end

# config/application.rb
config.middleware.insert_before ActionDispatch::Static, CloudfrontVerification

HTTPS Enforcement ensures encrypted communication between users and edge servers. All modern CDNs support free SSL certificates through Let's Encrypt or provide custom certificate options. Rails applications should enforce HTTPS to prevent mixed content warnings:

# config/environments/production.rb
config.force_ssl = true
config.ssl_options = {
  redirect: {
    exclude: ->(request) {
      # Allow health checks without SSL redirect
      request.path == '/health'
    }
  },
  hsts: {
    expires: 1.year,
    subdomains: true,
    preload: true
  }
}

Signed URLs restrict access to private content by generating time-limited URLs with cryptographic signatures. This approach works for protected downloads, premium content, and user-specific resources:

# app/services/signed_url_generator.rb
class SignedUrlGenerator
  def initialize
    @signer = Aws::CloudFront::UrlSigner.new(
      key_pair_id: ENV['CLOUDFRONT_KEY_PAIR_ID'],
      private_key: ENV['CLOUDFRONT_PRIVATE_KEY']
    )
    @domain = ENV['CLOUDFRONT_DOMAIN']
  end
  
  def generate(path, expires_in: 1.hour, ip_address: nil)
    url = "https://#{@domain}/#{path}"
    
    options = {
      expires: Time.now + expires_in
    }
    
    # Optional: restrict to specific IP
    if ip_address
      options[:ip_address] = ip_address
    end
    
    @signer.signed_url(url, options)
  end
  
  def generate_policy_url(path, conditions: {})
    policy = {
      'Statement' => [{
        'Resource' => "https://#{@domain}/#{path}",
        'Condition' => {
          'DateLessThan' => {'AWS:EpochTime' => (Time.now + 1.hour).to_i}
        }.merge(conditions)
      }]
    }
    
    @signer.signed_url("https://#{@domain}/#{path}", policy: policy.to_json)
  end
end

# app/controllers/downloads_controller.rb
class DownloadsController < ApplicationController
  before_action :authenticate_user!
  
  def show
    @file = File.find(params[:id])
    
    # Verify user has access
    unless @file.accessible_by?(current_user)
      return render status: :forbidden
    end
    
    # Generate signed URL
    generator = SignedUrlGenerator.new
    signed_url = generator.generate(
      @file.cdn_path,
      expires_in: 15.minutes,
      ip_address: request.remote_ip
    )
    
    redirect_to signed_url, allow_other_host: true
  end
end

Content Security Policy (CSP) requires careful configuration when using CDNs. The CSP header must allow asset loading from CDN domains:

# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
  cdn_domain = ENV['CDN_DOMAIN']
  
  policy.default_src :self
  policy.font_src    :self, cdn_domain
  policy.img_src     :self, cdn_domain, :data
  policy.object_src  :none
  policy.script_src  :self, cdn_domain
  policy.style_src   :self, cdn_domain
  
  # Allow inline styles for Asset Pipeline in development
  if Rails.env.development?
    policy.style_src :self, cdn_domain, :unsafe_inline
  end
end

# Report violations
Rails.application.config.content_security_policy_report_only = false
Rails.application.config.content_security_policy_nonce_generator = 
  ->(request) { SecureRandom.base64(16) }

DDoS Protection comes built into most CDN services through request rate limiting, IP reputation filtering, and traffic analysis. Applications can enhance protection by implementing additional rate limiting at the origin:

# app/middleware/rate_limiter.rb
class RateLimit
  def initialize(app, options = {})
    @app = app
    @limit = options[:limit] || 100
    @period = options[:period] || 1.minute
    @redis = Redis.new(url: ENV['REDIS_URL'])
  end
  
  def call(env)
    request = Rack::Request.new(env)
    
    # Bypass rate limiting for CDN health checks
    if request.get_header('HTTP_X_CLOUDFRONT_HEALTH_CHECK')
      return @app.call(env)
    end
    
    key = "rate_limit:#{client_identifier(request)}"
    count = @redis.incr(key)
    
    if count == 1
      @redis.expire(key, @period)
    end
    
    if count > @limit
      return [429, {'Content-Type' => 'text/plain'}, ['Too Many Requests']]
    end
    
    @app.call(env)
  end
  
  private
  
  def client_identifier(request)
    # Prefer CloudFront viewer IP header
    request.get_header('HTTP_CLOUDFRONT_VIEWER_ADDRESS') ||
      request.ip
  end
end

Tools & Ecosystem

The CDN ecosystem includes major providers, Ruby integration gems, and supporting tools for monitoring and optimization.

Major CDN Providers offer different capabilities and pricing models. CloudFront integrates deeply with AWS services, providing S3 origin support, Lambda@Edge for edge computing, and comprehensive analytics. Cloudflare emphasizes security features including WAF, DDoS protection, and bot management, while also offering generous free tiers. Fastly targets developers with real-time purging, VCL customization, and edge computing through Compute@Edge. Akamai serves enterprise customers with the largest CDN network and advanced optimization features.

Provider selection depends on requirements including geographic coverage, feature needs, pricing structure, and existing infrastructure. Applications already using AWS benefit from CloudFront integration, while applications requiring advanced security features might prefer Cloudflare.

# Gemfile
# AWS CloudFront
gem 'aws-sdk-cloudfront'
gem 'aws-sdk-s3'

# Cloudflare
gem 'cloudflare'

# Fastly
gem 'fastly'

# Multi-CDN support
gem 'cdn_uploader', github: 'your-org/cdn_uploader'

Asset Management Gems handle asset uploading and synchronization. The asset_sync gem specifically targets Rails asset pipeline integration with S3 and CloudFront. The fog gem provides abstracted cloud storage access across multiple providers including AWS, Google Cloud, and Azure.

# config/initializers/fog.rb
connection = Fog::Storage.new(
  provider: 'AWS',
  aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
  aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
  region: 'us-east-1'
)

# Upload file to S3
directory = connection.directories.get(ENV['ASSETS_BUCKET'])
directory.files.create(
  key: 'assets/logo.png',
  body: File.open('app/assets/images/logo.png'),
  public: true,
  cache_control: 'public, max-age=31536000'
)

Monitoring Tools track CDN performance and costs. CDN providers offer native dashboards showing cache hit ratios, bandwidth usage, and request patterns. Third-party tools like Datadog and New Relic integrate with multiple CDN providers for unified monitoring:

# app/services/cdn_monitor.rb
class CdnMonitor
  def initialize
    @statsd = Datadog::Statsd.new('localhost', 8125)
  end
  
  def track_request(path, cache_status)
    tags = [
      "path:#{path}",
      "cache_status:#{cache_status}"
    ]
    
    @statsd.increment('cdn.request', tags: tags)
    
    if cache_status == 'hit'
      @statsd.increment('cdn.cache_hit', tags: tags)
    else
      @statsd.increment('cdn.cache_miss', tags: tags)
    end
  end
  
  def track_bandwidth(bytes)
    @statsd.histogram('cdn.bandwidth_bytes', bytes)
  end
end

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  around_action :track_cdn_metrics
  
  private
  
  def track_cdn_metrics
    start = Time.now
    yield
    duration = Time.now - start
    
    cache_status = response.headers['X-Cache'] || 'unknown'
    
    CdnMonitor.new.track_request(
      request.path,
      cache_status
    )
  end
end

Testing Tools verify CDN configuration and behavior. Command-line tools inspect response headers to confirm caching behavior, while specialized tools like webpagetest.org analyze complete page load performance including CDN impact:

Automated testing verifies CDN integration in CI/CD pipelines:

# spec/features/cdn_spec.rb
require 'rails_helper'

RSpec.describe 'CDN Integration', type: :feature do
  it 'serves assets from CDN domain' do
    visit root_path
    
    css_link = page.find('link[rel=stylesheet]', visible: false)
    expect(css_link[:href]).to start_with("https://#{ENV['CDN_DOMAIN']}")
  end
  
  it 'sets appropriate cache headers for static assets' do
    response = Net::HTTP.get_response(
      URI("https://#{ENV['CDN_DOMAIN']}/assets/application.css")
    )
    
    expect(response['Cache-Control']).to include('max-age=31536000')
    expect(response['Cache-Control']).to include('public')
  end
  
  it 'includes CDN domain in CSP header' do
    visit root_path
    
    csp = page.response_headers['Content-Security-Policy']
    expect(csp).to include(ENV['CDN_DOMAIN'])
  end
end

Reference

HTTP Cache Headers

Header Purpose Example Value
Cache-Control Controls caching behavior public, max-age=3600
Expires Legacy cache expiration Wed, 21 Oct 2026 07:28:00 GMT
ETag Content validation token 686897696a7c876b7e
Last-Modified Content modification time Wed, 21 Oct 2024 07:28:00 GMT
Vary Cache key variations Accept-Encoding, Accept
Surrogate-Key Cache tag for purging product-123 category-5
Surrogate-Control CDN-specific cache control max-age=3600

Cache-Control Directives

Directive Effect Use Case
public Allow caching by any cache Static assets, public pages
private Allow caching only by browser User-specific content
no-cache Must revalidate before use Frequently updated content
no-store Never cache Sensitive data, CSRF tokens
max-age Cache duration in seconds All cached content
s-maxage CDN-specific cache duration Different TTL for CDN vs browser
must-revalidate Strict expiration enforcement Critical content
immutable Content never changes Fingerprinted assets

Common TTL Strategies

Content Type TTL Rationale
Static assets with fingerprints 1 year Never change, new versions use new URLs
Images with stable URLs 1 week Rarely change, acceptable staleness
HTML pages 5 minutes Balance freshness and cache benefit
API responses (public data) 15 minutes Acceptable staleness for most reads
API responses (user data) 0 Must be current
Sitemaps and feeds 1 hour Updated regularly but not critical
JavaScript/CSS bundles 1 year Fingerprinted via webpack/sprockets

Rails Configuration Options

Configuration Purpose Example
config.asset_host CDN hostname for assets https://cdn.example.com
config.action_controller.asset_host Override for controller assets Same as config.asset_host
config.assets.digest Enable fingerprinting true
config.assets.compile Runtime compilation false in production
config.public_file_server.headers Headers for static files Cache-Control header

CloudFront Response Headers

Header Source Meaning
X-Cache CloudFront Hit from cloudfront or Miss from cloudfront
X-Amz-Cf-Pop CloudFront Edge location serving request
X-Amz-Cf-Id CloudFront Request identifier
Age CDN/Browser Seconds object in cache
Via Proxy Proxy servers in chain

Asset Sync Configuration

Option Purpose Default
fog_provider Cloud provider AWS
fog_directory S3 bucket name Required
fog_region AWS region us-east-1
existing_remote_files Handle old files keep
gzip_compression Compress before upload false
manifest Use manifest for sync false
cache_asset_regexps Patterns to cache Empty

CDN Provider Comparison

Feature CloudFront Cloudflare Fastly Akamai
Global PoPs 400+ 300+ 70+ 4000+
Free Tier No Yes (generous) No No
Edge Computing Lambda@Edge Workers Compute@Edge EdgeWorkers
Purge API Yes Yes Instant Yes
Custom SSL Yes (ACM) Yes (Universal) Yes Yes
Origin Shield Yes Yes No Yes
Real-time Logs Yes Yes Yes Yes
Pricing Model Pay per GB Flat rate + usage Pay per request Enterprise