External images import as 0-byte files
Product or media imports create files with the right name but no content. This is almost always the remote image host rejecting requests that carry no User-Agent header.
You run an import (a product feed, a dropshipping integration, a migration tool, a “download external images” routine) and the images arrive empty: the file appears in wp-content/uploads with the correct filename, but its size is 0 bytes. The media library shows a broken thumbnail.
A strong clue that points to this exact cause: the plugin can preview or search the image fine, and only the import produces empty files.
Why this happens
Most import plugins fetch images with PHP’s file_get_contents(). That function sends no User-Agent header unless the code explicitly sets one, because PHP’s built-in default for the user_agent setting is empty.
Many image hosts sit behind a CDN or WAF (CloudFront, Cloudflare, Akamai and similar) configured to reject requests with a missing or empty User-Agent, since that pattern is typical of scrapers and bots. Those requests get a 403 Forbidden instead of the image.
The plugin then does something like this, without checking whether the download succeeded:
$image_data = file_get_contents( $image_url );
file_put_contents( $file, $image_data );
file_get_contents() returned false, so file_put_contents() writes nothing and you are left with a 0-byte file carrying the right name.
The preview or search feature usually works because that part of the plugin uses wp_remote_get(), WordPress’s own HTTP API, which always sends a User-Agent such as WordPress/6.8; https://example.com. Same server, same network, same moment: one request is allowed and the other is refused, purely because of that header.
Confirming the cause
If you have shell access, request one of the failing image URLs twice, once with a User-Agent and once without:
# With a normal User-Agent
curl -s -o /dev/null -w "%{http_code} %{size_download}\n" "https://cdn.example.com/path/to/image.jpg"
# With an empty User-Agent
curl -s -A "" -o /dev/null -w "%{http_code} %{size_download}\n" "https://cdn.example.com/path/to/image.jpg"
A result of 200 with a real byte count on the first command and 403 with a tiny body on the second confirms it.
How to fix it
1. Report it to the plugin developer (the real fix)
This is a bug in the plugin, and it is theirs to correct. Ask them to download images with WordPress’s HTTP API instead of raw file_get_contents():
// Preferred: WordPress helper, sends a User-Agent and handles errors
$tmp = download_url( $image_url );
// Or, if they keep their own request logic
$response = wp_remote_get( $image_url );
$image_data = wp_remote_retrieve_body( $response );
Ask them to check the result before writing the file, so a failed download reports an error instead of silently producing an empty image.
2. If it is your own code
Set a User-Agent on the request:
$context = stream_context_create( [
'http' => [ 'header' => "User-Agent: WordPress/6.8\r\n" ],
] );
$image_data = file_get_contents( $image_url, false, $context );
Or set a default for the whole script before the download runs:
ini_set( 'user_agent', 'WordPress/6.8' );
3. If you cannot change the plugin’s code
We can set a default User-Agent at the PHP level for your hosting account, so that every PHP download that would otherwise send an empty header sends a valid one instead. The plugin then works unmodified, and the setting survives plugin updates.
Cleaning up the empty files
Existing 0-byte attachments are not repaired by the fix, they simply stop being created. Delete the broken entries from the media library, or remove the affected products, then run the import again so the images are fetched properly.
To find them over SSH:
find ~/public_html/wp-content/uploads -type f -size 0 -name '*.jpg' -o -type f -size 0 -name '*.png'
Review the list before deleting anything, since some plugins legitimately ship empty placeholder files.