coollabsio/coolify · error · RuntimeException
S3 endpoint is not allowed: {error}
Error message
S3 endpoint is not allowed: {error} What it means
Thrown by S3Storage::testConnection() (app/Models/S3Storage.php:182). Before attempting any S3 traffic, the endpoint URL is run through the SafeWebhookUrl rule (with the storage's trustedInternalHosts()), which is Coolify's SSRF guard: it rejects non-http(s) schemes, missing/invalid hosts, trailing-dot hostnames, 'localhost'/'.local'/'.internal'/'.cluster.local' names, link-local addresses (169.254.0.0/16 — cloud metadata), and loopback/private/reserved IPs — including IPs resolved via DNS — unless the target is explicitly allowlisted in InstanceSettings (webhook_allowed_internal_hosts) or trusted internal hosts. A failing endpoint aborts the connection test, marks the storage unusable, and (once) emails team admins.
Source
Thrown at app/Models/S3Storage.php:182
);
}
public function testConnection(bool $shouldSave = false)
{
try {
$validator = Validator::make(
[
'endpoint' => $this['endpoint'],
'bucket' => $this['bucket'],
],
[
'endpoint' => ['required', new SafeWebhookUrl(trustedInternalHosts: $this->trustedInternalHosts())],
'bucket' => ['required', new ValidS3BucketName],
],
);
$validator->fails();
if ($validator->errors()->has('endpoint')) {
throw new \RuntimeException('S3 endpoint is not allowed: '.$validator->errors()->first('endpoint'));
}
if ($validator->errors()->has('bucket')) {
throw new \RuntimeException('S3 bucket name is not allowed: '.$validator->errors()->first('bucket'));
}
$disk = $this->filesystem();
// Test the connection by listing files with ListObjectsV2 (S3)
$disk->files();
$this->unusable_email_sent = false;
$this->is_usable = true;
} catch (\Throwable $e) {
$exception = $this->toUserFriendlyConnectionException($e);
$this->is_usable = false;
if ($this->unusable_email_sent === false && is_transactional_emails_enabled()) {
try {
$mail = new MailMessage;
$mail->subject('Coolify: S3 Storage Connection Error');View on GitHub (pinned to 70b9acc424)
Solutions
- Allowlist the internal target: Settings → Advanced → Endpoint section, add the hostname or CIDR (e.g. 192.168.1.0/24 or minio.internal) to webhook_allowed_internal_hosts, then re-test.
- Or use a publicly routable endpoint for the S3 target.
- Fix URL hygiene: scheme http/https, no trailing dot, a real hostname (not localhost), and make sure DNS resolves the way you expect (dig +short <host>).
- Remember DNS-resolved private IPs are blocked too — allowlisting must match the hostname that resolves, not just the literal IP.
Example fix
// before $storage->update(['endpoint' => 'http://192.168.1.10:9000']); $storage->testConnection(); // S3 endpoint is not allowed: ... private address // after — allowlist the internal network on the instance, then re-test $settings = \App\Models\InstanceSettings::find(0); $settings->webhook_allowed_internal_hosts = '192.168.1.0/24'; $settings->save(); $storage->testConnection();
Defensive patterns
Strategy: validation
Validate before calling
// Pre-validate the endpoint with the same rule before saving/testing
use App\Rules\SafeWebhookUrl;
Validator::validate([
'endpoint' => $endpoint,
], [
'endpoint' => ['required', new SafeWebhookUrl(trustedInternalHosts: $storage->trustedInternalHosts())],
]); Try / catch
try {
$storage->testConnection();
} catch (\Throwable $e) {
// testConnection() does not rethrow; it sets is_usable=false and emails admins.
if ($storage->fresh()->is_usable === false) {
return 'Endpoint blocked by SSRF guard — allowlist the internal host in Settings → Advanced, or use a public endpoint.';
}
} Prevention
- For internal MinIO/S3 targets, pre-allowlist the hostname or CIDR in InstanceSettings webhook_allowed_internal_hosts (Settings → Advanced → Endpoint section).
- Prefer real hostnames over raw IPs so the allowlist matches what DNS resolves to (resolved private IPs are also checked).
- Avoid localhost/.local/.internal names, trailing dots, and non-http(s) schemes in endpoints.
When it happens
Trigger: Saving or testing an S3 storage whose endpoint is an internal address: http://192.168.1.10:9000, http://10.x.x.x, http://minio.internal, http://localhost:9000, or a public-looking hostname that DNS-resolves to a private IP. Also https endpoints with a trailing dot or non-http scheme.
Common situations: Pointing Coolify backups at a self-hosted MinIO on the LAN; homelab setups where the S3 target is the same Docker network; split-horizon DNS resolving the bucket host to an internal IP; enabling the S3 storage test after tightening instance security settings.
Related errors
- S3 bucket name is not allowed: {error}
- Invalid Cron / Human expression
- The backup target is not a directory or persistent volume.
- The directory backup workdir is unavailable.
- Pre-deployment command: Could not find a valid container. Is
AI-assisted analysis of coollabsio/coolify@70b9acc424 (2026-08-17).
Data as JSON: /api/errors/b63e81ca828146e6.
Report an issue: GitHub.