How to store WordPress media files to Amazon S3

There are a lot of plugins available that help to offload WordPress files to cloud storage(Amazon S3) but here I am going to summarize my experiences with the S3 Uploads by HumanMade WordPress plugin and what I love the most about this plugin.

S3 Uploads – this plugin does not provide any user interface, it’s meant to be used by developers rather than users. the plugin is a GitHub-hosted project from HumanMade. This plugin is used only in case when the WP-CLI is required for the S3 stuff.

It’s a free plugin that requires little configuration and “just works”. It offers powerful features that might be available in any other free plugin, in other plugins with an add-on that may cost you approximately 40 to 340 dollars per year.
It allows you to migrate your existing and new files to Amazon S3, you can also use CDN. These features are missing in free plugins like WP Offload Media Lite.

WP Offload Media Lite

If you are setting up a new site and not going to use a CDN then the free version has a simple UI(admin options) and is easy to configure –  it allows you to upload only newly uploaded files, and with the pro version, you’ll also be able to store and serve your assets from CDN and upload existing files.  Read more – Amazon’s WP Offload Media Lite WP plugin documentation

Here’s how your WordPress site is going to function at the end of this blog:

  • You upload files to your WordPress Media Library like normal (i.e. through the regular Add Media button)
  • S3 Uploads plugin automatically copies or moves those files to Amazon S3
  • Your WordPress site either serves the file stored on S3 or optionally uses a CDN connected to your S3 bucket.

I’m going to break everything down step-by-step and use many screenshots and blogs link to make this blog simple and understandable.

Connect WordPress to Amazon S3 Bucket

Step 1.  Create a new AWS S3 Bucket in one of the AWS Regions

The bucket is like a directory/folder to store media objects(photos, videos, documents, etc.)

If you do not have an existing bucket for your site, create one. Login into the AWS console using a root or an IAM user account and navigate to – Services > Amazon S3 >  Create bucket, or click here to go straight to S3.

1.1 Choose a globally unique and appropriate bucket name
After you create a bucket, you cannot change its name because the bucket name is visible in the URL that points to the objects stored in the bucket. Read more about bucket naming rules – Link

1.2 Select a region close to your targeted user.

1.3 Edit S3 Block Public Access settings
To make your bucket publicly readable, you must disable block public access settings for the bucket and write a bucket policy that grants public read access.

Clear Block all public access, leave the other default option and click Create Bucket.

Step 2. Set a bucket policy to a bucket  

You can add a bucket policy to grant public read access to your bucket. When you grant public read access, anyone on the internet can access your bucket.

In the screenshot above, you can see that the regurwpdev bucket is already public, while the regurwp bucket that I created for this tutorial is marked as not public. After this step, you want that Public indicator for your bucket as well.

On the next screen, select your bucket and click on the Permissions tab and then select Bucket Policy:

You will see a JSON code editor. Paste in the below snippet and save your changes.

{
	"Version": "2008-10-17",
	"Statement": [
	{
		"Sid": "AllowPublicRead",
		"Effect": "Allow",
		"Principal": {
			"AWS": "*"
		},
		"Action": "s3:GetObject",
		"Resource": "arn:aws:s3:::regurwp/*"
	}
	]
}

Make sure to replace regurwp with the actual name of your bucket Choose Save changes.

Once you save your changes, you should see your bucket marked as Public in the Amazon S3 dashboard.

Read more about Website Access Permissions – Link

Step 3. Create an IAM user and add a policy 

we need to create a new IAM user for the plugin,  the user account will use API to manage (automatically add and remove) objects from the S3 bucket. Once we finish this step will get AWS keys  

If you have an IAM user account make sure that you have permission to create add New AMI accounts Or you can ask for the key and secret to your AWS admin

Sign in to the AWS Management Console and open the IAM console at – Link.

Select the user tab and click on create user button

In the Add IAM user console, you need to give your user a name. Then, make sure to select Programmatic access under Access type. Once you’ve done that, click – Next: Permissions.

On the next page, select the Attach existing policies directly tab and select Create policy.

That will launch a new window. In that new window, go to the JSON tab and paste the following code snippet :

