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

  1. 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.
  2. Or use a publicly routable endpoint for the S3 target.
  3. 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>).
  4. 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

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


AI-assisted analysis of coollabsio/coolify@70b9acc424 (2026-08-17). Data as JSON: /api/errors/b63e81ca828146e6. Report an issue: GitHub.