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.

Portrait of a young man with short brown hair and blue shirt in front of a green, lush landscape.

Written by

Jens Becker

Published at

May 30, 2025

Last updated on

July 1, 2026

Tags

Payload CMS

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

  1. Log in to the Hetzner Cloud Console.
  2. Create a new project (or select an existing one).
  3. Add a new storage bucket within the project.
  4. Navigate to the Access Keys section and generate a new key pair.
  5. Copy the Access Key ID and Secret Access Key into your Payload project's .env file.
  6. Also, copy the name of your storage bucket.

Your .env file should include the following variables:

ts
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:

ts
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:

ts
{
  "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

ts
[<bucket-name>]
aws_access_key_id = <access-key-id>
aws_secret_access_key = <secret-access-key>

config

ts
[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:

ts
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.