From 8822b608633a2a3556224e03bcd67917a90e6977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 8 Nov 2026 23:04:20 +0100 Subject: [PATCH] fastcgi: rewrite to index.php only if it exists (#8372) php_fastcgi used first_exist_fallback, which since v2.11.8 matches its final entry even when the file is absent. On a site without index.php, "/", directories with an index.html and missing files were all rewritten to the missing index.php, so file_server never served them. php_fastcgi now uses first_exist, as v2.11.7 effectively did since globbing skipped absent files. Explicitly configured first_exist_fallback keeps its semantics. The transport normalizes the path itself, so the dot-segment protection doesn't depend on this rewrite. Fixes #8367 --- .../php_fastcgi_handle_response.caddyfiletest | 1 - .../php_fastcgi_matcher.caddyfiletest | 3 +- .../php_fastcgi_subdirectives.caddyfiletest | 1 - ..._files_override_no_dir_index.caddyfiletest | 1 - caddytest/integration/phpfastcgi_test.go | 50 +++++++++++++++++++ .../reverseproxy/fastcgi/caddyfile.go | 11 +--- 6 files changed, 52 insertions(+), 15 deletions(-) diff --git a/caddytest/integration/caddyfile_adapt/php_fastcgi_handle_response.caddyfiletest b/caddytest/integration/caddyfile_adapt/php_fastcgi_handle_response.caddyfiletest index 84f4aed1f..9f6c49d54 100644 --- a/caddytest/integration/caddyfile_adapt/php_fastcgi_handle_response.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/php_fastcgi_handle_response.caddyfiletest @@ -58,7 +58,6 @@ "{http.request.uri.path}/index.php", "index.php" ], - "try_policy": "first_exist_fallback", "split_path": [ ".php" ] diff --git a/caddytest/integration/caddyfile_adapt/php_fastcgi_matcher.caddyfiletest b/caddytest/integration/caddyfile_adapt/php_fastcgi_matcher.caddyfiletest index 9d13b9884..c57d0dd82 100644 --- a/caddytest/integration/caddyfile_adapt/php_fastcgi_matcher.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/php_fastcgi_matcher.caddyfiletest @@ -73,8 +73,7 @@ php_fastcgi @test localhost:9000 "{http.request.uri.path}", "{http.request.uri.path}/index.php", "index.php" - ], - "try_policy": "first_exist_fallback" + ] } } ] diff --git a/caddytest/integration/caddyfile_adapt/php_fastcgi_subdirectives.caddyfiletest b/caddytest/integration/caddyfile_adapt/php_fastcgi_subdirectives.caddyfiletest index 55c2c4b28..ba8f6dd6d 100644 --- a/caddytest/integration/caddyfile_adapt/php_fastcgi_subdirectives.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/php_fastcgi_subdirectives.caddyfiletest @@ -59,7 +59,6 @@ php_fastcgi localhost:9000 { "{http.request.uri.path}/index.php5", "index.php5" ], - "try_policy": "first_exist_fallback", "split_path": [ ".php", ".php5" diff --git a/caddytest/integration/caddyfile_adapt/php_fastcgi_try_files_override_no_dir_index.caddyfiletest b/caddytest/integration/caddyfile_adapt/php_fastcgi_try_files_override_no_dir_index.caddyfiletest index 203ab3b63..dc4c3b886 100644 --- a/caddytest/integration/caddyfile_adapt/php_fastcgi_try_files_override_no_dir_index.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/php_fastcgi_try_files_override_no_dir_index.caddyfiletest @@ -32,7 +32,6 @@ php_fastcgi localhost:9000 { "{http.request.uri.path}", "index.php" ], - "try_policy": "first_exist_fallback", "split_path": [ ".php", ".php5" diff --git a/caddytest/integration/phpfastcgi_test.go b/caddytest/integration/phpfastcgi_test.go index 79371d8e5..08b33bc66 100644 --- a/caddytest/integration/phpfastcgi_test.go +++ b/caddytest/integration/phpfastcgi_test.go @@ -227,3 +227,53 @@ func TestPHPFastCGIDirectoryRedirectStaysOnOrigin(t *testing.T) { } } } + +// Without a PHP front controller, php_fastcgi must leave static files and +// directory indexes to file_server instead of sending them to a missing +// index.php (https://github.com/caddyserver/caddy/issues/8367). +func TestPHPFastCGIWithoutIndexPHP(t *testing.T) { + root := t.TempDir() + for name, content := range map[string]string{ + "index.html": "home", + "style.css": "css", + "about/index.html": "about", + } { + file := filepath.Join(root, filepath.FromSlash(name)) + if err := os.MkdirAll(filepath.Dir(file), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(file, []byte(content), 0o644); err != nil { + t.Fatal(err) + } + } + + backend := startFakePHPFPM(t) + + tester := caddytest.NewTester(t) + tester.InitServer(fmt.Sprintf(`{ + skip_install_trust + admin localhost:2999 + http_port 9080 + https_port 9443 + grace_period 1ns + } + + localhost:9080 { + root * %s + php_fastcgi %s + file_server + } + `, root, backend), "caddyfile") + + tester.AssertGetResponse("http://localhost:9080/", http.StatusOK, "home") + tester.AssertGetResponse("http://localhost:9080/about/", http.StatusOK, "about") + tester.AssertGetResponse("http://localhost:9080/style.css", http.StatusOK, "css") + tester.AssertGetResponse("http://localhost:9080/missing", http.StatusNotFound, "") + + // a front controller added later handles the paths that match no file + if err := os.WriteFile(filepath.Join(root, "index.php"), []byte("