diff --git a/503.php b/503.php
new file mode 100644
index 0000000..21d3cf2
--- /dev/null
+++ b/503.php
@@ -0,0 +1,164 @@
+
+
+
+
+
+
+ 404 β Seite nicht gefunden Β· Fabian Schieder
+
+
+
+
+
+
+
+
+
+
π
+
+
404
+
+
+
+
Seite nicht gefunden
+
+
+ Die Seite, die du suchst, existiert nicht (mehr)
+ oder wurde vielleicht verschoben.
+
+
+
+
+
+
+
+
diff --git a/includes/lib/server_status.php b/includes/lib/server_status.php
index fbab3d5..218c33c 100644
--- a/includes/lib/server_status.php
+++ b/includes/lib/server_status.php
@@ -99,13 +99,26 @@ function extract_http_status_code(array $headers): ?int
/**
* Decide if a HTTP status code means the service is reachable.
- * We treat 2xx/3xx as up and also 401/403 as up (service is there, but needs auth).
+ * Note: Redirects (3xx) are treated as not OK here because we intentionally don't follow redirects.
+ * Otherwise endpoints like /nextcloud (301 -> /nextcloud/ -> 503) would look "online".
*/
function is_reachable_http_code(?int $code): bool
{
if ($code === null) return false;
- if ($code >= 200 && $code < 400) return true;
+
+ // Explicitly DOWN: server-side failures like 503 Service Unavailable.
+ if ($code >= 500 && $code < 600) return false;
+
+ // UP: normal success
+ if ($code >= 200 && $code < 300) return true;
+
+ // UP: service is there but requires auth
if ($code === 401 || $code === 403) return true;
+
+ // UP: rate limited, but reachable
+ if ($code === 429) return true;
+
+ // Redirects are handled separately (we do not follow them)
return false;
}
@@ -181,12 +194,56 @@ function check_http_request(string $url, string $method, float $timeoutSeconds =
}
/**
- * HTTP check (no redirects). Try HEAD first, then a minimal GET fallback (some servers lie on HEAD).
+ * HTTP check (no redirects).
+ * - Tries HEAD first.
+ * - If result is a redirect, probes the Location once (still without following further redirects).
+ * - If HEAD fails, tries a minimal GET with Range.
* @return array{ok:bool, code:int|null, ms:int|null, error:string|null}
*/
function check_http_head(string $url, float $timeoutSeconds = 1.6): array
{
$head = check_http_request($url, 'HEAD', $timeoutSeconds);
+
+ // If we got a redirect, probe the Location target once, because we don't follow redirects automatically.
+ if ($head['code'] !== null && $head['code'] >= 300 && $head['code'] < 400) {
+ $loc = null;
+ if (isset($http_response_header) && is_array($http_response_header)) {
+ foreach ($http_response_header as $h) {
+ if (stripos((string)$h, 'Location:') === 0) {
+ $loc = trim(substr((string)$h, strlen('Location:')));
+ break;
+ }
+ }
+ }
+
+ if (is_string($loc) && $loc !== '') {
+ // Support relative redirects
+ if (str_starts_with($loc, '/')) {
+ $p = parse_url($url);
+ if (is_array($p) && !empty($p['scheme']) && !empty($p['host'])) {
+ $port = !empty($p['port']) ? ':' . (string)$p['port'] : '';
+ $loc = strtolower((string)$p['scheme']) . '://' . (string)$p['host'] . $port . $loc;
+ }
+ }
+
+ // Probe once; no further redirects.
+ $probe = check_http_request($loc, 'HEAD', $timeoutSeconds);
+ if ($probe['code'] === null) {
+ $probe = check_http_request($loc, 'GET', $timeoutSeconds, "Range: bytes=0-0\r\n");
+ }
+
+ // If the target is clearly reachable/unreachable, use that.
+ if ($probe['code'] !== null && ($probe['code'] < 300 || $probe['code'] >= 400)) {
+ return $probe;
+ }
+
+ // Otherwise keep the redirect info, but don't mark it UP.
+ return ['ok' => false, 'code' => $head['code'], 'ms' => $head['ms'], 'error' => 'Redirect'];
+ }
+
+ return ['ok' => false, 'code' => $head['code'], 'ms' => $head['ms'], 'error' => 'Redirect'];
+ }
+
if ($head['ok']) {
return $head;
}
diff --git a/index.php b/index.php
index 47c0efd..c6e8e4b 100644
--- a/index.php
+++ b/index.php
@@ -16,7 +16,7 @@ require_once __DIR__ . '/includes/lib/view_helpers.php';
// ββ Serverstatus (mit Cache) ββββββββββββββββββββββββββββββββββββββββββββββ
$cacheTtlSeconds = 30;
-$cacheKey = 'server_status_v2';
+$cacheKey = 'server_status_v3';
$cacheFile = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $cacheKey . '.json';
$serverStatus = null;
diff --git a/scripts/dump_headers.php b/scripts/dump_headers.php
new file mode 100644
index 0000000..86534df
--- /dev/null
+++ b/scripts/dump_headers.php
@@ -0,0 +1,44 @@
+\n");
+ exit(2);
+}
+
+$ctx = stream_context_create([
+ 'http' => [
+ 'follow_location' => 0,
+ 'max_redirects' => 0,
+ 'timeout' => 3.0,
+ 'ignore_errors' => true,
+ 'method' => 'GET',
+ 'header' => "User-Agent: HeaderDump/1.0\r\nRange: bytes=0-0\r\n",
+ ],
+ 'ssl' => [
+ 'verify_peer' => true,
+ 'verify_peer_name' => true,
+ ],
+]);
+
+$http_response_header = null;
+set_error_handler(static function (int $severity, string $message): bool {
+ fwrite(STDERR, "PHP warning captured: $message\n");
+ return true;
+});
+$fp = @fopen($url, 'rb', false, $ctx);
+restore_error_handler();
+if ($fp !== false) {
+ fclose($fp);
+}
+
+if (!isset($http_response_header) || !is_array($http_response_header)) {
+ echo "(no headers)\n";
+ exit(1);
+}
+
+foreach ($http_response_header as $h) {
+ echo $h, "\n";
+}
+
diff --git a/scripts/smoke_server_status.php b/scripts/smoke_server_status.php
index 700ca93..49eed8a 100644
--- a/scripts/smoke_server_status.php
+++ b/scripts/smoke_server_status.php
@@ -6,6 +6,7 @@ require __DIR__ . '/../includes/lib/server_status.php';
$targets = [
['url' => 'https://example.com'],
['url' => 'https://example.com/does-not-exist-hopefully'],
+ ['url' => 'https://fabianschieder.com/nextcloud'],
];
foreach ($targets as $t) {
@@ -15,5 +16,12 @@ foreach ($targets as $t) {
echo " error=", $r['error'];
}
echo PHP_EOL;
-}
+ // Zusatz: expliziter GET-Check (ohne Redirects), um 301/302 vs. 5xx besser zu sehen.
+ $g = check_http_request($t['url'], 'GET', 2.5, "Range: bytes=0-0\r\n");
+ echo " GET => ", ($g['ok'] ? 'UP' : 'DOWN'), " code=", ($g['code'] ?? 'null'), " ms=", ($g['ms'] ?? 'null');
+ if (!empty($g['error'])) {
+ echo " error=", $g['error'];
+ }
+ echo PHP_EOL;
+}