Other S3-compatible storage
Better Messages can keep the files members send in any storage that speaks the S3 API, through the Amazon S3 or S3-compatible storage. Browsers upload files straight to the bucket, and files open from the bucket instead of from your server.
Cloud storage works in the free version and in the WebSocket version, from Better Messages 3.1.
These providers have a guide of their own, with screenshots:
- Amazon S3
- Backblaze B2
- DigitalOcean Spaces
- Wasabi
- Cloudflare R2 and Bunny.net, which have their own choices in the Storage section
This page is for every other provider, such as Scaleway, Hetzner Object Storage, OVHcloud or a server you run yourself. The steps are the same everywhere, and Providers lists what to enter for the common ones.
What the provider needs#
Better Messages uses the parts of the S3 API that every serious S3-compatible storage has: uploads, uploads in parts, reads, partial reads, deletes, listing, presigned links signed with AWS Signature Version 4, and the bucket's CORS rule. A provider whose CORS cannot be set through the API still works, see Uploads straight to the bucket.
Google Cloud Storage has an S3-like API, but its CORS rules and uploads in parts work differently, and it is not supported.
What you need#
- A bucket. Keep it private for members-only files.
- An access key, an Access Key ID and a Secret Access Key, that can read, write, delete and list the files in that bucket. Give it access to that bucket only.
- The endpoint and the region of the bucket.
Choose how files open#
| How files open | Who can open a file | What the bucket needs |
|---|---|---|
| Members of the conversation only (default) | Only members, through links Better Messages signs for each of them | A private bucket |
| Anyone with the link | Anyone who has a file's link | A bucket of its own whose files anyone can read, or a CDN in front of it |
1. Create a bucket#
Create a bucket in your provider's console and leave it private. Pick a region close to most of your members.
Use letters, numbers and dashes in the name, and no dots: dots break HTTPS on many providers, and some do not allow them at all.
2. Create an access key#
Create an access key for Better Messages alone, limited to the new bucket. It must be able to:
- upload, read and delete files, and finish or abort uploads in parts
- list the bucket. Without it, some providers answer "access denied" instead of "not found" for a deleted file, and Better Messages cannot confirm that a file is gone.
- read and set the bucket's CORS rule, so Configure CORS can set it up for you. You can leave this out and add the rule yourself, see Uploads straight to the bucket.
Many providers take a policy in the format Amazon S3 uses. This one gives a key those rights on the bucket my-site-chat-files and nothing else:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:ListBucket", "s3:GetBucketCORS", "s3:PutBucketCORS"],
"Resource": "arn:aws:s3:::my-site-chat-files"
},
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:AbortMultipartUpload", "s3:ListMultipartUploadParts"],
"Resource": "arn:aws:s3:::my-site-chat-files/*"
}
]
}
Providers says where each provider creates such a key.
3. Connect the bucket in Better Messages#
In WordPress, go to Better Messages → Settings → Attachments. In the Storage section, click Connect next to Amazon S3 or S3-compatible, and fill in:
- Endpoint: the provider's S3 endpoint, see Providers. Without
https://in front, Better Messages adds it. - Region: the bucket's region.
- Bucket: the bucket's full name.
- Folder: where in the bucket this site keeps its files,
bmby default. Give each site its own folder if several sites share one bucket. - Access Key ID and Secret Access Key, from step 2.
With an endpoint filled in, Path-style URLs appears and is on: Better Messages then addresses files as endpoint/bucket/file, which every S3-compatible provider accepts and self-hosted servers need. Turn it off only if your provider asks for addresses like bucket.endpoint/file.
Click Configure CORS, then Test Connection. The test uploads a small file, reads it back, opens it the way members will, checks that the bucket refuses anyone else, checks that browsers can upload to it, deletes the file and checks it is gone. Every line should say Passed.
Click Connect. It runs the same test and saves the storage when the test passes. When a check fails, nothing is saved: fix what the check says, or click Save Anyway.
4. Choose which files go to the bucket#
Under File Groups, choose Amazon S3 or S3-compatible for each kind of file you want there, then click Save Settings. The choice applies to files sent after you save. Files in end-to-end encrypted conversations stay encrypted wherever they are kept.
Who can open files#
Members of the conversation only#
This is the default. The bucket stays private, and every member gets links to the files that Better Messages signs for them. A link works for one to one and a half days, and the messenger renews links on its own while the member keeps the conversation open. Someone who is not a member of the conversation gets no link, and without one the bucket opens nothing.
Anyone with the link#
Files open from a public address with no check: anyone who has a file's link can open it, and your site does no work when a file is viewed. Use this only for files that are not private, and keep them in a bucket of their own, because a public bucket opens every file in it.
Providers make files public in one of two ways:
- The whole bucket is made readable, with a bucket policy or a "public" setting.
- Each file is uploaded as public. For these providers, tick Upload each file as public-read, which appears under Anyone with the link when an endpoint is filled in.
In Better Messages, choose Anyone with the link (public URL) and enter the address files are served from as the Public URL: the bucket's public address, or a CDN in front of it. Providers lists the public address of each. Test Connection checks that a file opens from it.
A CDN in front of public files keeps a copy of each file, and a deleted file can keep opening from it until its cache expires.
Uploads straight to the bucket#
With Upload straight to the bucket on, the default, a member's browser or the mobile app sends a file straight to the bucket, using a link Better Messages signs for that one file. Files never pass through your server, and large files are sent in parts.
The bucket has to allow uploads from your site, with a CORS rule. Configure CORS adds one, named better-messages, for your site's address and the mobile app, and keeps any other rule the bucket has.
If your provider does not let applications set CORS, or your key cannot, add the rule yourself in the provider's console, with your site's address:
[
{
"AllowedOrigins": ["https://my-site.com", "capacitor://localhost", "https://localhost"],
"AllowedMethods": ["GET", "HEAD", "PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
capacitor://localhost and https://localhost are the Better Messages mobile app. Leave them out if you do not use the app. Some providers refuse capacitor://localhost: Configure CORS then leaves it out, and the iOS app uploads its files through your server.
While the bucket does not allow uploads from your site, files go through your server instead, so sending never fails because of CORS. With Upload straight to the bucket off, every file is uploaded to your server first and copied to the bucket after its message is sent.
The same rule lets members open files in end-to-end encrypted conversations. The messenger downloads such a file itself to decrypt it, and without the rule the browser refuses and shows Could not download media. So keep the rule even with Upload straight to the bucket off. If public files open through a CDN on your own domain, it has to pass the bucket's CORS header on, or set Access-Control-Allow-Origin itself. With encryption on, Test Connection checks this too. The mobile app is not affected.
Old versions of deleted files#
Some providers keep old versions of files, always or when versioning is on. A file deleted through the S3 API is then only hidden: it stops opening, but it stays in the bucket and is billed. Add a lifecycle rule in the provider's console that permanently deletes old versions, or turn versioning off.
Providers#
| Provider | Endpoint | Region |
|---|---|---|
| Amazon S3, see its guide | leave empty | the bucket's region, for example eu-central-1 |
| Backblaze B2, see its guide | https://s3.<region>.backblazeb2.com, shown as the bucket's Endpoint | the part after s3., for example us-west-002 |
| DigitalOcean Spaces, see its guide | https://<region>.digitaloceanspaces.com, for example https://nyc3.digitaloceanspaces.com | the region, for example nyc3 |
| Wasabi, see its guide | https://s3.<region>.wasabisys.com, the bucket's own region | the bucket's region, for example eu-central-2 |
| Scaleway | https://s3.<region>.scw.cloud, for example https://s3.fr-par.scw.cloud | the region, for example fr-par |
| Hetzner Object Storage | https://<location>.your-objectstorage.com, for example https://fsn1.your-objectstorage.com | the location, for example fsn1 |
| OVHcloud | https://s3.<region>.io.cloud.ovh.net, for example https://s3.gra.io.cloud.ovh.net | the region, for example gra |
| Self-hosted (MinIO, Garage and others) | your server's address | the region your server is set to, us-east-1 when it has none |
Scaleway. Keys belong to an IAM application with Object Storage rights in the project. Once a bucket has a bucket policy, only those it names can use the bucket, so name the key's application in any policy you add. For public files, tick Upload each file as public-read. Public address: https://<bucket>.s3.<region>.scw.cloud.
Hetzner Object Storage. A key works on every bucket of its project, so put the chat bucket in a project of its own. CORS can only be set through the S3 API, which is what Configure CORS does. For public files, set the bucket's visibility to public. Hetzner bills every file as at least 64 kB, and a bucket cannot have a domain of its own. Public address: https://<bucket>.<location>.your-objectstorage.com.
OVHcloud. Create an Object Storage user and give it access to the bucket. For public files, tick Upload each file as public-read. A bucket's own domain works only over plain HTTP, so public files open from OVHcloud's address. Public address: https://<bucket>.s3.<region>.io.cloud.ovh.net.
Self-hosted servers. Keep Path-style URLs on. MinIO's community edition does not take a CORS rule per bucket and allows uploads from every site by default, so Configure CORS says it cannot set CORS: that is expected. Public address: https://<your-server>/<bucket>.
Moving files you already have#
Files sent before you connected the bucket stay where they are and keep opening. To move them, click Move all files from one storage to another under File Groups, or move single files in Better Messages → Administration → Files. Moves run in the background, so you can leave the page while they run.
When a file is deleted#
When a file is deleted in Better Messages, it is marked Pending deletion in Administration → Files, and a background job deletes it from the bucket within a few minutes. The file leaves the list once the bucket confirms it is gone. If the bucket refuses, the job tries again later. See Old versions of deleted files for providers that keep them.
Keeping the keys out of the database#
You can put the keys in wp-config.php instead of the settings. The dialog then shows those fields as set in wp-config.php.
define( 'WORDPLUS_S3_ACCESS_KEY', 'your-access-key-id' );
define( 'WORDPLUS_S3_SECRET_KEY', 'your-secret-access-key' );
Troubleshooting#
"The provider does not recognise this access key." The Access Key ID is wrong, the key was deleted, or the endpoint belongs to another region than the key's.
"The secret key does not match the access key." The Secret Access Key does not belong to that Access Key ID.
"The key is not allowed to do this in the bucket." The key's policy does not cover this bucket, or misses one of the rights in step 2.
"The bucket does not exist at this endpoint." Check the bucket name and the endpoint.
"The bucket is in another region. Check the region and the endpoint." Enter the region the bucket was created in, and the endpoint of that region.
"Could not connect to …" Check the endpoint, and for a self-hosted server that it answers over HTTPS from your WordPress server.
"The key cannot tell a deleted file is gone: the bucket answers 403 for it instead of 404." The key cannot list the bucket. Give it that right, see step 2.
"This provider does not let applications set CORS." Add the CORS rule in the provider's console, see Uploads straight to the bucket. Some providers allow uploads from every site anyway, and the test then says browsers can upload.
"CORS does not allow uploads from … yet." Click Configure CORS. A new rule can take a few seconds to apply, so run Test Connection again if the first check still warns.
"Encrypted files will not open in browsers on …"
The address files open from sends no CORS header for your site. Click Configure CORS, or add the rule from Uploads straight to the bucket yourself. If files open through a CDN on your own domain, make it pass the header on or set Access-Control-Allow-Origin, then run Test Connection again.
"Anyone can read files in this bucket without a link." The bucket is public while files are set to members only. Make it private, or keep members-only files in another bucket.
"The file does not open from its public URL." Check the Public URL, and that the bucket lets anyone read its files, or that each file is uploaded as public-read.
"This server's clock is … seconds off the provider's." Signed links stop working when your server's clock is more than 15 minutes off. Ask your host to fix the server time.

