Uploads return 200 but the file 404s
The upload succeeds. The API returns 200, the entry saves, and the file will not load: the URL 404s, or it renders as a broken image in the admin. This is almost never data loss. It is usually the upload provider quietly falling back to local storage in production, or existing files rows still pointing at the old host after a provider change. Confirm which before you touch anything: open the raw file URL outside the admin, check whether only pre-existing media is broken, and read what the files table actually says.
00· Before anything
First: three questions, in order
This symptom has several distinct causes that look identical from the entry screen. Three questions separate them before you change anything.
One: does the raw file URL load in a new tab, outside the admin? If it loads there but not in the admin, the file is fine and the admin cannot render it, which points at CSP. Two: is it only pre-existing media that is broken, while new uploads work? That asymmetry points at the files table, not at config. Three: what does the table actually say.
SELECT url, provider FROM files LIMIT 5;Run that sequence before editing any config. It resolves almost every version of this without changing a thing, and it tells you which of the causes below you are actually in.
Likely causes · The order I check them
Cause 01 of 01
The upload provider isn't configured for production, so Strapi uses local storage
Files upload and work for a while, then vanish, often after a deploy or a restart. New uploads meet the same fate.
Check the provider column in the files table. If it says local in production, you found it. Cross-check against your config with a resolved-config log at boot.
When the provider is not resolved for the production environment, Strapi falls back to the local provider. On an ephemeral container filesystem the file is written, served once, and gone at the next restart.
Configure the upload provider in config/env/production/plugins, not just config/plugins. This is the same root cause as config being ignored in production.
The provider falling back to local on misconfiguration is observed behaviour, not documented. The ephemeral-filesystem half is documented, but only in the FAQ and only for self-hosted PaaS.
02
Do not apply the ephemeral-filesystem framing to Strapi Cloud. Cloud asset storage is a persistent, billed resource, so a 200-then-404 there has a different mechanism, usually the files-table or CSP cause below.
Likely causes · The order I check them
Cause 01 of 04
The provider changed, but the files table didn't
New uploads work. Everything uploaded before the change 404s. That asymmetry is the tell.
SELECT url, provider FROM files LIMIT 5. If url, formats and provider still point at the old host or provider for existing rows, this is it.
Changing the provider does not rewrite existing files rows. Their stored url, formats and provider still point at wherever they were uploaded.
Rewrite the files table to the new host and provider. Back up the table first, then run it in a transaction:
update files set url = replace(url, '<old-host>', '<new-host>') where url is not null;
update files set formats = replace(formats::TEXT, '<old-host>', '<new-host>')::jsonb where formats is not null;
update files set provider = replace(provider, '<old-provider>', '<new-provider>') where provider is not null;A provider change leaving existing rows untouched is observed behaviour, not documented. Check five rows before and after you commit.
Cause 02 of 04
The admin's CSP doesn't include the storage domain
The file loads fine in a new tab. In the admin it is a broken image. Opening the URL directly works.
Open the raw file URL outside the admin. If it loads there, the file is fine and the admin's content-security-policy is blocking the storage domain.
Add your storage domain to the img-src (and media-src for video) directives of the security middleware's contentSecurityPolicy, in config/env/production/middlewares.
Remember the Cloud full-replacement rule: include the whole middleware array, not just the security block.
Cause 03 of 04
Private bucket, bucket-name mismatch
Files 404 or come back unauthorised, and it started after a non-production database was cloned from production.
The S3 provider derives the bucket from each file's stored URL. On a mismatch it skips signing, returns an unsigned URL, and logs nothing. This happens every time a non-prod database is cloned from prod and the bucket name differs.
Align the bucket name across environments, or rewrite the files URLs for the cloned environment so the derived bucket matches the one that environment can actually sign for.
Cause 04 of 04
Absolute URLs pasted inside rich-text or dynamic-zone content
Most media is fine. A handful of images inside article bodies or components 404, and they all point at the old host.
A migration rewrites the files table. It does not rewrite prose. Absolute URLs pasted into rich-text or dynamic-zone fields keep pointing at the old host.
Rewrite the affected content fields as a separate migration, or store media as relations rather than pasted absolute URLs so the files rewrite covers them.
07
What's documented, and what's observed
Two of the most useful facts here are not in the documentation. The provider falling back to local on misconfiguration, and a provider change leaving existing files rows untouched, are both observed behaviour: reliable, reproducible, and worth knowing, but not written down. The ephemeral-filesystem behaviour is documented, though only in the FAQ and only for self-hosted PaaS. Treat the SQL rewrite as a support-grade workaround: back up the table, run it in a transaction, and check five rows before and after.
Common questions
Not if you back up first and run it in a transaction. It only rewrites the host and provider strings in existing rows; it does not touch the files themselves. Confirm with a SELECT on five rows before you commit.
That asymmetry almost always means the provider changed and the files table still points at the old host. New uploads get the new provider; old rows were never rewritten.
No. That is a content-security-policy problem. The admin will not render an image from a domain that is not in its CSP, even though the file is perfectly fine.
Diagnosed everything and still stuck?
S/01 — Performance & Architecture Rescue.
A fixed-scope week that finds the cause and fixes what's causing it.