diff --git a/app/Console/Commands/CleanupUnreachableServers.php b/app/Console/Commands/CleanupUnreachableServers.php
index 615dc21d20..f6b3ee9f6f 100644
--- a/app/Console/Commands/CleanupUnreachableServers.php
+++ b/app/Console/Commands/CleanupUnreachableServers.php
@@ -15,7 +15,8 @@ class CleanupUnreachableServers extends Command
public function handle()
{
echo "Running unreachable server cleanup...\n";
- $servers = Server::where('unreachable_count', '>=', 3)->whereHas('notificationThrottles', fn ($query) => $query->where('notification', Unreachable::class))->where('updated_at', '<', now()->subDays(7))->get();
+ // The localhost server is the Coolify host itself, so the cleanup never touches it.
+ $servers = Server::where('id', '!=', 0)->where('unreachable_count', '>=', 3)->whereHas('notificationThrottles', fn ($query) => $query->where('notification', Unreachable::class))->where('updated_at', '<', now()->subDays(7))->get();
if ($servers->count() > 0) {
foreach ($servers as $server) {
echo "Cleanup unreachable server ($server->id) with name $server->name";
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 736d63aed5..2ac649ce98 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -1438,6 +1438,15 @@ $siteAddress {
return $this->settings->force_disabled;
}
+ /**
+ * On self-hosted, only the unreachable cleanup disables a server, so a working connection can enable it again.
+ * On Cloud the flag enforces the plan server limit, and a transferred server stays an archive.
+ */
+ public function canBeReEnabled(): bool
+ {
+ return $this->settings->force_disabled === true && ! isCloud() && ! $this->isTransferredAway();
+ }
+
/**
* Server was migrated away from this Coolify instance (source side).
* Must not be revalidated or re-enabled as a live managed host.
@@ -2544,6 +2553,11 @@ $siteAddress {
public function validateConnection(bool $justCheckingNewKey = false)
{
+ // A validation enables a disabled self-hosted server again, but only when the connection works.
+ $isReEnabling = $this->canBeReEnabled() && ! $this->hasPlaceholderIp();
+ if ($isReEnabling) {
+ $this->forceEnableServer();
+ }
if ($this->skipServer()) {
return ['uptime' => false, 'error' => 'Server skipped.'];
}
@@ -2564,6 +2578,9 @@ $siteAddress {
return ['uptime' => true, 'error' => null];
} catch (\Throwable $e) {
+ if ($isReEnabling) {
+ $this->forceDisableServer();
+ }
$error = $e->getMessage();
if ($error === SshMultiplexingHelper::HOST_KEY_CHANGED_MESSAGE && ! $this->recordHostKeyMismatch()) {
$error = 'SSH host key verification failed, but the server still sends the trusted host key.';
diff --git a/resources/views/livewire/server/show.blade.php b/resources/views/livewire/server/show.blade.php
index 31b6fae612..938725d50f 100644
--- a/resources/views/livewire/server/show.blade.php
+++ b/resources/views/livewire/server/show.blade.php
@@ -221,6 +221,14 @@
@endif
+ @if ($server->canBeReEnabled())
+