Image delivery performance is crucial for modern web applications. By creating your own image CDN using AWS S3 and CloudFront, you can significantly improve load times and enhance the user experience. This guide walks you through setting up a robust image CDN infrastructure.

Why use a CDN for images

Content Delivery Networks (CDNs) offer several advantages for image delivery:

  • Reduced latency through edge location caching.
  • Lower bandwidth costs by offloading traffic from your origin server.
  • Improved website performance with faster page load times.
  • Enhanced user experience across diverse geographical regions.
  • Better handling of traffic spikes during sudden demand increases.

Setting up an AWS S3 bucket

  1. Create a New S3 Bucket:

    • Log into the AWS Management Console.
    • Navigate to S3.
    • Click Create bucket.
    • Enter a unique bucket name.
    • Select your preferred AWS Region.
    • Keep Block all public access enabled to secure your bucket.
    • Click Create bucket.
  2. Configure Bucket Policy:

    Update your bucket policy to allow CloudFront to access your S3 bucket securely using Origin Access Control (OAC):

    {
      "Version": "2012-10-17",
      "Statement": {
        "Sid": "AllowCloudFrontServicePrincipal",
        "Effect": "Allow",
        "Principal": {
          "Service": "cloudfront.amazonaws.com"
        },
        "Action": "s3:GetObject",
        "Resource": "arn:aws:s3:::your-bucket-name/*",
        "Condition": {
          "StringEquals": {
            "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/<CloudFront distribution ID>"
          }
        }
      }
    }
    

    Replace your-bucket-name with your actual bucket name and <CloudFront distribution ID> with your distribution's ID.

  3. Enable CORS (if needed):

    If your application requires Cross-Origin Resource Sharing (CORS), configure the CORS policy for your S3 bucket:

    [
      {
        "AllowedHeaders": ["*"],
        "AllowedMethods": ["GET"],
        "AllowedOrigins": ["*"],
        "ExposeHeaders": [],
        "MaxAgeSeconds": 3000
      }
    ]
    

Configuring cloudfront

  1. Create a CloudFront Distribution:

    • Navigate to the CloudFront console.
    • Click Create Distribution.
    • Under Origin Domain, enter your S3 bucket's domain.
    • For Origin Access, select Origin Access Control (OAC).
    • Click Create new OAC to allow CloudFront to securely access your S3 bucket.
    • Ensure that your bucket policy is updated to grant access to the OAC.
  2. Configure Default Cache Behavior:

    • Set Viewer Protocol Policy to Redirect HTTP to HTTPS.
    • Under Allowed HTTP Methods, select GET, HEAD, and OPTIONS.
    • Enable Cached HTTP Methods for GET and HEAD.
    • Customize cache key settings under Cache key and origin requests if needed.
  3. Configure Cache Settings:

    • Set Default TTL to 86,400 seconds (1 day).
    • Set Maximum TTL to 31,536,000 seconds (1 year).
    • Set Minimum TTL to 0 seconds.
  4. Configure Error Responses:

    CustomErrorResponses:
      - ErrorCode: 404
        ResponseCode: 404
        ResponsePagePath: /404.html
        ErrorCachingMinTTL: 300
      - ErrorCode: 500
        ResponseCode: 500
        ResponsePagePath: /500.html
        ErrorCachingMinTTL: 10
    
  5. Enable Security Headers:

    Create a Lambda@Edge function for the Viewer Response event:

    exports.handler = async (event) => {
      const response = event.Records[0].cf.response
      const headers = response.headers
    
      headers['strict-transport-security'] = [
        {
          key: 'Strict-Transport-Security',
          value: 'max-age=31536000; includeSubdomains; preload',
        },
      ]
      headers['x-content-type-options'] = [
        {
          key: 'X-Content-Type-Options',
          value: 'nosniff',
        },
      ]
      headers['x-frame-options'] = [
        {
          key: 'X-Frame-Options',
          value: 'DENY',
        },
      ]
      headers['x-xss-protection'] = [
        {
          key: 'X-XSS-Protection',
          value: '1; mode=block',
        },
      ]
      headers['referrer-policy'] = [
        {
          key: 'Referrer-Policy',
          value: 'no-referrer-when-downgrade',
        },
      ]
    
      return response
    }
    

Optimizing image delivery

  1. Configure Cache-Control Headers:

    When uploading images to S3, set appropriate Cache-Control headers to enable effective caching:

    s3_client.put_object(
        Bucket='your-bucket-name',
        Key='path/to/image.jpg',
        Body=image_data,
        ContentType='image/jpeg',
        CacheControl='public, max-age=31536000, immutable'
    )
    
  2. Use Query String Parameters for Versioning:

    Append a version query parameter to your image URLs to manage cache invalidation:

    https://d1234.cloudfront.net/images/logo.png?v=2
    
  3. Implement Image Optimization:

    For advanced image transformations, such as format conversion (e.g., converting images to WebP) and dynamic resizing, consider using AWS Lambda@Edge. This approach offers network access and extended execution times compared to CloudFront Functions, which have significant limitations.

Monitoring and cost optimization

  1. Set Up CloudWatch Monitoring:

    Create CloudWatch dashboards to track key metrics:

    • Cache hit ratios.
    • Error rates.
    • Latency and bandwidth usage.
    • Request counts.
  2. Configure Alerts:

    Set up CloudWatch alarms for:

    • High error rates (above 1%).
    • Low cache hit ratios (below 85%).
    • Unusual traffic patterns.
    • Bandwidth spikes.
  3. Optimize Costs:

    • Use Price Class 100 to target North America and Europe.
    • Enable compression to reduce bandwidth consumption.
    • Set appropriate TTL values to maximize cache hits.
    • Regularly review reserved capacity based on usage patterns.

Troubleshooting common issues

  • Verify that your CloudFront distribution status is "Deployed."
  • Check AWS CloudWatch logs for detailed error messages.
  • Ensure your S3 bucket policy and OAC configuration are correctly set.
  • Confirm that Cache-Control headers are applied by inspecting HTTP response headers.
  • Adjust TTL settings if you encounter stale content issues.

Testing your CDN

  1. Verify Distribution Status:

    Ensure your CloudFront distribution status is Deployed.

  2. Test Image Loading:

    <img
      src="https://your-distribution-id.cloudfront.net/images/test.jpg"
      alt="Test Image"
      loading="lazy"
      width="800"
      height="600"
    />
    
  3. Monitor Performance:

    • Use AWS CloudWatch metrics to track performance.
    • Monitor cache hit ratios and error rates.
    • Track latency across different regions.
    • Analyze bandwidth usage patterns.

Best practices

  1. Image Optimization:

    • Use appropriate formats: JPEG for photographs, PNG for graphics with transparency.
    • Implement responsive images using srcset and sizes attributes.
    • Enable Brotli or GZIP compression for text-based assets.
  2. Security:

    • Serve content exclusively over HTTPS.
    • Enforce proper CORS policies.
    • Use Signed URLs or Signed Cookies for private content as necessary.
    • Regularly rotate security credentials.
    • Ensure your S3 bucket is accessible only via CloudFront.
  3. Performance:

    • Set suitable TTL values to balance freshness and efficiency.
    • Employ query strings for cache busting.
    • Implement custom error pages.
    • Continuously monitor and optimize cache hit ratios.

Conclusion

Creating an image CDN using AWS S3 and CloudFront provides a scalable and secure solution for delivering images quickly across the globe. This setup helps improve website performance and offers advanced optimization techniques that ensure high-quality image delivery.

If you are looking for a managed service that includes both CDN functionality and advanced image processing, check out Transloadit, which offers comprehensive image optimization and delivery capabilities.