Hosting Media in Payload CMS with Hetzner S3 Object Storage
Written by Jens Becker
Last updated on June 21, 2025

In this post, we’ll take an in-depth look at how to use Hetzner Object Storage to host media files in Payload CMS , leveraging its flexible storage adapter system .
Whether you're building a custom CMS or a production-ready platform with asset management capabilities, integrating Hetzner's cost-effective S3-compatible storage with Payload is a powerful and affordable solution.
Setting Up the Storage Bucket
- Log in to the Hetzner Cloud Console .
- Create a new project (or select an existing one).
- Add a new storage bucket within the project.
- Navigate to the Access Keys section and generate a new key pair.
- Copy the Access Key ID and Secret Access Key into your Payload project’s
.env
file. - Also, copy the name of your storage bucket.
Your .env
file should include the following variables:
HETZNER_BUCKET="bucket-name"
HETZNER_ACCESS_KEY_ID="access-key-id"
HETZNER_SECRET_ACCESS_KEY="secret-access-key"
Payload Configuration
To connect Payload CMS to Hetzner Object Storage, you’ll use the @joneslloyd/payload-storage-hetzner
plugin. This community-built adapter allows seamless integration with Hetzner's S3 API.
Here’s a basic plugin configuration in your Payload setup:
import hetznerStorage from '@joneslloyd/payload-storage-hetzner';
export default buildConfig({
/* your config */
plugins: [
hetznerStorage({
collections: {
media: true,
},
bucket: process.env.HETZNER_BUCKET!,
region: 'nbg1',
credentials: {
accessKeyId: process.env.HETZNER_ACCESS_KEY_ID!,
secretAccessKey: process.env.HETZNER_SECRET_ACCESS_KEY!,
},
acl: 'public-read',
}),
],
});
For full configuration options, refer to the npm documentation .
Supporting Client-Side Uploads (Important for Vercel)
When deploying Payload on Vercel , you may run into upload limitations due to Vercel's 4.5 MB body size limit .
To work around this, you can enable client-side uploads. This allows media files to be uploaded directly to Hetzner from the browser, bypassing Payload’s API layer.
To enable client uploads, set clientUploads: true
in the plugin config.
CORS Configuration for Hetzner
Client-side uploads require properly configured CORS (Cross-Origin Resource Sharing) rules on your Hetzner Object Storage bucket. If not configured, you’ll encounter this common error:
Access to fetch at '<hetzner-url>' from origin '<your-domain>' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Step-by-Step: Setting CORS via AWS CLI
Unfortunately, Hetzner does not currently provide a UI to edit CORS settings. Instead, we’ll use the AWS CLI to configure them.
1. Add a CORS file
Create a s3-cors.json
file in your Payload project:
{
"CORSRules": [
{
"AllowedOrigins": ["https://your-domain.com"],
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"MaxAgeSeconds": 3000
}
]
}
This allows you to track your CORS configuration in version control.
2. Install AWS CLI
Follow the AWS CLI Installation Guide .
3. Create AWS CLI Profile
In your system’s .aws
folder (e.g., ~/.aws
or C:\Users\<your-user>\.aws
), add the following:credentials
[<bucket-name>]
aws_access_key_id = <access-key-id>
aws_secret_access_key = <secret-access-key>
config
[profile <bucket-name>]
region = eu-central-1
output = json
endpoint_url = https://<bucket-region>.your-objectstorage.com
4. Apply the CORS Configuration
From your Payload project directory, run:
aws s3api put-bucket-cors \
--bucket <your-bucket-name> \
--cors-configuration file://./s3-cors.json \
--endpoint-url https://<bucket-region>.your-objectstorage.com \
--profile <bucket-name>
Conclusion
With this setup, your Payload CMS is now fully integrated with Hetzner's Object Storage — and ready for efficient, scalable media handling. Whether you're building a marketing site, internal dashboard, or custom CMS, this combo offers performance and flexibility without high costs.
Thanks for reading! If you have questions, feedback, or improvements, feel free to reach out.