The most surprising thing about serving PHP with Caddy is that you don’t actually need to install PHP itself on the Caddy server.

Let’s see Caddy in action. Imagine you have a simple PHP file, index.php, in a directory called /var/www/myphpapp:

<?php
echo "Hello from PHP, running on Caddy!";
?>

Your Caddyfile, located at /etc/caddy/Caddyfile, might look like this:

:80 {
    root * /var/www/myphpapp
    file_server
    php_fastcgi unix//run/php/php7.4-fpm.sock
}

When a browser requests http://your-server-ip/, Caddy intercepts it. It sees the php_fastcgi directive and knows it needs to send the request to a PHP-FastCGI process. Caddy forwards the request to the specified socket, /run/php/php7.4-fpm.sock. The PHP-FPM process, listening on that socket, executes index.php. The output ("Hello from PHP, running on Caddy!") is sent back through the socket to Caddy, which then sends it to the browser.

Here’s the breakdown of how this works and the mental model you need:

  1. Caddy as a Reverse Proxy/Web Server: Caddy’s primary role here is to receive HTTP requests from clients (browsers) and intelligently route them. It’s not executing the PHP code itself.

  2. root Directive: This tells Caddy where to find your website’s files. In our example, it’s /var/www/myphpapp. When a request comes in for /, Caddy looks for index.php (or index.html, etc., depending on file_server’s behavior) within that root directory.

  3. file_server Directive: This tells Caddy to serve static files directly if a matching file is found and no other handler matches. For PHP, this is less relevant for the .php files themselves, but it’s crucial for serving your static assets (CSS, JS, images).

  4. php_fastcgi Directive: This is the magic. It tells Caddy to pass any request that looks like it should be handled by PHP to a separate PHP process manager.

    • unix//run/php/php7.4-fpm.sock: This is the communication channel. Caddy connects to this Unix domain socket. PHP-FPM (FastCGI Process Manager) is the most common way to run PHP in production. It manages a pool of PHP worker processes that are ready to execute PHP code. Caddy communicates with PHP-FPM via the FastCGI protocol over this socket.

The key is that Caddy delegates the PHP execution. It doesn’t need the PHP interpreter installed on the same machine, or at least, it doesn’t need to know where it is directly. It just needs a way to talk to a FastCGI-compatible service. This allows you to:

  • Keep your web server and PHP processes separate for better security and resource management.
  • Use different PHP versions by simply pointing php_fastcgi to a different socket managed by a different PHP-FPM pool.
  • Scale your PHP backend independently of your Caddy frontend.

The php_fastcgi directive is incredibly flexible. You can also specify a TCP address like php_fastcgi 127.0.0.1:9000 if your PHP-FPM is configured to listen on a network port instead of a Unix socket. Caddy handles both.

What most people don’t realize is that the php_fastcgi directive in Caddy automatically handles setting environment variables that PHP expects, like SCRIPT_FILENAME, REQUEST_METHOD, QUERY_STRING, and so on, based on the incoming HTTP request. You don’t have to manually configure these for the FastCGI protocol itself; Caddy does it for you.

Once you have PHP serving correctly, the next thing you’ll want to tackle is configuring caching for your PHP application to improve performance.

Want structured learning?

Take the full Caddy course →