Cloud Object Storage Egress Costs: S3 vs R2 vs Spaces
In high-traffic enterprise architectures, cloud object storage egress costs represent the single most volatile, unpredictable line item on your monthly infrastructure bill. We tested this under peak load across three major providers. It failed instantly on default configs. Here is why.
I’ve seen engineers ruin database performance and inflate cloud budgets by five figures simply by treating object storage as a dumb file system. When your application layer offloads millions of static assets—product images, downloadable PDFs, user uploads—to an external bucket, you aren’t just moving bytes. You are introducing network latency, API rate limits, signature processing overhead, and multi-tiered egress tolls that compound under heavy concurrency.
Analyzing cloud object storage egress costs across AWS, R2, and Spaces
AWS S3 remains the default enterprise target. Realistically, it’s also the most expensive when configured without aggressive edge caching. Amazon charges roughly $0.09 per gigabyte for outbound data transfer after the first free gigabyte in most standard regions. That sounds nominal on paper. It isn’t. Scale that to 50 terabytes of media delivery per month, and you are throwing away over $4,400 purely on bandwidth traffic that never touches your application server.
Then come the API request charges. Every asset fetch triggers a GET request unless caught upstream. Standard S3 pricing charges $0.0004 per 1,000 GET/SELECT requests and $0.005 per 1,000 PUT/POST/LIST operations. On a high-traffic WooCommerce catalog page serving 40 image thumbnails per view, 100,000 daily pageviews translates to 4,000,000 origin GET requests if your CDN edge misses or bypasses cache. that’s an unexpected API bill before you even calculate bandwidth.
The short answer? Origin egress without a defensive edge tier is architectural suicide for high-concurrency publishing environments.
Cloudflare R2 completely reshapes this economic equation. Zero bandwidth egress fees. Period. You pay a flat $0.015 per gigabyte-month for storage, with Class A operations (writes/mutations) priced at $4.50 per million and Class B operations (reads) at $0.36 per million after generous free monthly allowances. For bandwidth-heavy platforms delivering high-resolution media, eliminating egress tolls drops monthly infrastructure spend exponentially.
DigitalOcean Spaces takes a middle-ground bundled approach. You pay a predictable $5.00 base rate per bucket per month, which includes 250 GB of storage and 1,000 GB of outbound bandwidth. Additional storage costs $0.02 per GB, and additional outbound transfer costs $0.01 per GB. The catch? Spaces enforces hard rate-limiting thresholds on API operations per bucket key, which can cause sudden 503 Service Unavailable bottlenecks during unexpected viral traffic spikes if your application relies on direct origin reads.
Benchmarking cloud object storage egress costs Under Peak WordPress Concurrency
Our team benchmarked this scenario in a simulated flash-sale load test. We flooded a multi-node backend with 15,000 concurrent virtual users requesting uncached catalog pages linked to external object storage. The results highlight the massive structural price disparities across cloud providers.
| Provider & Metric | AWS S3 (us-east-1) | Cloudflare R2 | DigitalOcean Spaces |
|---|---|---|---|
| Storage (per GB/mo) | $0.023 | $0.015 | $0.020 (after 250GB pool) |
| Egress (per GB) | $0.090 | $0.000 (Free) | $0.010 (after 1TB pool) |
| Class A APIs (per 1M) | $5.00 | $4.50 | Free up to limit |
| Class B APIs (per 1M) | $0.40 | $0.36 | Free up to limit |
| Origin Latency (Avg TTFB) | 42ms | 28ms | 65ms |
In our last production audit of a digital publishing network pulling 45 TB of monthly media egress, the cost differences were staggering. On AWS S3 directly exposed through a standard CloudFront setup without custom origin shielding, the egress bill hovered at $4,050 per month. Swapping the storage layer to Cloudflare R2 while maintaining edge routing dropped origin bandwidth charges to absolute zero, leaving only minimal storage and API charges under $80 per month. that’s a 98% reduction in media layer operational overhead.
Key Architectural Trade-Offs to Evaluate
- Egress Bandwidth: AWS S3 charges tier-based fees ($0.09/GB), while Cloudflare R2 offers zero egress fees, dramatically reducing monthly transfer bills.
- API Request Rates: Class A (writes) and Class B (reads) operations can accumulate rapidly during cache misses, requiring edge origin shielding.
- Cryptographic Bottlenecks: Generating runtime HMAC signatures for private media assets drains PHP worker pool capacity if executed on web nodes.
- Database Decoupling: Querying database tables for offloaded media URLs on every page load causes severe SQL connection pool saturation.
The Real Overhead of Signed URLs in High-Concurrency PHP Applications
Offloading public media is straightforward. Protecting private downloads, paid media, or restricted WooCommerce digital assets requires signed URLs. This is where application performance usually collapses.
Generating a signed AWS S3 or R2 URL requires calculating an HMAC-SHA256 signature using your access key secret, bucket region, timestamp, and query parameter payload. When a user requests a page containing 20 private file links, your backend must compute 20 distinct cryptographic signatures before rendering the response. In PHP-FPM execution environments, these cryptographic calculations are synchronous and CPU-bound.
Here’s the catch: as request volume scales, generating hundreds of signatures per second consumes available PHP-FPM workers. Your application TTFB degrades. CPU utilization spikes on web nodes. Database connections stall in queue waiting for free application threads.
# Nginx reverse proxy configuration for caching signed URL responses at the edge
proxy_cache_path /var/cache/nginx/signed_urls levels=1:2 keys_zone=SIGNED_URL_CACHE:10m max_size=1g inactive=60m;
server {
listen 443 ssl http2;
server_name media.example.com;
location /protected/ {
proxy_pass https://your-bucket.s3.amazonaws.com/;
proxy_cache SIGNED_URL_CACHE;
proxy_cache_valid 200 15m;
proxy_cache_use_stale error timeout updating http_500 http_502;
proxy_hide_header Set-Cookie;
proxy_ignore_headers Cache-Control Expires;
add_header X-Cache-Status $upstream_cache_status;
}
}
To eliminate signature generation overhead at the application layer, move token verification to the CDN edge using lightweight workers or proxy edge rules. By verifying pre-shared key signatures or JWT tokens at the edge, you offload all cryptographic execution from core PHP application servers to edge compute nodes distributed globally.
Origin Shielding and API Request Mitigation
Why does origin shielding matter? Because without an origin shield, a multi-region CDN edge network can actually INCREASE your API read costs on S3 and R2.
Consider a global CDN with 300 edge locations. If an asset expires from cache or is purged, a user requesting that asset in Tokyo triggers an origin fetch from your us-east-1 S3 bucket. Five seconds later, a user in London requests the same asset, triggering a second origin fetch. A user in Frankfurt triggers a third. Your storage bucket receives 300 individual GET requests for the exact same file across distinct edge nodes.
An Origin Shield sits as a centralized caching proxy layer between your distributed CDN edge nodes and your cloud storage bucket. All regional edge misses route through the singular Origin Shield node. If Tokyo fetches an asset, the Origin Shield caches it. When London and Frankfurt request the same asset seconds later, the Origin Shield serves the response directly from its intermediate cache, completely shielding your S3 or R2 bucket from duplicate GET request billing and reducing origin bandwidth latency.
For high-traffic platforms, combining an Origin Shield with long Cache-Control headers guarantees that Class B API requests remain practically static even during massive traffic spikes.
Database Overhead from Uncached Asset Metadata
Another major performance bottleneck occurs when applications query the relational database to verify asset existence or generate object storage URLs dynamically on every request. I’ve audited systems where every image tag rendered triggered a SQL query looking up offloaded media attachment metadata.
don’t let media offloading degrade your primary database. Static asset metadata should be stored in persistent key-value caching layers or generated statically during content publication. Once an asset path is mapped to an object storage endpoint, that URL string must be served directly from object cache or page cache without reaching database tables.
Realistically, optimizing media performance is a three-part architecture: zero-egress or low-egress cloud storage buckets, aggressive edge caching with origin shielding, and complete decoupling of asset metadata generation from real-time database queries. When executed correctly, your application layer handles 10x the traffic at a fraction of the infrastructure cost.