Problem/Motivation

We should introduce a Caddyfile configuration to enable Drupal to be served by caddy. It would also make it possible to use FrankenPHP easily, and benefit from all it's features such as, server sent events, early hints to load assets faster, etc.

Later, once #2218651: [meta] Make Drupal compatible with persistent app servers like ReactPHP, PHP-PM, PHPFastCGI, FrankenPHP, Swoole is in a good state, we can enable the worker mode of FrankenPHP and improve performance. The end goal would be to recommend FrankenPHP as the way to deploy Drupal on production.

We can also make use of FrankenPHP static binary to provide a PHP-cli and make it possible to use and serve Drupal (including composer and drush) from an environement that does not have PHP installed. From what I tried the following works:

# download the relevant binary for you system from https://github.com/dunglas/frankenphp/releases
# download composer https://getcomposer.org/download/
$ frankenphp php-cli ~/opt/bin/composer create-project drupal/recommended-project look_no_hands
$ cd look_no_hands
$ frankenphp php-cli ~/opt/bin/composer require drush/drush
$ cd web
$ frankenphp run

You have a Drupal up and running on a system without PHP installed, you used composer and drush works. While this is out of scope for this issue, it is to give an idea about how it can be used and the relevance of adding a Caddyfile to core. There is a docker way of doing all this, but I wanted to highlight the simplest way it can be made to work.

Proposed resolution

Add a Caddyfile to core, next to .htaccess, taking inspiration from the frankenphp-drupal config.

Remaining tasks

Release notes snippet

TODO

Issue fork drupal-3437187

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

nod_ created an issue. See original summary.

nod_’s picture

Issue summary: View changes

nod_’s picture

Issue summary: View changes
Status: Active » Needs review

I've added a MR with a proof of concept showing how we can use the early hints feature to make JS/CSS load faster, improving performance.
The assets are in the headers of the first response, the browser does not need to parse the HTML to start loading assets, making the page show up faster. It's pretty significant when throttling the browser on a slow 3G connection.

Another improvement we could make is using server sent events (using the build-in mercure hub) to replace some of the bigpipe implementation, that could help simplify the code and workarounds made for things like #3387039: Large placeholders are not processed.

cmlara’s picture

It appears this might conflict with the new standard from the Core team and Drupal Security Team attempting to slim down on the amount of security surface area by removing supported servers types.

This might need to instead just be documentation only?

nod_’s picture

My personal end goal is to get everyone to use FrankenPHP in worker mode because I believe it'll make it cheaper to setup and host Drupal as well as be more performant.

It could address the "Server included" and the "Drupal.exe" use cases of Can Drupal scale down?. The built in mercure server could be used and relied on by contrib to provide more real-time features and make modules such as DrupalChat performant by default.

I do not think we should go the documentation only route. As a first step why not, but there is more to it than that. If we make the work to have Drupal compatible with "application servers" we should get something more than "just" performance for it.

mstrelan’s picture

I think part of dropping IIS and Windows support is that we can't test on it. Is it possible, or is there an existing issue, where we can run caddy in gitlab ci?

nod_’s picture

I would be surprised if we couldn't, it's linux friendly, there is a static binary that works everywhere or a docker image for it.

catch’s picture

Yeah I came here to post the same thing as #8, if we can add a frankenphp gitlab environmnet and run that on commit, that would ensure that what we're providing will actually work when people try to use it.

nod_’s picture

yeah i'll need help for that

dunglas’s picture

Hi,

FrankenPHP author and Caddy maintainer here.

At Les-Tilleuls.coop, we have internal GitLab CI pipelines that use FrankenPHP.
Symfony has a GitHub Actions workflow using it too: https://github.com/symfony/symfony/blob/4b659cbe4c140e5fb73961397e18964b...

This should be straightforward to add. Let me know if I can help doing this!

goz’s picture

If it can help pushing this issue, here is some quick perf tests of Drupal with frankenphp : https://www.iosan.fr/en/blog/what-does-frankenphp-and-drupal-have-say-ab...

Summary is :
NGINX+PHP-FPM : ~ 191ms
FrankenPHP : ~ 10ms

nod_’s picture

Tried to look into making testbot/ddev use frankenphp. We need someone who know those things to help out, I don't have the time to figure it out, I can help with how to configure frankenphp (to some extend) but the rest is above my head.

