Media and images: the upload that never stays on the server.

Where an upload goes when the container cannot keep it, which storage mode to choose, how an image CDN turns one file into every size, and the day a bucket setting rewrote every image URL on a site.

One image travelling from upload to delivery A single diagonal conveyor runs from bottom-left to top-right. The same picture travels along it, reshaped at five stops: an upload browser window, the WordPress container drawn with an open bottom so nothing is stored, an object-storage bucket holding 2026/10/photo.jpg, an image CDN lens that fans the picture into three widths w=400, w=800 and w=1600, and finally a visitor's phone showing the image. One amber item, a local uploads folder struck through under the open container, marks the copy that must not persist. A green check marks the bucket as the one place the original lives. UPLOAD WORDPRESS BUCKET 2026/10/photo.jpg IMAGE CDN VISITOR w=400 w=800 w=1600 uploads/
One upload, five stops. The container processes the file; object storage keeps the durable copy.

Five stops, and the server is only one of them.

A disposable container cannot be the only durable home for an upload. A file on one replica is not automatically available on another. On udx.io, the file passes through the server into object storage. Follow its path and the failure to watch for at each stop.

Browser

The editor sends a file to WordPress. The normal media upload is a request to the application; chunking depends on the uploader.

The smallest limit across the proxy, PHP and WordPress wins. Allow for request overhead as well as the file itself.

PHP, briefly

In this site's Ephemeral mode, WordPress generates its registered sizes, uploads the files to the bucket, then removes the local copies.

PHP still needs a writable temporary directory. A read-only application filesystem does not eliminate upload processing.

Bucket

The durable objects use year-and-month paths and hashed filenames. Versioning retains older generations when a live object is replaced or deleted.

Uniform bucket-level access disables object ACLs. After 90 consecutive days enabled, it cannot be turned off.

Image CDN

Imgix fetches the source and caches width and format variants. The responses measured here advertise a one-year cache lifetime.

Replacing the source at the same URL can leave old variants cached. Use a new versioned URL or purge the affected variants.

Visitor

The browser chooses among the image candidates the page provides, based on layout size and display density.

Check srcset and sizes in the rendered page. A phone can still download a large image when those hints are missing or wrong.

Reference: uniform bucket-level access and Object Versioning. These are separate from the image CDN cache policy.

One question: does the server keep a copy?

WP-Stateless, the plugin we maintain for this, has four modes. Two modes retain local media; two remove that dependency. Choose from the storage contract your application and plugins need.

Backup

Keeps the file on local disk and copies it to the bucket. Serves from local disk.

Local copy
kept
Served from
server
Independent replicas
local dependency
A single server you are not ready to leave, where the bucket is insurance.
CDN

Keeps the file on local disk and copies it to the bucket. Serves from the bucket.

Local copy
kept
Served from
bucket
Independent replicas
local dependency
A single server that wants bucket delivery today and a clean move later.
EphemeralThis site

Writes the file locally only while sizes are generated, uploads each, deletes the local copies.

Local copy
seconds
Served from
bucket
Independent replicas
supported
Containers with a writable but disposable filesystem. This site.
Stateless

Stores media in the bucket without maintaining a local uploads copy. PHP upload handling still uses temporary storage.

Local copy
none
Served from
bucket
Independent replicas
supported
A read-only application filesystem with writable temporary storage. Test plugins that expect local files.

Mode names are the plugin's. The question underneath them applies to any storage plugin on any platform.

One file, every size, measured.

One image from udx.io, measured on October 12, 2026 (UTC). Each row links to the same source or a specific Imgix variant. Sizes are decimal KB; content negotiation can change the format a browser receives.

Original on the bucketcached 10 hours at the origin56.1 KB
w=1600PNG, generated on first request57.4 KB
w=800PNG30.1 KB
w=400PNG13.6 KB
w=800, auto=format,compressserved as JPEG, 57% smaller than the PNG at the same width12.9 KB

The 1600-pixel PNG is slightly larger than the original. Resizing alone is not a guarantee of fewer bytes. At 800 pixels, automatic format and compression reduced this response by 57%. The original advertised ten hours of freshness; these CDN variants advertised one year. Both still need a versioned URL or invalidation if the source changes.

The month that moved, and two more.

Three media incidents from the operating record. The first one took six months to understand, because nothing in the application had changed.

The month that moved

February 2024First report

Every image URL on a site changed to the current month on the first of the month. The files were fine. The paths were wrong.

37 commentsThe long thread

Reports came from unrelated sites with nothing in common but the plugin. Reproductions failed because the trigger was a bucket setting, not a plugin setting.

The causeA one-way door

The buckets had switched to uniform bucket-level access. A per-object permission write failed, metadata was not saved, and the plugin regenerated paths using the current month. Uniform access becomes irreversible after 90 consecutive days enabled.

August 2024The fix

The fix skips the object ACL write for uniform buckets. The lesson: verify plugin compatibility before enabling uniform access, and resolve problems within its 90-day reversal window.

Incident 06 / MediaThe edge forgot the image lifetime

An image service pod killed for memory once a day

CauseThe CDN honored a 60-second cache lifetime instead of the application's four hours, so the origin re-optimized 3,160 images an hour for 303 distinct variants

