
A Strapi Upload Provider for Hetzner Object Storage (S3-compatible).
released October 29, 2025
npm install strapi-provider-upload-hetzner-s3A Strapi Upload Provider for Hetzner Object Storage (S3-compatible).
npm install strapi-provider-upload-hetzner-s3
# or
yarn add strapi-provider-upload-hetzner-s3Create or edit ./config/plugins.js (or .ts):
module.exports = ({ env }) => ({
upload: {
config: {
provider: "strapi-provider-upload-hetzner-s3",
providerOptions: {
accessKeyId: env("HETZNER_ACCESS_KEY_ID"),
secretAccessKey: env("HETZNER_SECRET_ACCESS_KEY"),
region: env("HETZNER_REGION"), // fsn1, nbg1, or hel1
params: {
Bucket: env("HETZNER_BUCKET_NAME"),
},
},
},
},
});module.exports = ({ env }) => ({
upload: {
config: {
provider: "strapi-provider-upload-hetzner-s3",
providerOptions: {
accessKeyId: env("HETZNER_ACCESS_KEY_ID"),
secretAccessKey: env("HETZNER_SECRET_ACCESS_KEY"),
region: env("HETZNER_REGION"), // fsn1, nbg1, or hel1
params: {
Bucket: env("HETZNER_BUCKET_NAME"),
ACL: "public-read", // Optional: makes files publicly readable
},
// Optional: Prefix for all uploads
prefix: env("HETZNER_BUCKET_PREFIX", "uploads"), // e.g., "strapi-assets"
// Optional: Custom Base URL (e.g., for CDN)
baseUrl: env("CDN_BASE_URL"), // e.g., "https://cdn.example.com"
},
},
},
});Create a .env file in the root of your project:
# Hetzner Object Storage Credentials
HETZNER_ACCESS_KEY_ID=your_access_key
HETZNER_SECRET_ACCESS_KEY=your_secret_key
# Region (fsn1, nbg1, or hel1)
HETZNER_REGION=fsn1
# Bucket Name
HETZNER_BUCKET_NAME=my-strapi-bucket
# Optional: Bucket Prefix
HETZNER_BUCKET_PREFIX=uploads
# Optional: CDN Base URL
CDN_BASE_URL=https://cdn.example.com| Region Code | Location | Endpoint |
|---|---|---|
fsn1 | Falkenstein, Germany | fsn1.your-objectstorage.com |
nbg1 | Nuremberg, Germany | nbg1.your-objectstorage.com |
hel1 | Helsinki, Finland | hel1.your-objectstorage.com |
To display thumbnails correctly in the Strapi Admin Panel, configure the Content Security Policy:
Edit ./config/middlewares.js:
module.exports = ({ env }) => [
// ... other middlewares
{
name: "strapi::security",
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
"connect-src": ["'self'", "https:"],
"img-src": [
"'self'",
"data:",
"blob:",
`${env("HETZNER_BUCKET_NAME")}.${env("HETZNER_REGION")}.your-objectstorage.com`,
],
"media-src": [
"'self'",
"data:",
"blob:",
`${env("HETZNER_BUCKET_NAME")}.${env("HETZNER_REGION")}.your-objectstorage.com`,
],
upgradeInsecureRequests: null,
},
},
},
},
// ... other middlewares
];If using a CDN Base URL:
"img-src": [
"'self'",
"data:",
"blob:",
env("CDN_BASE_URL"),
],
"media-src": [
"'self'",
"data:",
"blob:",
env("CDN_BASE_URL"),
],Hetzner Object Storage supports the following ACL values:
private (Default): Only the bucket owner has accesspublic-read: Everyone can read objectspublic-read-write: Everyone can read and write (⚠️ not recommended)authenticated-read: Only authenticated users can readRecommendation: Use public-read for public websites or omit ACL and configure bucket settings via Hetzner Console.
Use prefixes to organize your files:
prefix: "production/uploads" // All files under production/uploads/
prefix: "strapi/media" // All files under strapi/media/If your frontend directly accesses the files, configure CORS in the Hetzner Console:
{
"CORSRules": [
{
"AllowedOrigins": ["https://your-website.com"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3000
}
]
}Solution:
s3:PutObjectACL permissionSolution:
Solution:
middlewares.jsSolution:
# Run tests
npm test
# Tests with coverage
npm test -- --coverage
# Build
npm run build| Feature | Hetzner S3 | AWS S3 | DigitalOcean Spaces |
|---|---|---|---|
| Price (Storage) | ~€0.005/GB | ~€0.023/GB | ~€0.020/GB |
| Price (Transfer) | Free | ~€0.09/GB | ~€0.01/GB |
| EU Regions | ✅ 3 | ✅ 8+ | ❌ |
| S3-Compatible | ✅ | ✅ | ✅ |
| GDPR-Compliant | ✅ | ⚠️ | ⚠️ |
Contributions are welcome! Please create a pull request or open an issue.
# Clone repository
git clone https://github.com/raiva-technologies/strapi-provider-upload-hetzner-s3.git
# Install dependencies
npm install
# Run tests
npm test
# Build
npm run buildMIT License - see LICENSE file
If you have any questions or issues:
Made with ❤️ for the Strapi Community
Share your work with the community and get it listed in the Strapi ecosystem for everyone to discover and use.
Submit