I've been running the functional tests (not the JS ones yet) with the static frankenphp version. There are failures due to different handling of headers by caddy, something with authorization headers etc. It's going to take a while to run all this locally.

nod_’s picture

Seems there is already an issue on DDEV side: https://github.com/ddev/ddev/issues/5655

catch’s picture

The results in https://www.iosan.fr/en/blog/what-does-frankenphp-and-drupal-have-say-ab... are a bit confusing - if frankenphp is responding in about 1/20th the time, why is it only serving 565 calls vs. 483? You would expect it to be able to serve a much higher number of calls in the same time. Or if it's similar to the n500 in apache bench, you'd expect the calls to be identical.

It's also not clear if this is testing anonymous users or not, average response time of 191ms with Drupal's internal page cache would suggest a misconfiguration somewhere, it should be <10ms if page caching is working. If it's auth users or with page caching disabled, that would make more sense, but I'd still want to know why the number of requests served doesn't differ as much as the response time.

nod_’s picture

StatusFileSize
new39.3 KB

I'll stop the run here, gotta use my computer for other things.

$ ../vendor/bin/phpunit --testsuite functional
PHPUnit 9.6.15 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.3.4
Configuration: /home/theodore/DEV/DRUPAL/drupal/core/phpunit.xml

Testing 
..FSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSS   61 / 4439 (  1%)
SFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSS  122 / 4439 (  2%)
FSSSFSSSFSSSFSSSFSSSFSSSFF.......................E...........  183 / 4439 (  4%)
.............E..EEEE......E...E.E..EEEEE.E.......E..E..E.E.E.  244 / 4439 (  5%)
..EE.EEE.EEEE.EEE..EEE.......................................  305 / 4439 (  6%)
...........................EEF...............................  366 / 4439 (  8%)
.....E........FSSSFSSSFSSSFSSSFSSSFSSS........E.E..E.....EE..  427 / 4439 (  9%)
...EE.........EEE.....FSSSFSSSFSSSFSSSFSSSFSSS...EE..E.......  488 / 4439 ( 10%)
.FSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSFSSSE.....E.....  549 / 4439 ( 12%)
...E..........EE...E..........E......SSFSSFFSSSFSSSSSFSSFFSSS  610 / 4439 ( 13%)
FSSSSSFSSFF.FFFFFSSSFSSSF.FFFFSSFFFF.........................  671 / 4439 ( 15%)
..................E.....E.E......FE..F...E...................  732 / 4439 ( 16%)
..........E.........FFE.........E....E...^C

Attached are the details of the failures.There is a problem with the installer too apparently.

nod_’s picture

Tried to make a ddev addon but failing so far: https://github.com/theodoreb/ddev-frankenphp-drupal
Problem is with

Error: loading initial config: loading new config: loading http app module: provision http: loading pki app module: provision pki: provisioning CA 'local': generating root: saving root certificate: mkdir /data/caddy/pki: permission denied

If anyone has an idea/fix that'd be great, ping me on slack directly to chat

goz’s picture

@catch i agree with you, the number of calls does not reflect the difference of response time.
As explained in blog post, both tests are done with minimum configuration :

- a fresh drupal installation with default umami profile.
- no differences between the two jmeter scenarios

I don't have dedicated servers to make real tests, but i can at least confirm i tested in the same way both stacks.
I'm not expert about jmeter, so if someone want to improve those tests, feel free to contribute !

Tests have been done with anonymous users, requesting home page.

catch’s picture

I had a quick re-read of the post to see if there answer for the discrepancy was in there:

and searched for the "Vegan" string on performance tests for 600 seconds with an HTTP call (no HTTPS).

This might explain things - say nginx could serve 30 x 1 second requests per second, that's 18,000 requests in 600 seconds.

But if frankenphp can only serve 10 x 100ms requests per second, that'd be 6,000 even if each individual request is ten times as fast.