Evidence and correctionCDN hit ratio alongside the container memory graph. Fixed with an origin-honoring cache policy and a memory arena cap

View the operating record ↗
Incident 07 / MediaOne aborted request wedged the image

Images failing for 65 percent of visitors over thirty days, with a p95 response time of 30 seconds

CauseAn upstream image optimizer bug: one aborted request on a cold cache key wedged that key for every later visitor, and a year of cached HTML kept asking for an image width the site no longer generated

Evidence and correctionThirty days of CDN standard logs, 2,032 unique clients, status 000 for viewer disconnects. Nothing in application monitoring showed it

View the operating record ↗

Also in the record, from pull requests and the plugin issue tracker

SymptomUploads fail on a serverless platform with "Unable to create directory"CauseThe upload writes a temporary file under the uploads folder before the bucket sees itCorrectionProvide writable temporary storage and test the chosen mode on the platform
SymptomCDN purges silently stopped after a plugin updateCauseTwo plugins bundled different versions of the same cloud client libraryCorrectionNamespace the bundled library; the fleet upgraded in a day
SymptomAn image CDN bill climbed with no new trafficCauseVideo and PDF were being routed through the image optimizerCorrectionExclude non-image types from the CDN rewrite
SymptomThousands of broken image links after a bucket moveCauseContent held absolute URLs to the old bucketCorrectionRewrite at render time and purge the CDN, not a search-and-replace
SymptomA projected image bill above 1,000 dollars a monthCauseA third-party optimizer priced per image for a listings siteCorrectionAn own image service behind the CDN, about 60 dollars a month

You are reading the example.

This handbook is served by the system it describes. Here is the media path behind the page you are reading, checked on October 12, 2026 (UTC).

01 / Mode

Ephemeral.

The container keeps an upload only while sizes are generated. The inspected production pod held no retained media files in its uploads directory.

02 / Bucket

One bucket, versioned.

863 media items, 1.4 GB with every generated size, under year-and-month paths with hashed filenames. Object versioning since 2019; a seven-day soft delete since 2024. Attachment deletion removes the live remote object; versioning and soft delete have distinct recovery rules.

03 / Delivery

An image CDN in front.

Pages reference the CDN hostname with a width in the query string. The measured variants advertised a one-year cache lifetime. The source advertised ten hours and revalidation.

04 / What we would change

Generate nothing at upload.

Dynamic image support is off here, so WordPress still produces its standard sizes and uploads each one. Dynamic delivery could remove redundant generated files. First we need to verify the theme, editing flows and plugin compatibility; that change has not been made.

Seven decisions, in order.

Seven decisions to make before media becomes a migration problem.

Decide the bucket access mode first

Choose uniform bucket-level access or object ACLs deliberately. Uniform access becomes irreversible after 90 consecutive days enabled, not 90 days after bucket creation.

Pick the mode for the platform

Ephemeral suits disposable writable containers. Stateless avoids a local uploads copy, but still needs temporary upload handling. Test the plugins that read or modify media files.

Give uploads a writable temporary directory

Browser uploads need one even when nothing is kept. Put it on the fastest disposable disk you have.

Keep video and documents off the image CDN

Route them straight from the bucket. An image optimizer charges for every byte it touches.

Align cache policy with update policy

A short outer cache lifetime increases requests to the next layer. It does not necessarily regenerate an image: the optimizer may serve its own cached result. Measure each layer.

Keep old image URLs working

Cached pages may continue requesting older widths or paths. Preserve those variants or redirect them safely; version new URLs when the underlying image changes.

Treat the URL as permanent

A stable custom media hostname decouples content from a provider. A later move still needs matching paths, copied objects, TLS and a cache cutover; DNS alone does not move the files.

Asked when the images stop loading.

Should WordPress uploads live on the web server at all?

They can, on a deliberately managed persistent filesystem. For disposable containers and independent replicas, object storage removes the need to share a local uploads directory. Keep attachment metadata in the database and verify how each plugin reads, writes and deletes the remote files.

Which WP-Stateless mode should I use?

Ephemeral for containers with a writable disposable filesystem. Stateless avoids keeping an uploads copy, but PHP still needs temporary upload storage and some plugins need compatibility support. Backup and CDN retain local files. The plugin overview describes the four modes.

Why did all my image URLs change?

Usually because a file's stored path was regenerated. In the incident above, a bucket access-mode change made a permission write fail, the metadata never saved, and the plugin rebuilt the path with the current month on every request. Check the bucket's access mode and the plugin's stored metadata before touching the content.

Does an image CDN replace WordPress thumbnails?

It can, when the theme and media integrations support dynamic delivery. This site still generates standard WordPress sizes and uploads them; Imgix also serves variants from the source. Before disabling local size generation, test editing, srcset, downloads and plugins that reference registered thumbnails. On-demand delivery avoids generating unused variants but still has storage, request and transformation costs.

What about PDFs, video and other files?

Store them in the same bucket, serve them from the bucket or a plain CDN, and keep them away from the image optimizer. Two sites in our record paid for it: one in bandwidth, one in broken downloads because the CDN source did not match the bucket.

Images breaking while every dashboard stays green?

Tell us where uploads live, what serves them and what visitors see. We can scope a media delivery investigation and the engineering work needed to make the path reliable.