Hetzner Object Storage
A Strapi Upload Provider for Hetzner Object Storage (S3-compatible).
strapi-provider-upload-hetzner-s3
A Strapi Upload Provider for Hetzner Object Storage (S3-compatible).
🚀 Features
- ✅ Full integration with Hetzner Object Storage
- ✅ Support for all three Hetzner regions (FSN1, NBG1, HEL1)
- ✅ TypeScript Support
- ✅ Bucket prefixes for organized file structure
- ✅ Custom Base URL Support (e.g., for CDN)
- ✅ ACL configuration
- ✅ Unit Tests
- ✅ Buffer and Stream Upload Support
📋 Prerequisites
- Node.js >= 14.19.1
- Strapi >= 4.0.0
- A Hetzner Object Storage Bucket
📦 Installation
npm install strapi-provider-upload-hetzner-s3
# or
yarn add strapi-provider-upload-hetzner-s3🔧 Hetzner Object Storage Setup
1. Create Bucket
- Go to Hetzner Cloud Console
- Navigate to "Object Storage"
- Create a new bucket
- Choose a region:
- FSN1: Falkenstein, Germany
- NBG1: Nuremberg, Germany
- HEL1: Helsinki, Finland
2. Generate Access Keys
- Click on your bucket
- Go to "S3 Keys"
- Create a new S3 Key
- Save the Access Key and Secret Key securely
3. Bucket Settings
- Public Access: Enable this if your files should be publicly accessible
- CORS: Configure CORS settings if needed
⚙️ Configuration
Basic Configuration
Create or edit ./config/plugins.js (or .ts):
1module.exports = ({ env }) => ({
2 upload: {
3 config: {
4 provider: "strapi-provider-upload-hetzner-s3",
5 providerOptions: {
6 accessKeyId: env("HETZNER_ACCESS_KEY_ID"),
7 secretAccessKey: env("HETZNER_SECRET_ACCESS_KEY"),
8 region: env("HETZNER_REGION"), // fsn1, nbg1, or hel1
9 params: {
10 Bucket: env("HETZNER_BUCKET_NAME"),
11 },
12 },
13 },
14 },
15});Advanced Configuration
1module.exports = ({ env }) => ({
2 upload: {
3 config: {
4 provider: "strapi-provider-upload-hetzner-s3",
5 providerOptions: {
6 accessKeyId: env("HETZNER_ACCESS_KEY_ID"),
7 secretAccessKey: env("HETZNER_SECRET_ACCESS_KEY"),
8 region: env("HETZNER_REGION"), // fsn1, nbg1, or hel1
9 params: {
10 Bucket: env("HETZNER_BUCKET_NAME"),
11 ACL: "public-read", // Optional: makes files publicly readable
12 },
13 // Optional: Prefix for all uploads
14 prefix: env("HETZNER_BUCKET_PREFIX", "uploads"), // e.g., "strapi-assets"
15 // Optional: Custom Base URL (e.g., for CDN)
16 baseUrl: env("CDN_BASE_URL"), // e.g., "https://cdn.example.com"
17 },
18 },
19 },
20});Environment Variables
Create a .env file in the root of your project:
1# Hetzner Object Storage Credentials
2HETZNER_ACCESS_KEY_ID=your_access_key
3HETZNER_SECRET_ACCESS_KEY=your_secret_key
4
5# Region (fsn1, nbg1, or hel1)
6HETZNER_REGION=fsn1
7
8# Bucket Name
9HETZNER_BUCKET_NAME=my-strapi-bucket
10
11# Optional: Bucket Prefix
12HETZNER_BUCKET_PREFIX=uploads
13
14# Optional: CDN Base URL
15CDN_BASE_URL=https://cdn.example.com🌍 Available Regions
| 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 |
🖼️ Image Previews in Strapi Admin
To display thumbnails correctly in the Strapi Admin Panel, configure the Content Security Policy:
Edit ./config/middlewares.js:
1module.exports = ({ env }) => [
2 // ... other middlewares
3 {
4 name: "strapi::security",
5 config: {
6 contentSecurityPolicy: {
7 useDefaults: true,
8 directives: {
9 "connect-src": ["'self'", "https:"],
10 "img-src": [
11 "'self'",
12 "data:",
13 "blob:",
14 `${env("HETZNER_BUCKET_NAME")}.${env("HETZNER_REGION")}.your-objectstorage.com`,
15 ],
16 "media-src": [
17 "'self'",
18 "data:",
19 "blob:",
20 `${env("HETZNER_BUCKET_NAME")}.${env("HETZNER_REGION")}.your-objectstorage.com`,
21 ],
22 upgradeInsecureRequests: null,
23 },
24 },
25 },
26 },
27 // ... other middlewares
28];If using a CDN Base URL:
1"img-src": [
2 "'self'",
3 "data:",
4 "blob:",
5 env("CDN_BASE_URL"),
6],
7"media-src": [
8 "'self'",
9 "data:",
10 "blob:",
11 env("CDN_BASE_URL"),
12],🔐 Security & Best Practices
ACL Settings
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 read
Recommendation: Use public-read for public websites or omit ACL and configure bucket settings via Hetzner Console.
Bucket Organization with Prefixes
Use prefixes to organize your files:
1prefix: "production/uploads" // All files under production/uploads/
2prefix: "strapi/media" // All files under strapi/media/CORS Configuration
If your frontend directly accesses the files, configure CORS in the Hetzner Console:
1{
2 "CORSRules": [
3 {
4 "AllowedOrigins": ["https://your-website.com"],
5 "AllowedMethods": ["GET", "HEAD"],
6 "AllowedHeaders": ["*"],
7 "MaxAgeSeconds": 3000
8 }
9 ]
10}🚨 Troubleshooting
Error: "Access Denied"
Solution:
- Check your Access Key and Secret Key
- Ensure the key has the correct permissions
- If using ACL, ensure the key has
s3:PutObjectACLpermission
Error: "Bucket not found"
Solution:
- Check the bucket name (case-sensitive!)
- Ensure the bucket exists in the correct region
- Verify the region configuration (fsn1, nbg1, or hel1)
Images not displaying
Solution:
- Check the Content Security Policy in
middlewares.js - Ensure the bucket is publicly readable or ACL is set correctly
- Check browser console for CORS errors
Uploads failing
Solution:
- Check bucket size and limits
- Ensure the upload isn't too large
- Check network connection
- Enable debug logging in Strapi
🧪 Testing
# Run tests
npm test
# Tests with coverage
npm test -- --coverage
# Build
npm run build📊 Comparison to Other Providers
| 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 | ✅ | ⚠️ | ⚠️ |
🤝 Contributing
Contributions are welcome! Please create a pull request or open an issue.
Development Setup
# 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 build📝 License
MIT License - see LICENSE file
🔗 Links
💬 Support
If you have any questions or issues:
- Open a GitHub Issue
- Contact: hermann.delcampo@raiva.io
📈 Changelog
v1.0.0
- Initial Release
- Support for all three Hetzner regions
- TypeScript Support
- Full test coverage
- Prefix and Base URL Support
Made with ❤️ for the Strapi Community
Install now
npm install strapi-provider-upload-hetzner-s3
Create your own plugin
Check out the available plugin resources that will help you to develop your plugin or provider and get it listed on the marketplace.