Skip to content

fix(caddy): show DDEV 404 page without index.php on Caddy v2.11.8 - #77

Merged
stasadev merged 1 commit into
mainfrom
20261104_stasadev_caddy_no_index
Nov 4, 2026
Merged

stasadev merged 1 commit into
mainfrom
20261104_stasadev_caddy_no_index

Conversation

@stasadev

@stasadev stasadev commented Nov 4, 2026

Copy link
Copy Markdown
Member

Short Summary (TL;DR)

Caddy v2.11.8 sends every missing path to index.php, even when the docroot has no index.php, so FrankenPHP returned a PHP fatal error with status 200. Without index.php, the Caddyfile now serves files directly and returns 404 for missing PHP scripts, so the DDEV 403 and 404 pages show again.

The Issue

The scheduled tests for PHP 8.6 fail on all four jobs: https://github.com/ddev/ddev-frankenphp/actions/runs/41799786933

In a project without index.php, such as a new project with an empty docroot, every request returns status 200 with this output instead of the DDEV error page:

Warning: Unknown: Failed to open stream: No such file or directory in Unknown on line 0
Fatal error: Failed opening required '/var/www/html/index.php' (include_path='.:') in Unknown on line 0

The PHP 8.6.0RC5 FrankenPHP package from https://debs.henderkes.com/ is the first one built with Caddy v2.11.8. That Caddy release changes the first_exist_fallback try policy, which php_server uses, so that the last entry (index.php) always matches, even when the file doesn't exist (GHSA-f278-mgc7-46j3, see https://github.com/caddyserver/caddy/releases/tag/v2.11.8). FrankenPHP doesn't return 404 for a missing script, so PHP fails with status 200. The packages for PHP 8.2 to 8.5 still use Caddy v2.11.7, and they will get the same change when they are rebuilt.

How This PR Solves The Issue

When index.php doesn't exist in the docroot, common_config handles the request before php_server:

Request Result
A path that doesn't end in .php file_server serves it, or returns 403 or 404, which show the DDEV error pages
A .php path that doesn't exist 404 with the DDEV error page
A .php file that exists php_server runs it, as before

Nothing changes when index.php exists. The change works with Caddy v2.11.7 and v2.11.8.

With Caddy v2.11.8, a docroot without index.php returns the DDEV 404 page for /.

Manual Testing Instructions

Use PHP 8.6, because only its FrankenPHP package has Caddy v2.11.8 for now:

mkdir -p ~/tmp/frankenphp-no-index && cd ~/tmp/frankenphp-no-index
ddev config --project-type=php --php-version=8.6
ddev add-on get ddev/ddev-frankenphp --pr 77
ddev restart
ddev exec 'frankenphp version'
# Expect Caddy v2.11.8 in the output
curl -s -o /dev/null -w '%{http_code}\n' https://frankenphp-no-index.ddev.site/missing.html
# Expect 404, and the DDEV 404 page in a browser
curl -s -o /dev/null -w '%{http_code}\n' https://frankenphp-no-index.ddev.site/missing.php
# Expect 404, and the DDEV 404 page in a browser
echo '<?php echo "ok";' > index.php
curl -s https://frankenphp-no-index.ddev.site/some/route
# Expect "ok", because index.php gets all missing paths again
ddev delete -Oy frankenphp-no-index

Repeat with --php-version=8.5 to check the same results with Caddy v2.11.7.

Automated Testing Overview

The health checks now also request a missing .php file without index.php, and expect the DDEV 404 page.

Claude Code ran bats tests --filter-tags php86 --filter-tags php85 --filter-tags php84 on Linux with Docker on amd64, and all 5 tests passed. That covers Caddy v2.11.8 (PHP 8.6), Caddy v2.11.7 (PHP 8.4 and 8.5), worker mode, and a custom docroot. Nobody ran it on arm64 or with DDEV HEAD, which CI covers.