{
	"Version": "2012-10-17",
	"Statement": [
	{
		"Effect": "Allow",
		"Action": [
			"s3:CreateBucket",
			"s3:DeleteObject",
			"s3:Put*",
			"s3:Get*",
			"s3:List*"
		],
		"Resource": [
			"arn:aws:s3:::regurwp",
			"arn:aws:s3:::regurwp/*"
		]
	}
	]
}

Make sure to replace the two instances of regurwp with the actual name of your Amazon S3 bucket.

Then, click Review policy at the bottom:

On the next screen, give it a name and then click Create policy:

Now:

  1. Go back to the IAM Add User tab from before
  2. Click Refresh Icon(1)
  3. Select the policy that you just created from the list (you can search for it by name to save time)(2)
  4. Click Next: Review(3,4)

On the next screen, click Create user:

On the next screen, you should see:
Access key ID and Secret access key (you’ll need to click Show to actually see the secret key)

Keep both these values handy because you’re going to need them in the next step. You’re officially done with the AWS interface.

Step 4. Install the s3-uploads plugin and configure the settings

SSH(Secure Shell) is a network protocol that allows secure access over an encrypted connection, you can easily manage your files and folders, modify their permissions, edit files directly on the server, configure and install your scripts, etc.
SSH into your servers command line and navigate to the root directory (“public_html” in my case)

We’ll be using composer to download the plugin and its dependencies.
You can test whether the composer is already installed or not by running the composer command on the terminal.

composer

if you have already installed you’ll see output displaying the composer’s version and arguments. otherwise, you will get a Command ‘composer’ not found message. You can read more about the installation guide – link

4.1 install the plugin dependencies using composer

composer require humanmade/s3-uploads

The plugin required WP-CLI if it’s not already installed you can install it by following the steps read the official WP-CLI  installation guide – Read more
You can test if  WP-CLI is already installed or not by the below command

wp cli info

It will print various details about the WP-CLI environment on the terminal if the WP-CLI  is available on your server.

4.2 Via WP-CLI use the following command to activate the S3 Uploads plugin:

wp plugin activate s3-uploads

Note: If you get any error that the plugin is not found, simply change the plugin name to uppercase e.g. “S3-Uploads” and retype the command, eg.

wp plugin activate S3-Uploads

If you have installed the plugin using Composer lowercase command will work.

4.3 Edit your wp-config.php file
You can either do this via FTP, via cPanel file manager, or the command line. In your wp-config.php file, you’ll see a line that says:

/** Absolute path to the WordPress directory. */
if ( ! defined( 'ABSPATH' ) ) {
	define( 'ABSPATH', dirname( __FILE__ ) . '/' );
}

Immediately after that, put in the S3 Uploads plugin settings as follows:

/* 
*  BEGIN S3 configuration
*/
require_once __DIR__ . '/vendor/autoload.php';
 
define( 'S3_UPLOADS_BUCKET', 'your_bucket_name' );
define( 'S3_UPLOADS_REGION', ‘your_s3_region’ ); // the s3 bucket region (excluding the rest of the URL)
 
// You can set key and secret directly:
define( 'S3_UPLOADS_KEY', 'your_s3_key' );
define( 'S3_UPLOADS_SECRET', 'your_s3_secret' );
 
// Or if using IAM instance profiles, you can use the instance's credentials:
define( 'S3_UPLOADS_USE_INSTANCE_PROFILE', true );
 
/*
* END S3 configuration
*/

Make sure to replace ‘your_bucket_name,  your_s3_region, and your_s3_secrets with the actual value, here’s what my code in wp-config.php looks like for regur.net

4.4 The next thing that you should do is to verify your setup. You can do this using the verify command like so:

wp s3-uploads verify

You will get a success message – Success: Looks like your configuration is correct.

4.5 Uploading files to S3

If you have an existing media library with attachment files, use the below command to copy them all to S3 from the local disk.

wp s3-uploads upload-directory <from> <to> [--verbose]

For example, to migrate your whole uploads directory to S3, you’d run:

wp s3-uploads upload-directory /path/to/uploads/ uploads

There is also an all-purpose cp command for arbitrary copying to and from S3.

