How to Serve Static Files Directly from PHP-FPM Without Nginx: Configuration & Best Practices


8 views

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:

  1. Use Nginx unit (supports both PHP and static files)
  2. Accept the minor complexity of Nginx + PHP-FPM separation
  3. Implement a CDN for static assets

Remember to benchmark any solution under expected production loads.