diff --git a/tests/test.bats b/tests/test.bats
index ebb505f..035a54f 100644
--- a/tests/test.bats
+++ b/tests/test.bats
@@ -211,6 +211,12 @@ health_checks() {
assert_output --partial "x-ddev-403-source: ddev-webserver (frankenphp)"
assert_output --partial "
403: Forbidden"
+ run curl -s -D - https://${PROJNAME}.ddev.site/missing.php
+ assert_success
+ assert_output --partial "HTTP/2 404"
+ assert_output --partial "x-ddev-404-source: ddev-webserver (frankenphp)"
+ assert_output --partial "404: Not Found"
+
run ddev exec 'cd "/var/www/html/${DDEV_DOCROOT}" && mv index.php.bak index.php && rm -f forbidden.txt'
assert_success
diff --git a/web-build/Caddyfile.frankenphp b/web-build/Caddyfile.frankenphp
index 52b5729..47ed453 100644
--- a/web-build/Caddyfile.frankenphp
+++ b/web-build/Caddyfile.frankenphp
@@ -26,6 +26,22 @@
encode zstd br gzip
{$CADDY_SERVER_EXTRA_DIRECTIVES}
+ # Without index.php, serve files directly, so Caddy's 403 and 404 show the DDEV error pages.
+ # Caddy v2.11.8+ would send these requests to index.php even when it doesn't exist.
+ @noIndexFile {
+ not file index.php
+ not path *.php *.php/*
+ }
+ handle @noIndexFile {
+ file_server
+ }
+ @noIndexMissingScript {
+ not file index.php
+ path *.php *.php/*
+ not file {path}
+ }
+ error @noIndexMissingScript 404
+
# DDEV error pages (v1.25.4+) for 403/404 from Caddy itself, PHP responses pass through
handle_errors 403 404 {
@ddevErrorPage file {