Offloading WordPress media means a plugin copies every file uploaded to the Media Library into an S3 bucket and rewrites the image URLs in your pages to point at the bucket instead of wp-content/uploads. It keeps the web server's disk small, lets several web servers share one media library, and survives rebuilding the server. It also adds a plugin you depend on forever, changes how backups work, and - the part most guides skip - only works if visitors' browsers can read the objects, which depends on what your storage allows. For a custom S3-compatible endpoint, the plugins that work cleanly are Human Made's S3 Uploads (configured in code) and Advanced Media Offloader (configured in settings, with a generic S3-compatible option); the popular WP Offload Media officially targets named providers and reaches custom endpoints only through filters. This post covers the setup, the URL and access questions, migrating an existing library, and an honest answer to whether you need any of it.
What offloading actually changes#
Without offload, an upload goes to wp-content/uploads/2026/10/photo.jpg on the web server, WordPress generates the resized copies (photo-300x200.jpg, photo-1024x683.jpg and so on) next to it, and the post content contains a URL to the server itself.
With an offload plugin, the upload still lands on the web server first - WordPress needs the file locally to generate thumbnails and read metadata. The plugin then copies the original and every resized version to the bucket, records where each one went, and filters every place WordPress produces a media URL so the page points to storage. Some plugins delete the local copies afterwards to save disk; others keep them.
Two consequences follow. Images are now fetched by visitors directly from the storage endpoint, so that endpoint must answer anonymous requests for those objects. And the files are now outside the web server, so a backup of the web server no longer contains your media.
When it is worth it, and when it is not#
Offloading solves specific problems. If you do not have one of them, it adds moving parts for nothing.
Worth it when:
- The media library is large relative to the web plan's disk - a photography site or a shop with thousands of product images, where uploads are most of the disk usage.
- More than one web server serves the site, so every server needs the same files without syncing folders between them.
- The web server is disposable - you rebuild it from a repository and want nothing on it that cannot be recreated.
- Downloads are large - podcasts, video, software - and you want them out of the web server's bandwidth and disk.
Not worth it when:
- It is a normal blog or company site with a few gigabytes of images. Web hosting plans have enough disk for that, and the web server serves static files very efficiently.
- You hope it will make the site faster on its own. Moving images to a bucket in the same location as the web server does not shorten the distance to visitors. Speed comes from caching headers, image sizes and formats, and a CDN if your audience is far away. WordPress speed and caching covers what actually helps.
- You rely on plugins that expect local files - some image editors, PDF thumbnail generators, security scanners and backup plugins assume media is in
wp-content/uploads.
There is also a middle path that people overlook: keep media where it is, and copy wp-content/uploads to a bucket every night as a backup. No plugin, no URL changes, and the bucket still saves you if the server is lost. The rclone approach in game server backups to S3 works the same way for a WordPress uploads folder.
The public-access question#
This is the part to settle before installing anything. An offloaded image is loaded by a visitor's browser with a plain GET and no credentials. For that to work, the storage must serve those objects anonymously. On AWS that is done with a bucket policy or object ACLs set to public-read. S3-compatible services vary in which of those mechanisms they support, and not every storage product offers public buckets at all.
RE:NODE's S3 storage, for example, is sold as private storage with an access key and secret generated for your server; public buckets and bucket policies are not part of what it promises. So test before you build on it. Upload a test object the way your plugin will, then fetch it without credentials:
$ aws s3 cp test.jpg s3://media/test.jpg --acl public-read --profile store$ curl -I https://s3.example.com/media/test.jpgA 200 with Content-Type: image/jpeg means anonymous reads work and a public offload setup will too. A 403 AccessDenied means objects are private, and you have three honest options:
- Signed URLs. Some plugins can serve media through presigned URLs that expire. S3 Uploads does this for attachments marked private. It works, but every page render produces different image URLs, which defeats page caching and browser caching - fine for a members-only area, poor for a public site. Presigned URLs explains the mechanics and their limits.
- Serve media through a server you control that holds the keys and fetches from the bucket - a reverse proxy or a small app that signs requests. This is real work and adds a hop; it makes sense mainly when you need access control anyway.
- Use the bucket as the backup copy, not the serving copy. Keep media on the web server and sync it to the bucket nightly. Simple, robust, and no URL rewriting.
For most WordPress sites on private storage, the third option is the right one.
The plugins and their custom-endpoint support#
Three plugins come up in every discussion. They differ mostly in how they handle a non-AWS endpoint.
| Plugin | Configured in | Custom S3 endpoint | Path-style | Existing media |
|---|---|---|---|---|
| S3 Uploads (Human Made) | wp-config.php and a filter | Yes, via s3_uploads_s3_client_params | Yes, use_path_style_endpoint | WP-CLI command |
| Advanced Media Offloader | Constants or settings page | Yes, generic S3-compatible option | Yes, a constant | Bulk tool and WP-CLI |
| WP Offload Media (Lite/Pro) | Settings page or AS3CF_SETTINGS | Not officially; via filters | Via the same filters | Pro bulk tool |
S3 Uploads is a developer's plugin: no settings screen, everything in code, installed from GitHub or Composer rather than the plugin directory. It is the most transparent about what it does, which makes it the easiest to debug. Advanced Media Offloader is in the WordPress plugin directory, has a generic S3-compatible provider option with an endpoint, a domain and a path-style switch, and can remove local copies after upload ("Full Cloud Migration") or keep them as a fallback. WP Offload Media is the best-known name; its Lite version officially supports Amazon S3, DigitalOcean Spaces and Google Cloud Storage, and the developer's own position on other S3-compatible services is that they probably work with filters but are not tested. If you already use it for AWS, it is fine; for a custom endpoint, one of the other two is less friction.
Setting up S3 Uploads with a custom endpoint#
Install the plugin per its README (Composer: composer require humanmade/s3-uploads, or download a release into wp-content/plugins), then add the constants to wp-config.php, above the "That's all, stop editing" line:
define( 'S3_UPLOADS_BUCKET', 'media/wp' ); // bucket, optional prefixdefine( 'S3_UPLOADS_REGION', 'us-east-1' ); // any value the store acceptsdefine( 'S3_UPLOADS_KEY', getenv( 'S3_KEY' ) );define( 'S3_UPLOADS_SECRET', getenv( 'S3_SECRET' ) );define( 'S3_UPLOADS_BUCKET_URL', 'https://s3.example.com/media/wp' );define( 'S3_UPLOADS_HTTP_CACHE_CONTROL', 'public, max-age=31536000' );The endpoint and path-style switch go in a small must-use plugin, because they are SDK client options rather than plugin settings:
<?phpadd_filter( 's3_uploads_s3_client_params', function ( $params ) { $params['endpoint'] = 'https://s3.example.com'; $params['use_path_style_endpoint'] = true; $params['request_checksum_calculation'] = 'when_required'; $params['response_checksum_validation'] = 'when_required'; return $params;} );The two checksum lines are for recent versions of the AWS SDK for PHP (3.337 and later), which add integrity checksums that some S3-compatible servers reject; the plugin's own README recommends them for third-party endpoints. S3_UPLOADS_BUCKET_URL sets the base URL written into pages - with path-style that is the endpoint plus the bucket and prefix. Keep the key and secret out of the file itself if you can, as the getenv() calls above do; environment variables and secrets covers why.
S3 Uploads sets public-read on objects by default (S3_UPLOADS_OBJECT_ACL). On storage that does not honour ACLs, that setting has no effect, which brings you back to the public-access test above.
Verify the setup with WP-CLI before relying on it: wp s3-uploads verify checks that the plugin can write to and delete from the bucket.
Migrating an existing library#
New uploads are offloaded automatically. Everything uploaded before the plugin was enabled is not, and those posts still point at wp-content/uploads. Migration has two halves: copy the files, and change the URLs.
- Back up first. Database and
wp-content/uploads. The URL change is a search-and-replace across your content. - Copy the files. With S3 Uploads:
wp s3-uploads upload-directory wp-content/uploads uploads. With Advanced Media Offloader: its bulk tool, orwp advmo offloadfor large libraries. - Rewrite URLs in content. Offload plugins filter URLs that WordPress generates, but URLs pasted into post content are plain text. A WP-CLI search-replace handles them:
wp search-replace 'https://example.com/wp-content/uploads' 'https://s3.example.com/media/wp/uploads' --all-tables --dry-run, then again without--dry-run. Check that the target path matches where your plugin actually stores files. - Check pages. Open a few old posts and a product gallery, and look for images still loading from the old path in the browser's network panel.
- Only then remove local copies, if you want the disk back. Keep them until you are sure.
Reversing a migration is the same process the other way round, and it is the strongest argument for keeping local copies: a site whose only media copy is in the bucket is locked to that plugin and that storage.
Backups after offloading#
This is the change people discover during a restore. Your web server's backups - panel backups, a backup plugin, a tar of the site - now contain the database and the code, but not the media. The bucket is the only home of any file whose local copy was removed.
On RE:NODE, the S3 storage keeps one copy on NVMe in one location, without replication, and storage plans carry no panel backup slots of their own. That is fine for a serving copy, as long as something else holds a second one. Two ways to keep it:
- Keep local copies on the web server, so the existing web-server backups still include media.
- Sync the bucket elsewhere, for example
rclone sync store:media/wp /backups/wp-mediaon a machine you control, or to a second provider, on a schedule.
Also back up the plugin's settings and, for plugins that track offloaded items in their own database table, the database - restoring the files without that table leaves the plugin not knowing where anything is. Migrate WordPress to a new host is worth reading with this in mind; an offloaded site has one more piece to move.
Common problems#
Images 403 after offload. Objects are private and the storage does not serve anonymous reads. See the public-access section; either use signed URLs or keep media local.
New uploads fail with a signature or checksum error. The SDK's newer default checksums; add the two when_required options.
Some images still load from `wp-content/uploads`. Hard-coded URLs in content or in a page builder's data. Search-replace, and for page builders that store serialised data, use wp search-replace, which handles serialisation correctly - a plain SQL REPLACE() breaks it.
Thumbnails missing in the bucket. Sizes generated by a theme or plugin after the original upload are not always offloaded. Regenerate thumbnails, then re-run the plugin's offload for those items.
The site got slower. Image requests now go to a second hostname, with its own connection and TLS handshake. Long Cache-Control headers help repeat visits; HTTP caching headers covers the values. For distant visitors, a CDN in front of the media hostname is the real fix.
FAQ#
Does offloading media make WordPress faster?
Not by itself. The web server serves static images efficiently already, and a bucket in the same location is the same distance from visitors. Offloading saves disk and helps multi-server setups; speed comes from caching, image optimisation and a CDN.
Which plugin works with an S3-compatible endpoint?
S3 Uploads, through a filter that sets the endpoint and path-style, and Advanced Media Offloader, through its generic S3-compatible option. WP Offload Media can be made to work with filters but does not officially support custom endpoints.
Do I have to delete local copies?
No, and keeping them is safer. The local copies keep your web-server backups complete and make it possible to switch the plugin off without losing images. Remove them only if disk space is the reason you offloaded.
Can I offload only some files?
Most plugins offload everything in the Media Library. If you only want large downloads in the bucket, upload those directly with an S3 client and link them, and leave the library alone.
What happens if I deactivate the plugin?
URLs revert to wp-content/uploads. If the local files are still there, the site keeps working; if they were deleted, images break until you copy them back from the bucket and rewrite any URLs that were changed in content.




Comments
Completely anonymous: no account, no email, no cookie. We store the name you type, the text and the time - nothing else. Links are limited and markup is not rendered.