Php-fastcgi only serving .php files or wildcard possible


I might have a misunderstanding in using php-fastcgi directive, but maybe someone can point me in the right direction… :slight_smile:

I am using a PHP Laravel app in php_fpm Docker container, similar to the Example from the Wiki: Example: Docker laravel

When I now call the Laravel app through Caddy (2.4.5), I experience following:

  • I am successful when calling /index.php directly.
  • If i try /api or any other directory, I get back 200 OK from Caddy,
  • If i try /test.php or any other php file I get a 404 from php_fpm

So in other words, Caddy only forwards requests with *.php to the php_fpm container.

Is this the desired behaviour of Caddy?

If not, i’ll definitely dive deeper and probably come back with a filled out error template. :slight_smile:

FYI, here is my caddy config: {
	root * /var/www/public
	php_fastcgi core-test:9000

There is no difference if i have the file_server command in or not…?

Many thanks in advance


Yes. The file_server will serve any other files from your root path.

1 Like

To further explain, it’s a good idea to look at the expanded form of the php_fastcgi directive:

So basically:

  • The first chunk deals with redirecting requests where the path is a directory on disk which contains an index.php file. This also does nothing if the path already ends with a /. So for example it would redirect the request to path /foo to /foo/ (appending a /, to canonicalize the path to the directory), if /foo/index.php exists on disk.

  • The next chunk deals with doing path rewrites depending on whether a matching file exists on disk, and has the side-effect of remembering the part of the path after .php if the path included .php in it (this is important for the environment variables sent to your php-fpm server, so it works properly).

    • First it checks if {path} is a file that exists on disk. If so, it rewrites to that path. This essentially just short circuits the rest, and makes sure that requests to files that do exist on disk don’t get otherwise rewritten (see next steps below). So if you have a /js/app.js file on disk, then it will keep that path the same.
    • Second, it checks if {path}/index.php is a file that exists on disk. If so, it rewrites to that path. So for requests to a directory like /foo/ then it’ll look for /foo//index.php, normalize that to /foo/index.php, and rewrite to that if it exists. This is sometimes useful if you’re running a PHP app inside of another.
    • Lastly, it’ll rewrite to index.php, if that exists (it almost always should for modern PHP apps). This allows your PHP app to handle any request for paths that don’t map to files on disk, by using index.php as the entrypoint.
  • Then finally, the last chunk of config is what actually proxies the request to your php-fpm service to actually run your PHP code. It only proxies requests to paths that end with .php, so any file that isn’t a PHP file, and that does exist on disk, will not be handled by this and will fall though.

The php_fastcgi directive is not enough on its own, it should be paired with file_server to allow Caddy to serve your static files (JS, CSS, images, etc) which weren’t otherwise handled by the logic in php_fastcgi and fell through.

So to answer your questions, yes, that’s all working as intended.

  • If you make a request to /api, then it will be sent through your index.php, Laravel’s entrypoint, and go through your configured routes in Laravel. If you do have a defined route to handle /api, then Laravel will probably respond with a 200, and Caddy will proxy that back to the client.
  • A request to /test.php, when that file doesn’t exist, will get rewritten to index.php, and hit your Laravel entrypoint just the same. If you have no route in Laravel to handle that path, then Laravel will probably return a 404. If /test.php did exist though, then Caddy would ask php-fpm to run that script. It wouldn’t reach Laravel probably (unless you made it look like the contents of index.php)
  • Caddy will handle responding to any static file requests, and those won’t reach php-fpm and Laravel.

Many, many thanks a lot for the detailed explanation. I started debugging with the expanded form yesterday, but this makes it a lot easier to follow.

I did run through it step by step now, and I think I found the issue:

I’m running Caddy and the Laravel/FPM in two different Docker containers. (Idea is to use Caddy as proxy for multiple services in the future.)

If I follow the expanded form step by step, i think that Caddy’s try_files directive in the @indexFiles matcher is not matching the index.php (or any other file) since Caddy does not have access to the file system in the other container.

Just to confirm: There is probably no way to make try_files work across containers without sharing volumes?

Otherwise I’m probably going to define a very simple path matcher (e.g. /api/* and some other routes in my case) to rewrite the path to index-php before the reverse proxy, which should do the job for a Laravel Backend application.

Thanks again!!!

PS: Your explanation from above is maybe something worth to be added to the documentation as well. Helped me a lot to check if my understanding was right or not, when going through the code.

Yeah, when running in Docker, both Caddy and php-fpm need access to the files for your app, and should be using the same root path (because Caddy tells pjp-fpm which file to run by absolute path). Caddy needs the files for try_files and file_server to work properly, and php-fpm needs them to actually run the PHP code.

Thanks a lot for confirmation and your help. Learned a lot yesterday and today. :slight_smile:

I’ll mark the second post as solution. Think that’s most valuable for other users as well.

For those that might be interested in my way forward:

I’m probably setting up the Laravel/php_fpm app as microservice, which purely communicates via API (so no static assest). Even blade views, etc. would be no problem, since every request will go through PHP.

Following Caddyfile configuration should do the job:

root * /app/public
rewrite * /index.php
reverse_proxy /index.php {
  transport fastcgi {
    split .php

(code its coming from a local POC, of course I’m going to switch to the build in HTTPS :wink: and the IP is going to be replaced by the container name…)

I’m not sure I see the benefit of limiting that, but sure :man_shrugging: