# The Minimum IAM Policy for WordPress Media on S3

The minimum IAM policy for WordPress media on S3 allows `s3:ListBucket` on the bucket, and `s3:PutObject`, `s3:GetObject`, `s3:DeleteObject` and `s3:AbortMultipartUpload` on its files. That's all an offload plugin needs to upload, check, replace and remove media. Limit it to one bucket, and attach it to a user made for this site.

## What's the policy?

Replace `my-site-media` with your bucket's name.

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "OffloftListBucket",
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::my-site-media"]
    },
    {
      "Sid": "OffloftManageFiles",
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:AbortMultipartUpload"],
      "Resource": ["arn:aws:s3:::my-site-media/*"]
    }
  ]
}
```

## What does each permission do?

| Permission | Why it's needed |
|---|---|
| `s3:ListBucket` | Finds the bucket, and tells a missing file from a forbidden one. |
| `s3:PutObject` | Uploads files. |
| `s3:GetObject` | Reads a file back to check it. |
| `s3:DeleteObject` | Removes a file you delete in WordPress, and the test file. |
| `s3:AbortMultipartUpload` | Cleans up a large upload that didn't finish. |

## Why are there two resources?

`s3:ListBucket` acts on the bucket, so its resource has no `/*`. The others act on files, so theirs does. Mixing these up is the most common reason a correct-looking policy returns Access Denied.

## What about public access?

This policy is about what your site can do. Whether visitors can read files is separate: a bucket policy allows `s3:GetObject` for everyone, on the files only.

Keep **Object Ownership** on **Bucket owner enforced**. Then you don't need `s3:PutObjectAcl`.

## How do you apply it?

1. In IAM, go to **Policies → Create policy**, choose **JSON** and paste it.
2. Go to **Users → Create user**, choose **Attach policies directly** and pick your policy.
3. On the user, open **Security credentials** and create an access key. AWS shows the secret only once.

Never use root keys for a site. `AmazonS3FullAccess` reaches every bucket in your account, so a compromised site could reach them all.

## How do you test it?

Offloft's [connection test](/docs/connection-test) runs through credentials, bucket, upload, read back and delete, and stops at the first failure.

## Key points

- WordPress media needs five actions, on one bucket and its files.
- Separate the bucket ARN from the files ARN, because the actions apply to different resources.
- You don't need s3:PutObjectAcl if the bucket uses Bucket owner enforced.
- Never use root keys, or AmazonS3FullAccess, for a running site.

Updated 2026-11-06.

Source: https://www.offloft.com/guides/wordpress-s3-iam-policy