wp s3-uploads cp <from> <to>

Or you can test a single file upload to ensure it goes to the right place with something like So in my case, I typed:

wp s3-uploads cp ./test.txt s3://regurwp/wp-content/uploads/test.txt

If changes are not reflecting instantly you can deactivate the s3-plugin and activate again using the wp command

wp plugin deactivate s3-uploads
wp plugin activate s3-uploads

For some reason migrating the whole uploads directory to the S3 command was not working for me but both commands will copy existing files to the S3 bucket.
I suggest copying all files to the S3 bucket, In my case I typed

wp s3-uploads cp  ./wp-content/uploads s3://regurwp/uploads

It will copy all your existing files to s3 bucket. 
When you run a copy command on the terminal its output is generally a printed message for each file copied to S3.

Note: Before uploading, disable URL Rewrites so while copying the media objects to s3 your site images will not look broken. Once you are done with copying all files to S3, you can disable or remove them completely.
Add this end of the s3 plugin configuration block.

// disable URL rewriting altogether
define( 'S3_UPLOADS_DISABLE_REPLACE_UPLOAD_URL', true );

You can read more about plugin advance options and settings by visiting the official Github page – link

(Optional) Connect Amazon S3 Bucket to a CDN

Step 5. I suggest using a CDN if you have a global audience. It’s simple and easy to connect your Amazon S3 bucket to AWS CloudFront. 

5.1 Navigate to CloudFront console – All services > Networking & Content Delivery > CloudFront 
And click on the Create distribution button To create a CloudFront web distribution (console)

  1. Sign in to the AWS Management Console and open the CloudFront console at – link
  1. Choose Create Distribution.
  1. On the first page of the Create Distribution Wizard, in the Web section, choose Get Started.
  1. Specify settings for the distribution. 
  1. Save changes.
  1. After CloudFront creates your distribution, the value of the Status column for your distribution will change from InProgress to Deployed. If you choose to enable the distribution, it will be ready to process requests after the status switches to Deployed.

Setup default cache behavior settings  like bellow screenshot

After creating AWS CloudFront distribution it will take some time to deploy the settings.
You can see your distribution status as Enabled.

Read more about Creating Distribution – link

5.2 URL Rewrites
By default, S3 Uploads will use the canonical S3 URIs for referencing the uploads, i.e. [bucket name].s3.amazonaws.com/uploads/[file path]. If you want to use another URL to serve the images from (for instance, if you wish to use S3 as an origin for CloudFront), you should define S3_UPLOADS_BUCKET_URL in your wp-config.php:

// Define the base bucket URL (without trailing slash)
define( 'S3_UPLOADS_BUCKET_URL', 'https://your.origin.url.example/path' );

In my case

// Define the base bucket URL (without trailing slash)
define( 'S3_UPLOADS_BUCKET_URL', 'https://d37y3hjbzwjndv.cloudfront.net' );

Here’s what my code in wp-config.php looks like for regur.net

/* 
*  BEGIN S3 configuration
*/
 
require_once __DIR__ . '/vendor/autoload.php';
 
define( 'S3_UPLOADS_BUCKET', 'regurwp' );
define( 'S3_UPLOADS_REGION', 'us-east-1' ); // the s3 bucket region (excluding the rest of the URL)
 
// You can set key and secret directly:
define( 'S3_UPLOADS_KEY', 'AKIA55Q5ENVT4NTALD7J' );
define( 'S3_UPLOADS_SECRET', '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' );
 
// Or if using IAM instance profiles, you can use the instance's credentials:
define( 'S3_UPLOADS_USE_INSTANCE_PROFILE', true );
 
// Define the base bucket URL (without trailing slash)
define( 'S3_UPLOADS_BUCKET_URL', 'https://dhuy6x2sj634j.cloudfront.net' );
define( 'S3_UPLOADS_AUTOENABLE', true );
 
/*
* END S3 configuration
*/

Once you add your CloudFront URL, check your site. all media files should serve the files from the CDN.

While writing this blog I found some useful blogs and took references from these blogs. If you want to understand and read more about WordPress S3 please prefer these blogs as well.