Resolving mod_proxy_fcgi and .htaccess RewriteRule Priority Conflicts in Apache 2.4 with PHP-FPM


2 views

After migrating to Apache 2.4 with PHP-FPM via mod_proxy_fcgi, many developers encounter an interesting interaction between ProxyPassMatch directives and .htaccess rewrite rules. The core issue stems from processing order - ProxyPassMatch in the vhost configuration executes before .htaccess rules get applied.

Here's what happens with the current configuration:

Original request: /blogname/wp-admin/load-styles.php
ProxyPassMatch fires first: /blogname/wp-admin/load-styles.php → fcgi backend
.htaccess rewrites never get executed for PHP files

The most reliable approach is to move critical rewrites from .htaccess to your virtual host configuration, before the ProxyPassMatch directive:


<VirtualHost *:80>
    # Your existing directives
    
    # WordPress multi-site rewrites
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^ - [L]
    RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
    RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
    
    # Then process PHP files
    ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.2:9126/var/www/$1
    
    # Final rewrite for front controller
    RewriteRule . /index.php [L]
</VirtualHost>

For some environments, using SetHandler may provide more predictable behavior:


<FilesMatch "\.php$">
    SetHandler "proxy:fcgi://127.0.0.2:9126"
</FilesMatch>

# Then include your rewrite rules
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
RewriteRule . /index.php [L]

When troubleshooting these issues:

  • Enable mod_rewrite logging with LogLevel alert rewrite:trace6
  • Check proxy communication with LogLevel debug proxy_fcgi:trace3
  • Test rewrite rules in isolation using online htaccess testers

Remember that moving rules from .htaccess to the vhost configuration:

  • Improves performance by eliminating per-directory config parsing
  • Makes behavior more predictable
  • Simplifies debugging since all rules are in one place

When using Apache 2.4 with PHP-FPM through mod_proxy_fcgi, we often encounter an interesting interaction between ProxyPass directives and .htaccess RewriteRules. The fundamental issue stems from the processing order in Apache's request handling pipeline.

In your configuration, the ProxyPassMatch directive is intercepting PHP requests before WordPress's rewrite rules get a chance to modify the paths. This creates a mismatch between what WordPress expects and what actually gets proxied to PHP-FPM.

# Current vhost configuration
ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.2:9126/<path>/$1

# WordPress .htaccess rules
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]

In a WordPress multisite installation with subdirectory structure, paths like /blogname/wp-admin/load-styles.php need to be internally rewritten to wp-admin/load-styles.php before being processed. The current ProxyPassMatch is bypassing this crucial transformation.

There are several ways to handle this:

Option 1: Modify ProxyPassMatch to Handle Rewritten Paths

# Adjust the ProxyPassMatch to work with rewritten paths
ProxyPassMatch ^/(wp-(content|admin|includes)/.*\.php(/.*)?)$ fcgi://127.0.0.2:9126/<path>/$1
ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.2:9126/<path>/$1

Option 2: Use SetHandler Instead

This approach gives more control to the rewrite engine:

# Replace ProxyPassMatch with FilesMatch

    SetHandler "proxy:fcgi://127.0.0.2:9126"
    # Optionally set the PATH_INFO
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} ^(.*)\.php$
    RewriteRule ^ - [E=PATH_INFO:%1]

Option 3: Adjust Rewrite Rules to Work with Proxy

Modify the WordPress .htaccess to ensure paths are correct before proxying:

RewriteEngine On
RewriteBase /

# Skip existing files/directories
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

# Handle WordPress paths
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ /$2 [L,PT]  # Note the PT flag
RewriteRule . index.php [L]

To better understand what's happening:

# Add logging to your vhost
LogLevel rewrite:trace6
LogLevel proxy:debug

# Or add specific rewrite logging
RewriteLog "/var/log/apache2/rewrite.log"
RewriteLogLevel 3

Each approach has different performance implications. The SetHandler method (Option 2) generally has less overhead than ProxyPassMatch as it doesn't require pattern matching for every request.

For WordPress multisite installations, Option 2 (SetHandler) combined with adjusted rewrite rules typically provides the most reliable solution while maintaining good performance.