When setting up Mastodon with an S3-compatible storage backend, you may encounter the error ‘Media could not be stored’ when users try to upload images, videos, or other files. This problem typically occurs due to misconfigured S3 bucket permissions, incorrect endpoint URLs, or missing environment variables in your Mastodon configuration. This article explains the root causes of this error and provides clear, step-by-step instructions to resolve it for both AWS S3 and compatible services like DigitalOcean Spaces or MinIO.
Key Takeaways: Fixing S3 Media Storage in Mastodon
- S3_ALIAS_HOST environment variable: Must match the exact bucket endpoint URL to avoid DNS routing failures.
- Bucket CORS configuration: Required for browser-based uploads; missing CORS rules block media uploads silently.
- IAM user permissions: The S3 user needs s3:PutObject, s3:GetObject, and s3:ListBucket actions on the bucket.
Why Mastodon Cannot Store Media on S3
Mastodon uses the Active Storage library in Ruby on Rails to handle file uploads. When configured for S3, it relies on the environment variables defined in your .env.production file or Docker environment. The ‘Media could not be stored’ error appears when Mastodon fails to upload the file to the S3 bucket during the write operation.
The three most common root causes are:
Incorrect Bucket Endpoint Configuration
Mastodon expects the S3_ALIAS_HOST variable to match the public-facing endpoint of your bucket. If you use a different endpoint for the API requests versus the public URL, the upload call fails. For example, DigitalOcean Spaces uses nyc3.digitaloceanspaces.com for API calls but requires a CDN or custom domain for the alias host.
Missing or Incorrect Bucket Permissions
The IAM user or access keys that Mastodon uses must have explicit permissions to write objects to the bucket. If the policy only allows read operations, uploads fail silently. Additionally, the bucket itself must not have a bucket policy that denies public access to the object after upload.
Missing CORS Headers for Direct Uploads
Mastodon can be configured to upload files directly from the browser to S3 using presigned URLs. This method requires the S3 bucket to have a CORS configuration that allows requests from your Mastodon domain. Without this, the browser upload fails and Mastodon logs the ‘Media could not be stored’ error.
Steps to Fix the S3 Media Storage Error in Mastodon
Follow these steps in order. Test after each step by uploading a small image file in Mastodon’s web interface.
- Verify S3 environment variables in .env.production
Open your Mastodon.env.productionfile. Confirm these variables are set correctly:S3_ENABLED=trueS3_BUCKET=your-bucket-nameAWS_ACCESS_KEY_ID=your-access-keyAWS_SECRET_ACCESS_KEY=your-secret-keyS3_REGION=us-east-1(or your bucket’s region)S3_PROTOCOL=httpsS3_HOSTNAME=nyc3.digitaloceanspaces.com(for Spaces) ors3.amazonaws.com(for AWS)S3_ALIAS_HOST=your-custom-domain.comor the direct bucket URL. For AWS, useyour-bucket.s3.amazonaws.com. - Test S3 access with AWS CLI
Install the AWS CLI on your Mastodon server. Runaws s3 ls s3://your-bucket-name --endpoint-url https://nyc3.digitaloceanspaces.com. If this fails, your credentials or endpoint are wrong. Fix the variables and rerun. - Update bucket CORS configuration
Create a file namedcors.jsonwith this content:{ "CORSRules": [ { "AllowedOrigins": ["https://your-mastodon-domain.com"], "AllowedMethods": ["GET", "PUT", "POST"], "AllowedHeaders": [""], "ExposeHeaders": ["ETag"] } ] }
Apply it using the AWS CLI:aws s3api put-bucket-cors --bucket your-bucket-name --cors-configuration file://cors.json --endpoint-url https://nyc3.digitaloceanspaces.com. Replace the endpoint URL with your provider’s API endpoint. - Check IAM user permissions
In your cloud provider, verify the IAM user attached to the access keys has at least these permissions on the bucket:s3:PutObject,s3:GetObject,s3:ListBucket,s3:DeleteObject. Attach a policy like this:{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucket", "s3:DeleteObject" ], "Resource": [ "arn:aws:s3:::your-bucket-name", "arn:aws:s3:::your-bucket-name/" ] } ] } - Restart Mastodon services
After making changes, restart all Mastodon processes:systemctl restart mastodon-web mastodon-sidekiq mastodon-streaming. For Docker setups, rundocker-compose restart. - Test the upload
Log in to Mastodon as an admin. Go to Preferences > Profile and upload a new avatar image. If the error persists, check the Mastodon logs:journalctl -u mastodon-web -n 50. Look for lines containing ‘ActiveStorage::IntegrityError’ or ‘S3’.
If Mastodon Still Shows the S3 Storage Error
Even after applying the main fix, some edge cases can cause the error to reappear. Below are specific scenarios and their resolutions.
Bucket Region Mismatch
If you set S3_REGION to a value that does not match the bucket’s actual region, the AWS SDK may route the request to the wrong endpoint. For DigitalOcean Spaces, the region is part of the endpoint URL (e.g., nyc3). Set S3_REGION to that value. For AWS, use the exact region code such as us-west-2.
SSL Certificate Issues on Custom Domain
If you use a custom domain for S3_ALIAS_HOST, the domain must have a valid SSL certificate. Mastodon uses HTTPS for all S3 requests. If the certificate is self-signed or expired, the upload fails. Use a certificate from Let’s Encrypt or your cloud provider’s CDN.
File Size Exceeds Bucket Limit
Some S3-compatible providers have a default file size limit of 100 MB. If a user uploads a video larger than this, Mastodon logs the ‘Media could not be stored’ error. Check your provider’s documentation for the maximum object size. For AWS S3, the limit is 5 TB, but your bucket policy may restrict it.
Mastodon S3 vs Local File Storage: Configuration Comparison
| Item | S3 Storage | Local File Storage |
|---|---|---|
| Configuration variables | S3_ENABLED, S3_BUCKET, AWS access keys, S3_HOSTNAME, S3_ALIAS_HOST | No additional variables; files stored in /public/system |
| Scalability | Unlimited storage, automatic replication across regions | Limited by server disk space; manual backup needed |
| Latency for users | Depends on CDN or bucket region; can be global | Local to the server; can be slow for remote users |
| Maintenance | Requires monitoring of bucket policies, CORS, and access keys | Requires disk cleanup and backup scripts |
| Cost | Pay per GB stored and per request; can be higher at scale | One-time hardware cost; no per-request fees |
The ‘Media could not be stored’ error in Mastodon is almost always a configuration mismatch between the Mastodon environment variables and the S3 bucket settings. By verifying the endpoint URL, CORS rules, and IAM permissions, you can resolve the issue in under 15 minutes. After fixing, test with a small file upload. If problems persist, check the Mastodon logs for ActiveStorage errors. For advanced setups, consider using a CDN like Cloudflare in front of your S3 bucket to reduce latency and add an extra caching layer.