--- 
canonical: 'https://mwop.net/blog/2025-03-21-caddy-php-fpm.html'
title: 'Handling PHP-FPM using Caddy'
author: "[Matthew Weier O'Phinney](https://mwop.net)"
created: '2025-03-21T11:59:06-05:00'
updated: '2025-03-21T11:59:06-05:00'
tags:
  - caddy
  - php
  - til

---
I've been using Caddy for years as a reverse proxy in front of other services, but recently wanted to use it to directly route traffic to PHP-FPM.
Caddy has a specialized reverse proxy directive for PHP-FPM, `php_fastcgi`, which seemed like it would do the trick, but I found that no traffic was ever getting routed to my FPM pool.





## What was happening

There's a [fantastic StackOverflow answer describing what happens](https://stackoverflow.com/a/77024172), and it's great reading.

The long and the short of it is that the `php_fastcgi` directive looks for matching files on the disk, and in the case of paths that look like directories, an `index.php` under that directory.
In other words, _it cannot match arbitrary paths (aka pretty URLs)_, just files.
Which doesn't work with basically any fraṁework-based application; as the author of that SO answer posits, the directive was likely written to target Wordpress.
Additionally, this causes issues if the PHP-FPM web pool is using a different root directory than you're using in your Caddy server, as Caddy will look for the files in the root defined for the server, and return a 404 if it can't find it, _without ever passing it on to PHP-FPM_.

## The solution

The solution is to use the standard `reverse_proxy` directive, and have it use the `fastcgi` transport.
You wrap this in a `handle` directive so that you can also specify a different filesystem root to pass to PHP-FPM.

As an example, let's assume:

- I'm running PHP-FPM on port 9000 of the host "app".
- The filesystem root for my PHP application is in `/var/www/app.example.org/public`
- I want to route all requests to `index.php` in that root so that they are handled by the application.

I can write the Caddyfile as follows:

```lua
app.example.org {
    # Note that this is the filesystem root for the _server_
    root * /static/app.example.org

    file_server

    handle {
        # This is the filesystem root for the FPM pool
        root * /var/www/app.example.org/public
        rewrite index.php
        reverse_proxy app:9000 {
            transport fastcgi {
                split .php
                capture_stderr
            }
        }
    }
}
```

If the file is not found in the filesystem, it will try to pass it on to PHP-FPM.
When it does, it rewrites to `index.php` in the PHP-FPM root (which is a different root than Caddy uses for its own file server!); PHP-FPM will use the matched path as the `PATH_INFO`, and your framework remains happy.
