If a WordPress website’s media library grows to several tens of gigabytes, you can move the uploads to an object storage service (Amazon S3, Cloudflare R2, Backblaze B2). The website will then serve the images from the storage service using its own domain (for example, media.pelda.hu), whilst freeing up space on the hosting account. An offloading plugin (WP Offload Media or Media Cloud) handles the uploads and URL rewriting.
This isn’t worth it for everyone. Outsourcing means yet another service, yet another set of access credentials and yet another place to perform backup operations, as well as a plugin that needs to work after every WordPress and PHP update. If your media library fits comfortably within your storage space and you simply want to speed up the delivery of images, a CDN is simpler: it delivers images from a server close to the visitor in exactly the same way, without any moving parts (speeding up your website with a CDN).
How does outsourcing work?
The plugin performs two separate tasks, and it is the confusion between these that causes most migration failures:
- It copies files upon upload. When WordPress generates the image and its thumbnails in the
wp-content/uploadsfolder, the plugin uploads them all to the storage. A setting determines whether the local files are retained. Only deletion frees up space, but only retention provides a way back. - When displayed, it rewrites the URL. The URLs of images are changed to point to the hosting provider’s (or your own domain’s) address. The database is not modified in this process, so you can revert to the original state by disabling the plugin. However, website builders often include the full URLs of images within the content; these are not rewritten.
A single uploaded photo can result in a dozen files, including the thumbnails. If the thumbnails are missing from the copy, the large image will load, but on smaller screens (and high-resolution displays) the page will appear without an image.
Which hosting plan should you choose?
All three use the same S3-compatible interface, and the plugins handle them in the same way. The difference lies in how traffic is charged and how the service is delivered:
| Amazon S3 | Cloudflare R2 | Backblaze B2 | |
|---|---|---|---|
| Outgoing traffic | paid (discounted via CloudFront) | There are no traffic charges; operations are charged on a per-transaction basis | Free up to three times the stored volume; unlimited to partner CDNs (e.g. Cloudflare) |
| Public service | CloudFront or the storage URL | Only behind your own domain or a Worker; the r2.dev address is for development purposes only |
With CDN enabled |
| Strengths | the widest range of plugin and tool support, lifecycle policies, version management | Traffic is free of charge | the cheapest storage |
We have deliberately chosen not to list specific prices: these vary, and the cost depends on the ratio of traffic to storage. Assess your media library (see below) and use the provider’s own calculator to work out the cost.
1. Check the media library
In the cPanel Terminal interface (Advanced group) or via SSH:
cd ~/public_html
du -sh wp-content/uploads
find wp-content/uploads -type f | wc -l
wp post list --post_type=attachment --format=count
The number of files is typically ten to fifteen times the number of attachments, due to thumbnails. If there are significantly more than this, it is generating unnecessary file sizes; it is worth sorting this out before the migration. The largest files are also shown in the cPanel Disk Usage interface (Check Disk Usage).
2. Repository and restricted-access key
Create a bucket with the provider and an API key that grants read and write access to this bucket only. Do not use a key for the entire account. Also make a note of the S3-compatible endpoint address (for R2: https://<fiókazonosító>.r2.cloudflarestorage.com; for B2: https://s3.<régió>.backblazeb2.com).
3. Your own domain for your hosting account
Before making any changes, link the storage to your own subdomain (for example, media.pelda.hu). This way, you can switch providers later without having to change the address. For R2, you can add it in the ‘Custom Domains’ section on the storage’s ‘Settings’ page if the domain is hosted on Cloudflare; for S3, use CloudFront; and for B2, use a CDN in front of it. Checking with a pre-uploaded test image:
curl -sI https://media.pelda.hu/wp-content/uploads/2026/10/proba.jpg | head -5
You should see a 200 response and a content-type: image/jpeg header. A 403 response means that the domain is assigned but public access is not enabled; with R2, this is the most common reason why it ‘doesn’t work’.
4. Copying existing files
For smaller media libraries (a few GB), the plugin’s own tool handles this: both WP Offload Media and Media Cloud can upload existing attachments whilst simultaneously populating their own databases.
For large media libraries, it is quicker and more reliable to copy using the rclone program, which resumes after an interruption and performs a checksum comparison. rclone is not installed on aWh’s hosting accounts, but by running it on your own machine, you can copy files directly from the hosting account to the storage via SFTP (on cPanel hosting accounts, using an SSH key on port 4231). Set up two connections using the rclone config command: one of type sftp for the hosting account (tarhely) and one of type s3 for the cloud storage (tarolo), then:
rclone copy tarhely:public_html/wp-content/uploads tarolo:sajat-tarolo/wp-content/uploads --transfers 8 --progress
rclone check tarhely:public_html/wp-content/uploads tarolo:sajat-tarolo/wp-content/uploads --one-way
At the end of the check, it should show 0 differences found. If an error is reported, run the copy command again: it will only copy the missing files. After copying, run the plugin’s own tool as well, so that the plugin knows which attachments are already in the repository; without this, it will continue to serve the older images from the storage space.
5. Configuring the extension
Do not store the access key in the database (as it will be included in every backup and every staging copy), but in the wp-config.php file. WP Offload Media, for example, reads the AS3CF_SETTINGS constant; the exact identifier for the provider is specified in the plugin’s documentation:
define( 'AS3CF_SETTINGS', serialize( array(
'provider' => 'SZOLGALTATO',
'access-key-id' => 'KULCS_AZONOSITO',
'secret-access-key' => 'TITKOS_KULCS',
) ) );
In the plugin interface, enter the directory, the path prefix (wp-content/uploads, as copied), enable serving via your own domain, and leave the option to delete local files disabled. Check:
wp eval 'echo wp_get_attachment_url( 123 ), PHP_EOL;'
wp eval 'echo wp_get_attachment_image_srcset( 123, "large" ), PHP_EOL;'
Replace 123 with a valid attachment ID (wp post list --post_type=attachment --field=ID --posts_per_page=1). Both commands should output the address https://media.pelda.hu/….
6. Last synchronisation
Whilst the copy was running, new images may have been added to the website. Run the copy and check commands from step 4 again, then run the plugin tool. The second run will only move a few files.
Verification
- Upload a new image via the admin interface and check that its URL points to your own domain and that the file has appeared in the storage area.
- Request all image sizes for an old post (all
srcsetURLs): each one should return200. A403or404error indicates an incomplete copy. - If the website builder has entered the full URLs of the images directly into the content, you can rewrite them using the wp search-replace command (
https://pelda.hu/wp-content/uploads→https://media.pelda.hu/wp-content/uploads), always starting with a test run.
Only enable the deletion of local files weeks later, following a thorough check – if at all. From that point onwards, the storage is the only copy: the website backup no longer contains the media library, so you will need to make a separate backup from the storage.
Common errors
Mixed content warning. If the image URLs http:// are not secure, your browser will block them on an HTTPS page. Enable HTTPS enforcement in the extension and check that your domain’s certificate is valid (mixed content).
Fonts are not loading. The browser applies CORS rules to web fonts (and some other file types): it will only load them from a different domain if the server sends an Access-Control-Allow-Origin header. This does not apply to plain images, which is why the images work whilst the font is silently replaced. Configure a CORS rule on the server for your website’s address, for GET and HEAD requests.
Local files deleted too early. If the delete option was enabled during an incomplete copy, some files will be missing. Restore the wp-content/uploads folder from the daily backup (restore from JetBackup), copy it back to the storage, and regenerate the missing thumbnails: wp media regenerate --only-missing.