🌩️ The Definitive Guide to Using S3 Uploads on Bitnami Lightsail (No ACL Errors, No 0-Byte Files)

The Definitive Guide to Using S3 Uploads on Bitnami Lightsail

If you’re running WordPress on Amazon Lightsail using the Bitnami stack, and you want to offload your media library to Amazon S3 using the excellent Human Made S3 Uploads plugin, you’ve probably hit at least one of these:

  • ❌ “There has been a critical error on this website”
  • AccessControlListNotSupported: The bucket does not allow ACLs
  • ❌ 0-byte files in your S3 bucket
  • ❌ Confusion about /opt/bitnami/wordpress vs /bitnami/wordpress
  • ❌ WP-CLI not seeing the plugin

This guide walks you through a clean, modern, and battle-tested setup:

  • 📦 Plugin installed via Composer
  • ☁️ Media stored in S3 (with Bucket Owner Enforced, no ACLs)
  • 🧰 Full support for WP-CLI commands like wp s3-uploads verify
  • 💯 No critical errors. No 0-byte uploads. No hacks.

0. 🧱 Prerequisites

You should already have:

  • A Lightsail instance running the Bitnami WordPress image. If not read host wordpress on AWS Lightsail first.
  • SSH access to the instance as the bitnami user
  • An S3 bucket, e.g. nocodeaws-media, with:
    • Object Ownership: Bucket owner enforced (no ACLs)
  • A way for your instance to access S3:
    • Either IAM user with Access Key + Secret
    • Or an instance role (more advanced; we’ll use IAM user with Access keys here for simplicity)

1. 🗂️ Understand Bitnami’s Directory Layout (Very Important)

Bitnami doesn’t store everything where a typical Linux LAMP stack does.

Two paths matter:

/opt/bitnami/wordpress   # Application code (mostly managed by Bitnami)
/bitnami/wordpress       # Writable WordPress root (this is where YOU work)

Why this matters

  • WordPress core, plugins, wp-content, and the real wp-config.php live under /bitnami/wordpress.
  • If you install Composer or plugins in the wrong directory (/opt/bitnami/...), WordPress and WP-CLI may not see them correctly.
  • In this guide, whenever we say “WordPress root”, we mean:
cd /bitnami/wordpress

Keep that in mind — it will save you from a lot of weirdness.


2. 📥 Install Composer on the Bitnami Stack

We’ll install Composer globally, but in a Bitnami-safe way, so you can use the composer command anywhere.

SSH into your instance:

ssh -i your-key.pem bitnami@your-lightsail-ip

Then:

cd /bitnami/wordpress

# 1) Download Composer installer
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"

# 2) Build composer.phar in the current directory
php composer-setup.php

# 3) Move it to /usr/local/bin as 'composer' (requires sudo)
sudo mv composer.phar /usr/local/bin/composer

# 4) Remove the installer
php -r "unlink('composer-setup.php');"

# 5) Verify
composer --version

Why we do it this way

  • We don’t use apt install composer because that often installs Composer wired to the system PHP, not Bitnami’s PHP.
  • By building Composer and moving it ourselves, we:
    • Control which PHP it uses
    • Keep it compatible with Bitnami’s PHP stack
    • Avoid version mismatches

3. 📦 Install S3 Uploads and AWS SDK via Composer

Now that Composer is installed, we’ll use it to install the Human Made S3 Uploads plugin and the AWS SDK for PHP.

From WordPress root:

cd /bitnami/wordpress

composer require humanmade/s3-uploads aws/aws-sdk-php

If you see something like this – Do you trust “composer/installers” to execute code and wish to enable it now? enter Y

This will:

  • Download the S3 Uploads plugin
  • Download the AWS SDK, required to connect to S3 bucket
  • Update composer.json and composer.lock
  • Create a vendor/ directory

You should now have:

