@marcel91200 / caddy Mirror · read-only / commits / 8822b608
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 10, 23:45 UTC

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

Kévin Dunglas committed Nov 8, 2026 at 23:04 UTC 8822b608633a2a3556224e03bcd67917a90e6977
6 files changed +52 -15
caddytest/integration/caddyfile_adapt/php_fastcgi_handle_response.caddyfiletest
-1
@@ -58,7 +58,6 @@
58 "{http.request.uri.path}/index.php",
59 "index.php"
60 ],
61 - "try_policy": "first_exist_fallback",
61 "split_path": [
62 ".php"
63 ]
caddytest/integration/caddyfile_adapt/php_fastcgi_matcher.caddyfiletest
+1 -2
@@ -73,8 +73,7 @@ php_fastcgi @test localhost:9000
73 "{http.request.uri.path}",
74 "{http.request.uri.path}/index.php",
75 "index.php"
76 - ],
77 - "try_policy": "first_exist_fallback"
76 + ]
77 }
78 }
79 ]
caddytest/integration/caddyfile_adapt/php_fastcgi_subdirectives.caddyfiletest
-1
@@ -59,7 +59,6 @@ php_fastcgi localhost:9000 {
59 "{http.request.uri.path}/index.php5",
60 "index.php5"
61 ],
62 - "try_policy": "first_exist_fallback",
62 "split_path": [
63 ".php",
64 ".php5"
caddytest/integration/caddyfile_adapt/php_fastcgi_try_files_override_no_dir_index.caddyfiletest
-1
@@ -32,7 +32,6 @@ php_fastcgi localhost:9000 {
32 "{http.request.uri.path}",
33 "index.php"
34 ],
35 - "try_policy": "first_exist_fallback",
35 "split_path": [
36 ".php",
37 ".php5"
caddytest/integration/phpfastcgi_test.go
+50
@@ -227,3 +227,53 @@ func TestPHPFastCGIDirectoryRedirectStaysOnOrigin(t *testing.T) {
227 }
228 }
229 }
230 +
231 +// Without a PHP front controller, php_fastcgi must leave static files and
232 +// directory indexes to file_server instead of sending them to a missing
233 +// index.php (https://github.com/caddyserver/caddy/issues/8367).
234 +func TestPHPFastCGIWithoutIndexPHP(t *testing.T) {
235 + root := t.TempDir()
236 + for name, content := range map[string]string{
237 + "index.html": "home",
238 + "style.css": "css",
239 + "about/index.html": "about",
240 + } {
241 + file := filepath.Join(root, filepath.FromSlash(name))
242 + if err := os.MkdirAll(filepath.Dir(file), 0o755); err != nil {
243 + t.Fatal(err)
244 + }
245 + if err := os.WriteFile(file, []byte(content), 0o644); err != nil {
246 + t.Fatal(err)
247 + }
248 + }
249 +
250 + backend := startFakePHPFPM(t)
251 +
252 + tester := caddytest.NewTester(t)
253 + tester.InitServer(fmt.Sprintf(`{
254 + skip_install_trust
255 + admin localhost:2999
256 + http_port 9080
257 + https_port 9443
258 + grace_period 1ns
259 + }
260 +
261 + localhost:9080 {
262 + root * %s
263 + php_fastcgi %s
264 + file_server
265 + }
266 + `, root, backend), "caddyfile")
267 +
268 + tester.AssertGetResponse("http://localhost:9080/", http.StatusOK, "home")
269 + tester.AssertGetResponse("http://localhost:9080/about/", http.StatusOK, "about")
270 + tester.AssertGetResponse("http://localhost:9080/style.css", http.StatusOK, "css")
271 + tester.AssertGetResponse("http://localhost:9080/missing", http.StatusNotFound, "")
272 +
273 + // a front controller added later handles the paths that match no file
274 + if err := os.WriteFile(filepath.Join(root, "index.php"), []byte("<?php // front controller"), 0o644); err != nil {
275 + t.Fatal(err)
276 + }
277 + tester.AssertGetResponse("http://localhost:9080/missing", http.StatusOK, "SCRIPT_FILENAME="+filepath.Join(root, "index.php"))
278 + tester.AssertGetResponse("http://localhost:9080/style.css", http.StatusOK, "css")
279 +}
modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
+1 -10
@@ -19,7 +19,6 @@ import (
19 "net/http"
20 "slices"
21 "strconv"
22 - "strings"
22
23 "github.com/caddyserver/caddy/v2"
24 "github.com/caddyserver/caddy/v2/caddyconfig"
@@ -140,10 +139,9 @@ func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
139 // }
140 // redir @canonicalPath {path}/ 308
141 //
143 -// # If the requested file does not exist, try index files and assume index.php always exists
142 +// # If the requested file does not exist, try index files
143 // @indexFiles file {
144 // try_files {path} {path}/index.php index.php
146 -// try_policy first_exist_fallback
145 // split_path .php
146 // }
147 // rewrite @indexFiles {http.matchers.file.relative}
@@ -317,18 +315,12 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
315 if indexFile != "off" {
316 var dirRedir bool
317 dirIndex := "{http.request.uri.path}/" + indexFile
320 - tryPolicy := "first_exist_fallback"
318
319 // if tryFiles wasn't overridden, use a reasonable default
320 if len(tryFiles) == 0 {
321 tryFiles = []string{"{http.request.uri.path}", dirIndex, indexFile}
322 dirRedir = true
323 } else {
327 - if !strings.HasSuffix(tryFiles[len(tryFiles)-1], ".php") {
328 - // use first_exist strategy if the last file is not a PHP file
329 - tryPolicy = ""
330 - }
331 -
324 dirRedir = slices.Contains(tryFiles, dirIndex)
325 }
326
@@ -370,7 +362,6 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
362 rewriteMatcherSet := caddy.ModuleMap{
363 "file": h.JSON(fileserver.MatchFile{
364 TryFiles: tryFiles,
373 - TryPolicy: tryPolicy,
365 SplitPath: extensions,
366 }),
367 }