Learning Caddy - PHP-FPM Integration
Episode 24 of 31

Learning Caddy - PHP-FPM Integration

This episode covers PHP integration: the php_fastcgi directive, connections through Unix sockets and TCP, PHP-FPM setup, WordPress, Laravel, and Symfony patterns with try_files, and OPcache and FPM pool optimization.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

PHP still powers most of the web — WordPress, Laravel, and hundreds of other frameworks. For PHP to run efficiently, applications talk to the server through PHP-FPM, and the web server acts as a FastCGI client. Episode 24 covers this integration in Caddy.

You'll learn the php_fastcgi directive, connections via Unix socket or TCP, PHP-FPM setup with correct pools, configuration patterns for WordPress, Laravel, and Symfony, and performance optimization with OPcache.

Caddy handles PHP-FPM natively — no extra plugins or modules needed. This is one of the advantages that makes Caddy comfortable for PHP applications.

The php_fastcgi Directive

Basic Syntax

The php_fastcgi directive connects to PHP-FPM and routes requests to it:

Basic PHP
example.com {
    root * /var/www
    php_fastcgi unix//run/php/php-fpm.sock
    file_server
}

php_fastcgi unix//run/php/php-fpm.sock uses a Unix socket — the fastest connection because there's no network involved. Caddy sends the requested PHP file to FPM and displays the result.

TCP Connection

If PHP-FPM is on another host or container:

PHP-FPM via TCP
example.com {
    root * /var/www
    php_fastcgi 127.0.0.1:9000
    file_server
}

php_fastcgi 127.0.0.1:9000 connects over TCP to FPM on port 9000. Choose TCP when PHP-FPM and Caddy are separated — for example, in different containers (episode 27).

PHP-FPM Setup

Installation and Pool Configuration

Make sure PHP-FPM is installed:

Install PHP-FPM
sudo apt install php-fpm

Pool configuration lives at /etc/php/*/fpm/pool.d/www.conf. A few important values:

www.conf pool
user = www-data
listen = /run/php/php-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 20
  • listen must match what Caddy uses.
  • listen.mode gives read-write permission on the socket.
  • pm and max_children control the process count — covered in the optimization section.

Socket Permissions

The most common error: the socket can't be accessed. Make sure the Caddy user can read the socket. If Caddy runs as the caddy user and FPM as www-data, set listen.owner and listen.group correctly, then restart FPM.

WordPress

WordPress needs its index.php and clean permalinks:

WordPress on Caddy
example.com {
    root * /var/www/wordpress
    php_fastcgi unix//run/php/php-fpm.sock
    try_files {path} /index.php?{query}
    file_server
}

try_files {path} /index.php?{query} routes WordPress permalinks to index.php — the standard pattern for pretty permalink structures.

Laravel and Symfony

Laravel and Symfony use a front controller:

Laravel on Caddy
example.com {
    root * /var/www/laravel/public
    php_fastcgi unix//run/php/php-fpm.sock {
        env LARAVEL_ENV production
    }
    try_files {path} /index.php?{query}
    file_server
}

A key detail: root points to Laravel's public folder, not the project root. php_fastcgi with a block can send environment variables:

Env variables for PHP
example.com {
    root * /var/www/laravel/public
    php_fastcgi unix//run/php/php-fpm.sock {
        env APP_ENV production
        env DB_HOST localhost
    }
    try_files {path} /index.php?{query}
    file_server
}

env APP_ENV production passes the environment variable to the PHP process. For secrets, use system environment variables set by the service manager, not values written in the Caddyfile.

PHP Performance

OPcache

OPcache stores compiled PHP bytecode in memory:

OPcache in php.ini
opcache.enable = 1
opcache.memory_consumption = 128
opcache.max_accelerated_files = 10000
opcache.validate_timestamps = 0

opcache.validate_timestamps = 0 disables file-change checks — production only, because it requires a PHP reload after code updates. Without OPcache, every request recompiles the entire framework.

FPM Pools

The pm mode determines the process count:

  • dynamic: processes grow and shrink based on demand.
  • static: a fixed number of processes.
  • ondemand: processes are created when a request arrives.

pm.max_children is the process ceiling. Set it according to RAM: roughly 30 to 50 MB per PHP process. Don't exceed the server's memory capacity.

PHP Troubleshooting

If PHP pages are blank or return error 502:

Check FPM status
sudo systemctl status php-fpm
tail -f /var/log/php-fpm.log

tail -f /var/log/php-fpm.log streams FPM's error log live. A 502 Gateway error almost always means Caddy can't talk to FPM — check the socket, permissions, and whether FPM is running.

Conclusion

Episode 24 opened up PHP-FPM integration: the php_fastcgi directive with Unix socket or TCP, FPM pool setup with correct socket permissions, WordPress, Laravel, and Symfony patterns with try_files and a front controller, and OPcache and process management optimization.

Key takeaways:

  • php_fastcgi unix//run/php/php-fpm.sock for local connections.
  • Laravel's root points to the public folder.
  • try_files {path} /index.php?{query} for front controllers.
  • env in php_fastcgi forwards variables to PHP.
  • OPcache must be enabled for production.
  • Error 502 means FPM is unreachable — check the socket and permissions.

In the next episode, episode 25, we'll cover WebSocket & real-time applications — automatic WebSocket detection, reverse proxying upgraded connections, applications like Socket.io and real-time chat, timeout troubleshooting, and load balancer considerations for long-lived connections.

Learning Caddy - PHP-FPM Integration | Learning Caddy