/bitnami/wordpress/vendor/autoload.php
/bitnami/wordpress/vendor/aws/aws-sdk-php/...
and a few more folders in /bitnami/wordpress/vendor folder

And because S3 Uploads uses Composer’s installer integration, you should also see the plugin in:

/bitnami/wordpress/wp-content/plugins/s3-uploads/

4. 🔌 Activate the S3 Uploads Plugin

Bitnami usually comes with WP-CLI.

Check:

cd /bitnami/wordpress
wp --info

If that works, activate the plugin:

sudo wp plugin activate s3-uploads

Or activate via the WordPress plugins page.


5. ☁️ Configure Your S3 Bucket and IAM

This is the most important step, and the #1 cause of upload failures, 0-byte files, or ACL errors.

5.1 Create an S3 Bucket (Step-by-step)

  1. Go to AWS Console → S3
  2. Click Create bucket
  3. Choose:
    • Bucket name: nocodeaws-media (or your preferred)
    • AWS Region: closest to your Lightsail instance (e.g. us-east-1)
  4. Object Ownership → ACLs disabled (Bucket owner enforced)
    ✔️ Select Bucket owner enforced
    This is crucial, because S3 Uploads must operate without ACLs.
  5. Block Public Access settings:
  • For now, we will turn OFF “Block all public access”. This is not the recommended way forward but for now let it be. When this setting is OFF, any one on the internet, including your website, can access the files stored in the bucket. On one hand, this is good as our website needs to access these images but on the other hand, people can use this s3 file link on their websites (hotlink your images) and you’ll end up paying AWS for their usage. Once this setup is done, we will use Cloudfront and create an Origin Access Control (OAC) to resolve this issue. Then, your S3 bucket becomes private and only CloudFront is allowed to read the bucket.
  1. Leave remaining settings default.
  2. Create bucket.

5.2 Setting Bucket Policy

Essentially, this bucket policy allows to read files stored in your bucket.

