diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php
index 27608547a..78312d65a 100644
--- a/app/Actions/Proxy/CheckProxy.php
+++ b/app/Actions/Proxy/CheckProxy.php
@@ -37,8 +37,12 @@ class CheckProxy
return false;
}
}
+
+ // Determine proxy container name based on environment
+ $proxyContainerName = $server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
+
if ($server->isSwarm()) {
- $status = getContainerStatus($server, 'coolify-proxy_traefik');
+ $status = getContainerStatus($server, $proxyContainerName);
$server->proxy->set('status', $status);
$server->save();
if ($status === 'running') {
@@ -47,7 +51,7 @@ class CheckProxy
return true;
} else {
- $status = getContainerStatus($server, 'coolify-proxy');
+ $status = getContainerStatus($server, $proxyContainerName);
if ($status === 'running') {
$server->proxy->set('status', 'running');
$server->save();
@@ -65,30 +69,8 @@ class CheckProxy
$portsToCheck = ['80', '443'];
foreach ($portsToCheck as $port) {
- // Try multiple methods to check port availability
- $commands = [
- // Method 1: Check /proc/net/tcp directly (convert port to hex)
- "cat /proc/net/tcp | grep -q '00000000:".str_pad(dechex($port), 4, '0', STR_PAD_LEFT)."'",
- // Method 2: Use ss command (modern alternative to netstat)
- "ss -tuln | grep -q ':$port '",
- // Method 3: Use lsof if available
- "lsof -i :$port >/dev/null 2>&1",
- // Method 4: Use fuser if available
- "fuser $port/tcp >/dev/null 2>&1",
- ];
-
- $portInUse = false;
- foreach ($commands as $command) {
- try {
- instant_remote_process([$command], $server);
- $portInUse = true;
- break;
- } catch (\Throwable $e) {
-
- continue;
- }
- }
- if ($portInUse) {
+ // Use the smart port checker that handles dual-stack properly
+ if ($this->isPortConflict($server, $port, $proxyContainerName)) {
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 {
@@ -126,4 +108,144 @@ class CheckProxy
return true;
}
}
+
+ /**
+ * 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' => 'which ss >/dev/null 2>&1',
+ 'check' => [
+ // Get listening process details
+ "ss_output=\$(ss -tuln state listening sport = :$port 2>/dev/null) && echo \"\$ss_output\"",
+ // Count IPv4 listeners
+ "echo \"\$ss_output\" | grep -c 'LISTEN.*:$port'",
+ ],
+ ],
+ // Set 2: Use netstat as alternative to ss
+ [
+ 'available' => 'which 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' => 'which 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($lines[0] ?? '');
+ $count = intval(trim($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;
+ }
+ }
}