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
}