Go to the bucket → Permissions → Bucket policy → Paste (Make sure to change arn:… :

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": "*",
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::nocodeaws-media/*"
  }]
}

5.3 Create an IAM User for WordPress

  1. Go to IAM → Users → Create user
  2. Choose name:
    wordpress-s3-uploader
  3. Click Next
  4. Under Permissions → Attach policies directly
    Search for:
    • AmazonS3FullAccess (works but broad)
      Or safer custom policy (recommended):
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::nocodeaws-media/*"
    },
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::nocodeaws-media"
    }
  ]
}
  1. Create user.
  2. Go to Security Credentials
    Click Create Access Key
  3. Choose “Other” as the use case.
  4. Note and/or download (we will need this later):
    • Access Key ID
    • Secret Access Key

Why this matters

  • S3 Uploads needs Put, Get, Delete, List permissions for your S3 bucket.
  • Bucket Owner Enforced requires uploads without ACLs.
  • IAM user access keys allow the plugin to authenticate efficiently.

6. 🔧 Wire S3 Uploads into WordPress (wp-config.php)

Open wp-config.php:

sudo nano /bitnami/wordpress/wp-config.php

If you scroll down, you will see this:

require_once ABSPATH . 'wp-settings.php';

Add below code before this line – require_once ABSPATH . ‘wp-settings.php’;

/**
 * Load Composer autoloader
 */
require_once __DIR__ . '/vendor/autoload.php';

// Enable the plugin automatically
define( 'S3_UPLOADS_AUTOENABLE', true );

/** S3 Uploads (Human Made) config **/
define( 'S3_UPLOADS_BUCKET', 'nocodeaws-media' );        // your bucket
define( 'S3_UPLOADS_REGION', 'us-east-1' );              // your region
define( 'S3_UPLOADS_KEY', 'AKIAxxxxxxxxxxxxxxxx' );   // your Access Key ID that you downloaded in step 5.3
define( 'S3_UPLOADS_SECRET', 'xxxxxxxxxxxxxxxxxxxxxxxx' ); // your Secret Access Key that you downloaded in step 5.

// New S3 buckets block ACLs. Disable ACLs or you'll get "AccessControlListNotSupported" error.
define( 'S3_UPLOADS_DISABLE_ACL', true );

// Do not store a local copy of files
define( 'S3_UPLOADS_USE_LOCAL', false );


// Optional: if/when you add CloudFront, set its URL here to serve media via CDN:
# define( 'S3_UPLOADS_BUCKET_URL', 'https://dXXXX.cloudfront.net' );

Make sure to use update above code to use your bucket name, correct aws region and Access keys

# define( ‘S3_UPLOADS_BUCKET_URL’, ‘https://dXXXX.cloudfront.net’ ); – Dont change anything here. Later, when we configure Cloudfront, we will change this url again.

Save (Ctrl+O, Enter) and exit (Ctrl+X).


7. 🛠️ Add a Small MU-Plugin to Strip ACLs (Critical)

7.1 Make sure MU-plugins folder exists

sudo mkdir -p /bitnami/wordpress/wp-content/mu-plugins

7.2 Create the ACL-stripping helper

Now, create a simple MU Plugin (Must Use Plugin), named zzz-s3-no-acl to act as an ACL filter. This prevents AWS errors even if S3 Uploads plugin tries to send ACLs. It, essentially, filters out any ACL parameters from any requests sent to the bucket.

sudo tee /bitnami/wordpress/wp-content/mu-plugins/zzz-s3-no-acl.php > /dev/null <<'PHP'
<?php
add_filter( 's3_uploads_putObject_params', function( $params ) {
    if ( isset( $params['ACL'] ) ) {
        unset( $params['ACL'] );
    }
    return $params;
}, 9999 );
PHP

sudo chown bitnami:daemon /bitnami/wordpress/wp-content/mu-plugins/zzz-s3-no-acl.php
sudo chmod 644 /bitnami/wordpress/wp-content/mu-plugins/zzz-s3-no-acl.php

By default, the S3 Uploads plugin tries to send ACL parameters to your bucket, but your S3 bucket is ACL-disabled (the modern default for newly created S3 buckets). Thus, this request is rejected by the bucket and you get this error – AccessControlListNotSupported.The bucket does not allow ACLs.

So, to mitigate this, we need to make sure that the plugin does not send any ACL requests and that’s exactly what zzz-s3-no-acl does.


8. 🔁 Restart the Bitnami Stack

sudo /opt/bitnami/ctlscript.sh restart

9. 🧪 Verify the Setup

9.1 Check ACL filter

cd /bitnami/wordpress
sudo wp --skip-packages eval 'echo (has_filter("s3_uploads_putObject_params") ? "filter_present\n" : "no_filter\n");'

Expected:

filter_present

9.2 Run S3 Uploads verifier

sudo wp s3-uploads verify

Should show:

File uploaded to S3 successfully.
File deleted from S3 successfully.
Success: Looks like your configuration is correct.

9.3 Upload an image through WordPress 🎉

  • Go to Media → Add New
  • Upload a file
  • Check S3 bucket → file appears with correct size
  • Media Library shows thumbnails

10. 🎯 What We’ve Achieved

You now have a properly integrated S3 Uploads setup on Bitnami Lightsail:

  • ✔️ Composer installed safely
  • ✔️ AWS SDK + S3 Uploads installed via Composer
  • ✔️ Plugin activated normally
  • ✔️ ACL errors eliminated
  • ✔️ 0-byte uploads fixed
  • ✔️ WP-CLI verification fully working
  • ✔️ WordPress → S3 media offload seamless

This solution is:

  • 🚀 Modern
  • 🔐 Secure
  • 🧼 Clean
  • 📦 Dependency-managed
  • 💯 Production-ready

In the next tutorial we will add cloudfront CDN distribution to resolve the hotlinking issue, making the framework stronger and make the images load faster.

CloudFront + OAC follow-up tutorial

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top