When containerizing WordPress applications, many developers instinctively pair PHP-FPM with Nginx/Apache. However, this creates unnecessary complexity when you want strict separation of concerns between containers. The fundamental question is: Can PHP-FPM handle static file delivery natively?
Out of the box, PHP-FPM is configured to process only .php
files through its security.limit_extensions
directive. This means requests for static assets (CSS, JS, images) typically return 404 errors when hitting PHP-FPM directly.
; Default php-fpm.conf snippet
security.limit_extensions = .php .php3 .php4 .php5 .php7
To enable static file serving, modify your php-fpm.conf
:
; Allow processing of static files
security.limit_extensions = .php .html .css .js .png .jpg .jpeg .gif .svg
; Optional: Set proper MIME types
default_mimetype = "text/html"
Combine this with a docker-compose.yml
that excludes Nginx:
version: '3'
services:
php-fpm:
image: php:8.2-fpm
volumes:
- ./wordpress:/var/www/html
ports:
- "9000:9000"
reverse-proxy:
image: nginx
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
While technically possible, serving static files through PHP-FPM has tradeoffs:
- Pros: Simplified architecture, no shared volumes needed
- Cons: Higher memory usage, no built-in caching
For better performance, configure your reverse proxy to cache static content:
# nginx.conf snippet
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
proxy_pass http://php-fpm:9000;
proxy_cache static_cache;
proxy_cache_valid 200 1d;
expires max;
}
For development environments, consider PHP's built-in server with router script:
// router.php
if (preg_match('/\.(?:png|jpg|jpeg|gif|css|js)$/', $_SERVER["REQUEST_URI"])) {
return false; // serve the requested resource as-is
} else {
include __DIR__ . '/index.php';
}
Then run:
php -S 0.0.0.0:8080 router.php
When containerizing WordPress, many developers instinctively reach for Nginx or Apache to handle static files while using PHP-FPM exclusively for PHP processing. But what if we want PHP-FPM to handle everything?
This architecture offers several advantages:
- Simplified containerization (single service container)
- Clear separation between reverse proxy and application
- No shared volumes between containers
- Consistent request handling path
Edit your www.conf
(typically in /etc/php/{version}/fpm/pool.d/
):
[www] ... ; Enable static file serving security.limit_extensions = .php .html .htm .css .js .jpg .jpeg .png .gif .ico
For WordPress, you'll need to modify the .htaccess
equivalent behavior. Create a router.php
:
<?php $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $ext = pathinfo($path, PATHINFO_EXTENSION); // Static files check if ($ext && !in_array($ext, ['php', 'phtml', 'phar'])) { if (file_exists(__DIR__ . $path)) { $mime_types = [ 'css' => 'text/css', 'js' => 'application/javascript', 'png' => 'image/png', // Add other mime types ]; header('Content-Type: ' . ($mime_types[$ext] ?? mime_content_type(__DIR__ . $path))); readfile(__DIR__ . $path); exit; } } // Fall back to WordPress include __DIR__ . '/index.php';
While functional, this approach has tradeoffs:
- PHP-FPM isn't optimized for static file serving
- No native sendfile support
- Memory overhead per request
The reverse proxy (Nginx) in front should handle caching aggressively:
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ { expires 1y; add_header Cache-Control "public, immutable"; proxy_pass http://php-fpm-container; }
For development environments, consider:
php -S 0.0.0.0:80 -t /var/www/html router.php
For production workloads, I'd recommend one of these approaches:
- Use Nginx unit (supports both PHP and static files)
- Accept the minor complexity of Nginx + PHP-FPM separation
- Implement a CDN for static assets
Remember to benchmark any solution under expected production loads.