Possible changes to make to the load test:
1. Create a regular authenticated user in Umami, and get jmeter to add their session cookie to the request (I haven't used jmeter for years now but there must be docs for this). This means a lot more PHP to execute, making any network traffic variation less of a variable.

2. Go down to single concurrency, so it's how many end-to-end requests can be done in the 600s, this will eliminate any concurrency limits that might be in place in server config and a lot of hardware/resource bottlenecks too.

mstrelan’s picture

This issue feels it should have a meta for discussing the merits of frankenphp and another issue for gitlab ci, so we can focus this issue on the caddyfile.

nod_’s picture

It's going to take time for all this, I enjoy a bit of chaos at the start :) I don't want to split things up too early and dissipate the interest.

Technically it should go to the ideas queue to discuss merits, then a plan and all the proper issues in the proper places set up. Just wanted to shake things up and see if some people already got it without all the detailed explanations, once we have something more solid it'll be time to go into more details about what that all means. Just getting this in drupalci will be a challenge, ddev is more realistic and we can find the problems there first. If we know there is a deal-breaker in how this works there is no point in spending DA time getting the CI sorted out.

andypost’s picture

For CI it needs another image a-la https://git.drupalcode.org/project/drupalci_environments/-/tree/dev/php/...
Then it will be much easier to add extra CI run

PS: Let's file issue to project/drupalci_environments

mglaman’s picture

Would it be possible to hack on this in an experimental GitHub repo using GitHub actions without having to wait for a CI image?

goz’s picture

@catch i figured out where i was wrong.

I misconfigured my Drupal settings for the nginx+php-fpm (no cache).

I update the blog post to fix this.

I make tests again as anonymous with all caches disabled (Drupal + twig) to enforce calculation.
And another as anonymous with all caches enabled.

Jmeter has been configured to launch only one thread at time, with no delay between two threads.

Summary is :

No cache :
NGINX+PHP-FPM : ~ 202ms
FrankenPHP : ~ 226ms

Cache enabled :
NGINX+PHP-FPM : ~ 5.43ms
FrankenPHP : ~ 6.36ms

Otherwise, something is still wrong since there is a big difference between the time with/without cache and the number of request with/without cache.

nod_’s picture

Got a working ddev setup:

ddev get theodoreb/ddev-frankenphp-drupal, and it should serve your project with frankenphp, repo here https://github.com/theodoreb/ddev-frankenphp-drupal/tree/main it's using the static build since I just can't seem to make the frankenphp docker image work. Relaunched the tests, much less problems.

nod_’s picture

catch’s picture

I think you mentioned some of the core test failures are due to different headers being sent by frankenphp vs. apache, I think I've seen this with nginx and ddev too at least a couple of times too.

mstrelan’s picture

I've been running core's functional tests with FrankenPHP and this Caddyfile on my localhost and getting pretty good results. At first there were many failing tests due to the self signed certificate, but disabling HTTPS resolved those. Currently I'm down to the below list of fails:

Unexpected status code

  • \Drupal\Tests\ckeditor5\Functional\ImageUploadTest::testUploadFileExtension - getting 403 instead of 422
  • \Drupal\Tests\ckeditor5\Functional\ImageUploadAccessTest::testCkeditor5ImageUploadRoute - getting 403 instead of 201
  • \Drupal\Tests\ckeditor5\Functional\ImageUploadAccessTest - getting 403 instead of 422
  • \Drupal\Tests\file\Functional\DownloadTest::testFileCreateUrl - getting 404 instead of 200
  • \Drupal\Tests\rest\Functional\Views\StyleSerializerTest::testResponseFormatConfiguration - getting 200 instead of 406
  • \Drupal\Tests\system\Functional\CsrfRequestHeaderTest::testRouteAccess - getting 200 instead of 403
  • \Drupal\Tests\system\Functional\System\HtaccessTest::testFileAccess - getting 200 instead of 403

Unexpected http headers

  • \Drupal\FunctionalTests\WebAssertTest::testResponseHeaderExists - Failed asserting that the response has a 'Null-Header' header.
  • \Drupal\FunctionalTests\WebAssertTest::testResponseHeaderDoesNotExist - AssertionFailedError not thrown
  • \Drupal\Tests\jsonapi\Functional\MessageTest::testPostIndividual - unexpected allow header value
  • \Drupal\Tests\system\Functional\Routing\RouterTest::testFinishResponseSubscriber - no vary header
  • \Drupal\Tests\system\Functional\System\HtaccessTest::testSvgzContentEncoding - missing gzip for x-encoded-content-encoding

Unit test fails

  • \Drupal\Tests\Scripts\TestSiteApplicationTest::testInstallScript - BadRequestHttpException
  • \Drupal\Tests\Scripts\TestSiteApplicationTest::testInstallInDifferentLanguage - non-zero exit code

Other issues

  • \Drupal\Tests\mysql\Functional\Mysql8RequirePrimaryKeyUpdateTest::testDatabaseLoaded - missing db privileges
  • \Drupal\Tests\update\Functional\FileTransferAuthorizeFormTest::testViaAuthorize - page missing "Files were added successfully" message
  • \Drupal\Tests\update\Functional\UpdateUploadTest::testUploadModule - Current page is "/core/authorize.php/core/authorize.php", but "/core/authorize.php" expected.
nod_’s picture

way less failures than i feared. I have a problem installing a site too so that makes sense the tests fail, thanks for testing that!

I'm nowhere regarding the gitlab ci stuff in #3438767: Support FrankenPHP as a webserver I don't really have time to spend on it and I'm not even sure where to start so any help would be welcome :)

nod_’s picture

mstrelan’s picture

way less failures than i feared. I have a problem installing a site too so that makes sense the tests fail, thanks for testing that!

I hadn't run FunctionalJavascript tests yet but now that I've started to run some they seem to mostly be passing, only failing on similar issues to the Functional tests.

Started having a look at the first of those fails mentioned in #29. It's expecting a 422 but getting a 403. The interesting thing is that if I repeat the test 10 times it passes 5 five times. So it's definitely capable of sending a 422, but for some reason it's not consistent.

$ phpunit -c core/phpunit.xml.dist core/modules/ckeditor5/tests/src/Functional/ImageUploadTest.php  --filter=testUploadFileExtension --repeat=10
PHPUnit 9.6.19 by Sebastian Bergmann and contributors.

Testing 
F.FF...F.F

It's possible that some of random files created by $this->getTestFiles are causing issues and others aren't. Would need to investigate a little further and try to remove the randomness.

I'm nowhere regarding the gitlab ci stuff in #3438767: Support FrankenPHP as a webserver I don't really have time to spend on it and I'm not even sure where to start so any help would be welcome :)

I've also been thinking about gitlab ci but need some more time. Perhaps as a starting point we could just add the frankenphp static binary to an existing image (like drupalci/php-8.2-cli) and work to build our own image over time

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new1.57 KB

The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

mstrelan’s picture

Not sure the Caddyfile supports running in a subdirectory. I have tests running and somewhat passing in Github Actions at https://github.com/mstrelan/drupalci-frankenphp but if I move to a subdirectory it doesn't work. Also struggled to get this working on Drupal Gitlab CI, I think mostly due to file permissions and ownership, but I'll post more about that on #3438767: Support FrankenPHP as a webserver.

andypost’s picture

btw PHP 8.3.11 fixing lots of memory leaks so it makes sense to rebuild the image, gonna add drupalci image this weekend based on https://github.com/mstrelan/drupalci-frankenphp

renrhaf’s picture

Hi there, coming from here https://github.com/dunglas/frankenphp-drupal/issues/1#issuecomment-23439... just to say that we are using FrankenPHP on a few Drupal projects in production for a few months now. We had some issues related to BigPipe as FrankenPHP has a known issue with PHP Fibers, but appart from that things are running smoothly. We can not use the worker mode yet as the Symfony Runtime is not supported by Drupal as of now.

FrankenPHP got some good performance boosts this summer so the performance tests that were done might be giving different results now.
I'm not sure how to help here to make things go forward.

EDIT: I've created an example repo of what kind of configuration we are using. It is a dockerized Drupal 11 with FrankenPHP. See https://github.com/Renrhaf/drupal11-frankenphp

nod_’s picture

A blog post with some details/benchmarks would help :)

andypost’s picture

As tests running in subdirectory most of them failed, so config should be tuned for CI

el7cosmos’s picture

looking at the failed test

cURL error 7: Failed to connect to localhost port 80 after 0 ms: Couldn't connect to server

this seems like the frankenphp server not started properly.

andypost’s picture

both brotli and zstd supported out of box https://externals.io/message/127347

andypost’s picture

steinmb’s picture

https://thephp.foundation/blog/2025/06/08/php-30/ - 30 years of PHP: FrankenPHP is now part of the PHP organisation

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

andypost’s picture

Added 8.5-ubuntu-frankenphp:dev image also added note about subdir but looks it's not enough yet

catch’s picture

This would allow us to test #3570909: Support early hints for fonts - functionality isn't yet supported in core PHP.