Hosting Media in Payload CMS with Hetzner S3 Object Storage
This article covers how to integrate Hetzner Object Storage as a media backend for Payload CMS, including client-side uploads and CORS configuration for Vercel deployments.
This article covers how to integrate Hetzner Object Storage as a media backend for Payload CMS, using its flexible storage adapter system.
For both custom CMS projects and production-ready platforms with asset management, Hetzner's cost-effective S3-compatible storage integrates cleanly with Payload.
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
.envfile. - 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, use the @joneslloyd/payload-storage-hetzner plugin. This community-built adapter enables 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, upload limitations may arise due to Vercel's 4.5 MB body size limit.
To work around this, 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 tracking 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, Payload CMS is fully integrated with Hetzner's Object Storage — ready for efficient, scalable media handling. The combination offers performance and flexibility without high costs.