This guide walks you through setting up a custom CDN domain for your Cloudflare R2 bucket to serve uploaded images efficiently.
Prerequisites
- Cloudflare account with R2 enabled
- Domain managed by Cloudflare (or partial CNAME setup)
- R2 bucket already created (e.g.,
tendsocial)
Step 1: Navigate to R2 Bucket Settings
- Log in to your Cloudflare Dashboard
- Go to R2 in the left sidebar
- Select your bucket (e.g.,
tendsocial) - Click on the Settings tab
Step 2: Connect Custom Domain
- Under Public access or Custom Domains, click Connect Domain
- Enter your desired subdomain (e.g.,
cdn.tendsocial.com) - Click Continue
Cloudflare will automatically:
- Create a CNAME DNS record pointing to your R2 bucket
- Provision an SSL certificate
- Enable CDN caching
Step 3: Verify Domain Status
- Wait for the domain status to change from "Initializing" to "Active" (usually 1-2 minutes)
- Refresh the page if needed
- Once active, your custom domain is ready to use
Step 4: Configure Cache Rules (Optional but Recommended)
To optimize CDN performance:
- Go to Cache → Cache Rules in your Cloudflare dashboard
- Create a new rule:
- Name: R2 Image Caching
- When incoming requests match:
Hostname equals cdn.tendsocial.com - Then:
- Cache eligibility: Eligible for cache
- Cache TTL: 1 year (or custom)
- Browser TTL: 1 year
- Save and deploy
Step 5: Configure CORS (Required for Presigned URLs)
For client-side uploads using presigned URLs, you need to configure CORS:
- In your R2 bucket settings, find CORS Policy
- Add the following configuration:
json
[
{
"AllowedOrigins": [
"https://app.tendsocial.com",
"http://localhost:5173"
],
"AllowedMethods": [
"GET",
"PUT",
"POST",
"DELETE",
"HEAD"
],
"AllowedHeaders": [
"*"
],
"ExposeHeaders": [
"ETag"
],
"MaxAgeSeconds": 3600
}
]Note: Update AllowedOrigins to match your actual frontend domains.
Step 6: Update Environment Variables
Update your backend environment variables:
bash
# Cloudflare R2 Configuration
S3_BUCKET=tendsocial
S3_REGION=auto
S3_ENDPOINT=https://370e3c7ed6e50972fea1a19da4eb21c6.r2.cloudflarestorage.com
AWS_ACCESS_KEY_ID=your-r2-access-key-id
AWS_SECRET_ACCESS_KEY=your-r2-secret-access-key
# CDN Domain (custom domain you just configured)
CDN_DOMAIN=cdn.tendsocial.comFor Google Cloud Run:
- Go to your Cloud Run service
- Edit & Deploy New Revision
- Add/update the
CDN_DOMAINsecret/environment variable - Deploy
Step 7: Test the Setup
Test 1: Upload an Image
bash
# Generate presigned URL
curl -X POST https://api.tendsocial.com/api/upload/presigned-url \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"filename": "test.png",
"contentType": "image/png",
"fileSize": 50000
}'You should receive a response with:
uploadUrl- Presigned URL for uploadcdnUrl- CDN URL (e.g.,https://cdn.tendsocial.com/...)key- S3 object keyimageId- Database record ID
Test 2: Upload to Presigned URL
bash
curl -X PUT "PRESIGNED_URL_FROM_ABOVE" \
-H "Content-Type: image/png" \
--data-binary "@path/to/test.png"Test 3: Access via CDN
bash
curl -I "https://cdn.tendsocial.com/YOUR_FILE_KEY"Expected response:
- Status:
200 OK cf-cache-status: HIT(after first request)cache-control: public, max-age=31536000, immutable
Troubleshooting
Domain Status Stuck on "Initializing"
- Wait 5-10 minutes
- Ensure your domain is properly configured in Cloudflare DNS
- Check that there are no conflicting DNS records
CORS Errors in Browser
- Verify CORS policy is correctly configured in R2 bucket settings
- Ensure your frontend domain is in the
AllowedOriginslist - Check browser console for specific CORS error messages
Images Not Loading
- Verify the CDN domain is active
- Check that
CDN_DOMAINenvironment variable is set correctly - Test direct R2 URL first (without CDN) to isolate the issue
Cache Not Working
- Verify cache rules are configured
- Check
cache-controlheaders in response - Use
curl -Ito inspect headers - Clear Cloudflare cache if needed (Caching → Configuration → Purge Everything)
Performance Optimization
Enable Smart Tiered Cache
- Go to Caching → Tiered Cache
- Enable Smart Tiered Cache
- This reduces requests to R2 origin and improves global performance
Enable Argo Smart Routing (Optional, Paid)
For even faster global delivery:
- Go to Traffic → Argo
- Enable Argo Smart Routing
- This optimizes routing for your CDN traffic
Security Best Practices
- Use Presigned URLs for Uploads: Never expose R2 credentials to the client
- Validate File Types: Always validate on the server before generating presigned URLs
- Set Expiration Times: Keep presigned URL expiration short (1 hour recommended)
- Monitor Usage: Set up alerts for unusual upload patterns
- Rate Limiting: Implement rate limits on upload endpoints (already configured in API)
Cost Considerations
- R2 Storage: $0.015/GB/month
- Class A Operations (writes): $4.50 per million requests
- Class B Operations (reads): $0.36 per million requests
- Egress: FREE when using Cloudflare CDN (custom domain)
Important: Using a custom domain (CDN) eliminates egress fees. Direct R2 URLs (.r2.dev) have egress charges.
Next Steps
- [ ] Set up monitoring for R2 usage in Cloudflare dashboard
- [ ] Configure alerts for storage limits
- [ ] Implement image optimization (resizing, compression) if needed
- [ ] Set up automated cleanup for orphaned files