🤖 Developed with assistance from Claude Code

## Short Summary (TL;DR)

Caddy v2.11.8 sends every missing path to `index.php`, even when the docroot has no `index.php`, so FrankenPHP returned a PHP fatal error with status 200. Without `index.php`, the Caddyfile now serves files directly and returns 404 for missing PHP scripts, so the DDEV 403 and 404 pages show again.

## The Issue

The scheduled tests for PHP 8.6 fail on all four jobs: https://github.com/ddev/ddev-frankenphp/actions/runs/41799786933

In a project without `index.php`, such as a new project with an empty docroot, every request returns status 200 with this output instead of the DDEV error page:

```text
Warning: Unknown: Failed to open stream: No such file or directory in Unknown on line 0
Fatal error: Failed opening required '/var/www/html/index.php' (include_path='.:') in Unknown on line 0
```

The PHP 8.6.0RC5 FrankenPHP package from https://debs.henderkes.com/ is the first one built with Caddy v2.11.8. That Caddy release changes the `first_exist_fallback` try policy, which `php_server` uses, so that the last entry (`index.php`) always matches, even when the file doesn't exist (GHSA-f278-mgc7-46j3, see https://github.com/caddyserver/caddy/releases/tag/v2.11.8). FrankenPHP doesn't return 404 for a missing script, so PHP fails with status 200. The packages for PHP 8.2 to 8.5 still use Caddy v2.11.7, and they will get the same change when they are rebuilt.

## How This PR Solves The Issue

When `index.php` doesn't exist in the docroot, `common_config` handles the request before `php_server`:

| Request | Result |
| --- | --- |
| A path that doesn't end in `.php` | `file_server` serves it, or returns 403 or 404, which show the DDEV error pages |
| A `.php` path that doesn't exist | 404 with the DDEV error page |
| A `.php` file that exists | `php_server` runs it, as before |

Nothing changes when `index.php` exists. The change works with Caddy v2.11.7 and v2.11.8.

With Caddy v2.11.8, a docroot without `index.php` returns the DDEV 404 page for `/`.

## Manual Testing Instructions

Use PHP 8.6, because only its FrankenPHP package has Caddy v2.11.8 for now:

```bash
mkdir -p ~/tmp/frankenphp-no-index && cd ~/tmp/frankenphp-no-index
ddev config --project-type=php --php-version=8.6
ddev add-on get ddev/ddev-frankenphp --pr 77
ddev restart
ddev exec 'frankenphp version'
# Expect Caddy v2.11.8 in the output
curl -s -o /dev/null -w '%{http_code}\n' https://frankenphp-no-index.ddev.site/missing.html
# Expect 404, and the DDEV 404 page in a browser
curl -s -o /dev/null -w '%{http_code}\n' https://frankenphp-no-index.ddev.site/missing.php
# Expect 404, and the DDEV 404 page in a browser
echo '<?php echo "ok";' > index.php
curl -s https://frankenphp-no-index.ddev.site/some/route
# Expect "ok", because index.php gets all missing paths again
ddev delete -Oy frankenphp-no-index
```

Repeat with `--php-version=8.5` to check the same results with Caddy v2.11.7.

## Automated Testing Overview

The health checks now also request a missing `.php` file without `index.php`, and expect the DDEV 404 page.

Claude Code ran `bats tests --filter-tags php86 --filter-tags php85 --filter-tags php84` on Linux with Docker on amd64, and all 5 tests passed. That covers Caddy v2.11.8 (PHP 8.6), Caddy v2.11.7 (PHP 8.4 and 8.5), worker mode, and a custom docroot. Nobody ran it on arm64 or with DDEV HEAD, which CI covers.

🤖 Developed with assistance from [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@stasadev
stasadev merged commit 3e180a9 into main Nov 4, 2026
16 checks passed
@stasadev
stasadev deleted the 20261104_stasadev_caddy_no_index branch November 4, 2026 09:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant