isFunctional()) { return false; } if ($server->isBuildServer()) { if ($server->proxy) { $server->proxy = null; $server->save(); } return false; } $proxyType = $server->proxyType(); if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) && ! $fromUI) { return false; } if (! $server->isProxyShouldRun()) { if ($fromUI) { throw new \Exception('Proxy should not run. You selected the Custom Proxy.'); } else { return false; } } // Determine proxy container name based on environment $proxyContainerName = $server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy'; if ($server->isSwarm()) { $status = getContainerStatus($server, $proxyContainerName); $server->proxy->set('status', $status); $server->save(); if ($status === 'running') { return false; } return true; } else { $status = getContainerStatus($server, $proxyContainerName); if ($status === 'running') { $server->proxy->set('status', 'running'); $server->save(); return false; } if ($server->settings->is_cloudflare_tunnel) { return false; } $ip = $server->ip; if ($server->id === 0) { $ip = 'host.docker.internal'; } $portsToCheck = []; try { if ($server->proxyType() !== ProxyTypes::NONE->value) { $proxyCompose = GetProxyConfiguration::run($server); if (isset($proxyCompose)) { $yaml = Yaml::parse($proxyCompose); $configPorts = []; if ($server->proxyType() === ProxyTypes::TRAEFIK->value) { $ports = data_get($yaml, 'services.traefik.ports'); } elseif ($server->proxyType() === ProxyTypes::CADDY->value) { $ports = data_get($yaml, 'services.caddy.ports'); } if (isset($ports)) { foreach ($ports as $port) { $configPorts[] = str($port)->before(':')->value(); } } // Combine default ports with config ports $portsToCheck = array_merge($portsToCheck, $configPorts); } } else { $portsToCheck = []; } } catch (\Exception $e) { Log::error('Error checking proxy: '.$e->getMessage()); } if (count($portsToCheck) === 0) { return false; } $portsToCheck = array_values(array_unique($portsToCheck)); // Check port conflicts in parallel $conflicts = $this->checkPortConflictsInParallel($server, $portsToCheck, $proxyContainerName); foreach ($conflicts as $port => $conflict) { if ($conflict) { if ($fromUI) { throw new \Exception("Port $port is in use.
You must stop the process using this port.

Docs: https://coolify.io/docs
Discord: https://coolify.io/discord"); } else { return false; } } } return true; } } /** * Check multiple ports for conflicts in parallel * Returns an array with port => conflict_status mapping */ private function checkPortConflictsInParallel(Server $server, array $ports, string $proxyContainerName): array { if (empty($ports)) { return []; } try { // Build concurrent port check commands $results = Process::concurrently(function ($pool) use ($server, $ports, $proxyContainerName) { foreach ($ports as $port) { $commands = $this->buildPortCheckCommands($server, $port, $proxyContainerName); $pool->command($commands['ssh_command'])->timeout(10); } }); // Process results $conflicts = []; foreach ($ports as $index => $port) { $result = $results[$index] ?? null; if ($result) { $conflicts[$port] = $this->parsePortCheckResult($result, $port, $proxyContainerName); } else { // If process failed, assume no conflict to avoid false positives $conflicts[$port] = false; } } return $conflicts; } catch (\Throwable $e) { Log::warning('Parallel port checking failed: '.$e->getMessage().'. Falling back to sequential checking.'); // Fallback to sequential checking if parallel fails $conflicts = []; foreach ($ports as $port) { $conflicts[$port] = $this->isPortConflict($server, $port, $proxyContainerName); } return $conflicts; } } /** * Build the SSH command for checking a specific port */ private function buildPortCheckCommands(Server $server, string $port, string $proxyContainerName): array { // First check if our own proxy is using this port (which is fine) $getProxyContainerId = "docker ps -a --filter name=$proxyContainerName --format '{{.ID}}'"; $checkProxyPortScript = " CONTAINER_ID=\$($getProxyContainerId); if [ ! -z \"\$CONTAINER_ID\" ]; then if docker inspect \$CONTAINER_ID --format '{{json .NetworkSettings.Ports}}' | grep -q '\"$port/tcp\"'; then echo 'proxy_using_port'; exit 0; fi; fi; "; // Command sets for different ways to check ports, ordered by preference $portCheckScript = " $checkProxyPortScript # Try ss command first if command -v ss >/dev/null 2>&1; then ss_output=\$(ss -Htuln state listening sport = :$port 2>/dev/null); if [ -z \"\$ss_output\" ]; then echo 'port_free'; exit 0; fi; count=\$(echo \"\$ss_output\" | grep -c ':$port '); if [ \$count -eq 0 ]; then echo 'port_free'; exit 0; fi; # Check for dual-stack or docker processes if [ \$count -le 2 ] && (echo \"\$ss_output\" | grep -q 'docker\\|coolify'); then echo 'port_free'; exit 0; fi; echo \"port_conflict|\$ss_output\"; exit 0; fi; # Try netstat as fallback if command -v netstat >/dev/null 2>&1; then netstat_output=\$(netstat -tuln 2>/dev/null | grep ':$port '); if [ -z \"\$netstat_output\" ]; then echo 'port_free'; exit 0; fi; count=\$(echo \"\$netstat_output\" | grep -c 'LISTEN'); if [ \$count -eq 0 ]; then echo 'port_free'; exit 0; fi; if [ \$count -le 2 ] && (echo \"\$netstat_output\" | grep -q 'docker\\|coolify'); then echo 'port_free'; exit 0; fi; echo \"port_conflict|\$netstat_output\"; exit 0; fi; # Final fallback using nc if nc -z -w1 127.0.0.1 $port >/dev/null 2>&1; then echo 'port_conflict|nc_detected'; else echo 'port_free'; fi; "; $sshCommand = \App\Helpers\SshMultiplexingHelper::generateSshCommand($server, $portCheckScript); return [ 'ssh_command' => $sshCommand, 'script' => $portCheckScript, ]; } /** * Parse the result from port check command */ private function parsePortCheckResult($processResult, string $port, string $proxyContainerName): bool { $exitCode = $processResult->exitCode(); $output = trim($processResult->output()); $errorOutput = trim($processResult->errorOutput()); if ($exitCode !== 0) { return false; } if ($output === 'proxy_using_port' || $output === 'port_free') { return false; // No conflict } if (str_starts_with($output, 'port_conflict|')) { $details = substr($output, strlen('port_conflict|')); // Additional logic to detect dual-stack scenarios if ($details !== 'nc_detected') { // Check for dual-stack scenario - typically 1-2 listeners (IPv4+IPv6) $lines = explode("\n", $details); if (count($lines) <= 2) { // Look for IPv4 and IPv6 in the listing if ((strpos($details, '0.0.0.0:'.$port) !== false && strpos($details, ':::'.$port) !== false) || (strpos($details, '*:'.$port) !== false && preg_match('/\*:'.$port.'.*IPv[46]/', $details))) { return false; // This is just a normal dual-stack setup } } } return true; // Real port conflict } return false; } /** * Smart port checker that handles dual-stack configurations * Returns true only if there's a real port conflict (not just dual-stack) */ private function isPortConflict(Server $server, string $port, string $proxyContainerName): bool { // First check if our own proxy is using this port (which is fine) try { $getProxyContainerId = "docker ps -a --filter name=$proxyContainerName --format '{{.ID}}'"; $containerId = trim(instant_remote_process([$getProxyContainerId], $server)); if (! empty($containerId)) { $checkProxyPort = "docker inspect $containerId --format '{{json .NetworkSettings.Ports}}' | grep '\"$port/tcp\"'"; try { instant_remote_process([$checkProxyPort], $server); // Our proxy is using the port, which is fine return false; } catch (\Throwable $e) { // Our container exists but not using this port } } } catch (\Throwable $e) { // Container not found or error checking, continue with regular checks } // Command sets for different ways to check ports, ordered by preference $commandSets = [ // Set 1: Use ss to check listener counts by protocol stack [ 'available' => 'command -v ss >/dev/null 2>&1', 'check' => [ // Get listening process details "ss_output=\$(ss -Htuln state listening sport = :$port 2>/dev/null) && echo \"\$ss_output\"", // Count IPv4 listeners "echo \"\$ss_output\" | grep -c ':$port '", ], ], // Set 2: Use netstat as alternative to ss [ 'available' => 'command -v netstat >/dev/null 2>&1', 'check' => [ // Get listening process details "netstat_output=\$(netstat -tuln 2>/dev/null) && echo \"\$netstat_output\" | grep ':$port '", // Count listeners "echo \"\$netstat_output\" | grep ':$port ' | grep -c 'LISTEN'", ], ], // Set 3: Use lsof as last resort [ 'available' => 'command -v lsof >/dev/null 2>&1', 'check' => [ // Get process using the port "lsof -i :$port -P -n | grep 'LISTEN'", // Count listeners "lsof -i :$port -P -n | grep 'LISTEN' | wc -l", ], ], ]; // Try each command set until we find one available foreach ($commandSets as $set) { try { // Check if the command is available instant_remote_process([$set['available']], $server); // Run the actual check commands $output = instant_remote_process($set['check'], $server, true); // Parse the output lines $lines = explode("\n", trim($output)); // Get the detailed output and listener count $details = trim(implode("\n", array_slice($lines, 0, -1))); $count = intval(trim($lines[count($lines) - 1] ?? '0')); // If no listeners or empty result, port is free if ($count == 0 || empty($details)) { return false; } // Try to detect if this is our coolify-proxy if (strpos($details, 'docker') !== false || strpos($details, $proxyContainerName) !== false) { // It's likely our docker or proxy, which is fine return false; } // Check for dual-stack scenario - typically 1-2 listeners (IPv4+IPv6) // If exactly 2 listeners and both have same port, likely dual-stack if ($count <= 2) { // Check if it looks like a standard dual-stack setup $isDualStack = false; // Look for IPv4 and IPv6 in the listing (ss output format) if (preg_match('/LISTEN.*:'.$port.'\s/', $details) && (preg_match('/\*:'.$port.'\s/', $details) || preg_match('/:::'.$port.'\s/', $details))) { $isDualStack = true; } // For netstat format if (strpos($details, '0.0.0.0:'.$port) !== false && strpos($details, ':::'.$port) !== false) { $isDualStack = true; } // For lsof format (IPv4 and IPv6) if (strpos($details, '*:'.$port) !== false && preg_match('/\*:'.$port.'.*IPv4/', $details) && preg_match('/\*:'.$port.'.*IPv6/', $details)) { $isDualStack = true; } if ($isDualStack) { return false; // This is just a normal dual-stack setup } } // If we get here, it's likely a real port conflict return true; } catch (\Throwable $e) { // This command set failed, try the next one continue; } } // Fallback to simpler check if all above methods fail try { // Just try to bind to the port directly to see if it's available $checkCommand = "nc -z -w1 127.0.0.1 $port >/dev/null 2>&1 && echo 'in-use' || echo 'free'"; $result = instant_remote_process([$checkCommand], $server, true); return trim($result) === 'in-use'; } catch (\Throwable $e) { // If everything fails, assume the port is free to avoid false positives return false; } } }