36

I recently installed Apache 2.4 on my local machine, together with PHP 5.4.8 using PHP-FPM.

Everything went quite smoothly (after a while...) but there is still a strange error:

I configured Apache for PHP-FPM like this:

<VirtualHost *:80>
    ServerName localhost
    DocumentRoot "/Users/apfelbox/WebServer"
    ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/Users/apfelbox/WebServer/$1
</VirtualHost>

It works, for example if I call http://localhost/info.php I get the correct phpinfo() (it is just a test file).

If I call a directory however, I get a 404 with body File not found. and in the error log:

[Tue Nov 20 21:27:25.191625 2012] [proxy_fcgi:error] [pid 28997] [client ::1:57204] AH01071: Got error 'Primary script unknown\n'

Update

I now tried doing the proxying with mod_rewrite:

<VirtualHost *:80>
    ServerName localhost
    DocumentRoot "/Users/apfelbox/WebServer"

    RewriteEngine on    
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/Users/apfelbox/WebServer/$1 [L,P]
</VirtualHost>

But the problem is: it is always redirecting, because on http://localhost/ automatically http://localhost/index.php is requested, because of

DirectoryIndex index.php index.html

Update 2

Ok, so I think "maybe check whether there is a file to give to the proxy first:

<VirtualHost *:80>
    ServerName localhost
    DocumentRoot "/Users/apfelbox/WebServer"

    RewriteEngine on    
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} -f
    RewriteRule ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/Users/apfelbox/WebServer/$1 [L,P]
</VirtualHost>

Now the complete rewriting does not work anymore...

Update 3

Now I have this solution:

<VirtualHost *:80>
    ServerName localhost
    DocumentRoot "/Users/apfelbox/WebServer"

    RewriteEngine on    
    RewriteCond /Users/apfelbox/WebServer/%{REQUEST_FILENAME} -f
    RewriteRule ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/Users/apfelbox/WebServer/$1 [L,P]
</VirtualHost>

First check, that there is a file to pass to PHP-FPM (with the full and absolute path) and then do the rewriting.

This does not work when using URL rewriting inside a subdirectory, also it fails for URLs like http://localhost/index.php/test/ So back to square one.


Any ideas?

13 Answers 13

43

After hours of searching and reading Apache documentation I've come up with a solution that allows to use the pool, and also allow the Rewrite directive in .htaccess to work even when the url contains .php files.

<VirtualHost ...>

 ...

 # This is to forward all PHP to php-fpm.
 <FilesMatch \.php$>
   SetHandler "proxy:unix:/path/to/socket.sock|fcgi://unique-domain-name-string/"
 </FilesMatch>

 # Set some proxy properties (the string "unique-domain-name-string" should match
 # the one set in the FilesMatch directive.
 <Proxy fcgi://unique-domain-name-string>
   ProxySet connectiontimeout=5 timeout=240
 </Proxy>

 # If the php file doesn't exist, disable the proxy handler.
 # This will allow .htaccess rewrite rules to work and 
 # the client will see the default 404 page of Apache
 RewriteCond %{REQUEST_FILENAME} \.php$
 RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} !-f
 RewriteRule (.*) - [H=text/html]

</VirtualHost>

As per Apache documentation, the SetHandler proxy parameter requires Apache HTTP Server 2.4.10.

I hope that this solution will help you too.

8
  • 2
    This is definitely the answer for 2015, everything else here is crap for a modern setup (Let's say debian stable)
    – Dmitri DB
    Jul 4, 2015 at 9:50
  • 1
    I've been banging my head against the wall on this same problem for quite some time, and I have an extremely similar setup as yours. Would you please post your .htaccess Rewrite directives? From what I understand, everything in this answer is only what you have in your httpd.d/site.conf file.
    – David W
    Nov 5, 2015 at 10:56
  • 2
    At the moment using this RewriteRule seems quite dangerous as it could expose config.php files in plain if they were inside Aliased directories and therefore not existing in %{DOCUMENT_ROOT}/%{REQUEST_URI}.
    – Zulakis
    Feb 16, 2016 at 10:12
  • 2
    Amazing 9 lines of code. This is the holy grail and the only thing that works 100% for me. Just a sidenote: If you're switching from a solution using LocationMatch, you don't need to append the absolute file path to the fcgi url. Turn on proxy and rewrite logging in apache to watch out for this.
    – Phil
    Mar 8, 2016 at 9:02
  • 2
    +1 because this post unlike every other resource I've seen helped me understand what "unique-domain-name-string" is supposed to represent.
    – trey-jones
    Apr 5, 2016 at 19:46
11

I ran into this problem yesterday as well – Apache 2.4 moved out of Debian/experimental into Debian/unstable forcing me to deal with this new stuff; not on our production servers of course ;).

After reading what feels like millions of sites, Apache docs, bug reports and debugging output in the error log I finally got it to work. No, there's no support for FPM with sockets, yet. The default Debian config has been using sockets for some time now, so Debian users will have to change that too.

Here's what works for a CakePHP site and PHPMyAdmin (the latter needs some config if you're using the Debian packages though), so I can confirm that mod_rewrite still works as expected to do fancy URL rewriting.

Notice DirectoryIndex index.php, which might be the reason none of your configs worked for "folders" (at least that's what didn't work here).

I still get File not found. for directories, but only if there's no index file it can parse. Would love to get rid of that too, but it's not that critical as for now.


<VirtualHost *:80>
    ServerName site.localhost

    DocumentRoot /your/site/webroot
    <Directory />
            Options FollowSymlinks
            DirectoryIndex index.php
            AllowOverride All
            Require all granted
    </Directory>

    <LocationMatch "^(.*\.php)$">
            ProxyPass fcgi://127.0.0.1:9000/your/site/webroot
    </LocationMatch>

    LogLevel debug
    ErrorLog /your/site/logs/error.log
    CustomLog /your/site/logs/access.log combined
</VirtualHost>

The above vhost works perfectly well with an .htaccess in the root like this:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>

I don't quite get what you mean by URL rewriting inside a subdirectory though (I'm only rewriting to the root's index.php).


(Oh, and you'll have to make sure Xdebug doesn't conflict with FPM on your system, out of the box they want to use the same ports.)

3
  • This is a good solution but unfortunately this approach does not work when urls containing .php need to be rewritten e.g. for WordPress multisite. /ms_blog_1/wp-admin/load-scripts.php?blah=blah
    – Phil
    Mar 8, 2016 at 7:48
  • For me, just adding an override DirectoryIndex index.html in the vhost in question fixed it. If I have DirectoryIndex index.php, then it seems other PHP files end up giving the 'File not found' and 'Primary script unknown' error. In my case, I have an index.html but a php file test.php. Jun 3, 2016 at 4:06
  • In my case adding QSA to the RewriteRule was sufficient: RewriteRule ^(.*)$ index.php [QSA,L] Dec 16, 2019 at 22:11
4

All you need to do is to set:

 ProxyErrorOverride on

And do not forget to set the customer page by:

ErrorDocument 404 /path/to/error_page_file    
3

Yet another solution (requires Apache >= 2.4.10) - Inside the vhost:

# define worker
<Proxy "unix:/var/run/php5-fpm-wp.bbox.nuxwin.com.sock|fcgi://domain.tld" retry=0>
    ProxySet connectiontimeout=5 timeout=7200
</Proxy>

<If "%{REQUEST_FILENAME} =~ /\.php$/ && -f %{REQUEST_FILENAME}">
    SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1
    SetHandler proxy:fcgi://domain.tld
</If>

So here, the fcgi handler for PHP will be set only if the file exists and if its name matches with PHP file extension.

BTW: For those which would have idea to set the ProxyErrorOverride to On, be aware that this is really a bad idea. Usage of this directive is not without causing any issue. For instance, any PHP application sending HTTP code such as 503 would lead to unexpected result. Default error handler would be involved in any cases and for PHP applications that provide API, that is really a bad behavior.

1
  • Unfortunately still had "AH01071: Got error 'Primary script unknown\n'" error using this solution.
    – klor
    Nov 22, 2018 at 17:32
2

This is what I've got. It seems to work OK. I put Drupal in a subdirectory and its rewrites work, directory indexes work, and PATH_INFO works.

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} ^/((.*\.php)(/.*)?)$
RewriteCond %2 -f
RewriteRule . fcgi://127.0.0.1:9000/%1 [L,P]
RewriteOptions Inherit

I've tried to do something like this without rewrites ("If" and such), but I couldn't get anything to work.

EDIT: Note that if you were to implement this as a shared hosting provider, this could be a security issue. It would allow users to pass PHP scripts to an arbitrary fcgi proxy. If you had a seperate pool for every user, that would allow for elevation of privilege attacks.

1

Best way to solve this is to turn on debugging logs for mod_proxy and mod_rewrite and php-fpm. In apache 2.4 you can now turn on debugging logs for specific modules only. http://httpd.apache.org/docs/current/mod/core.html#loglevel Per-module and per-directory configuration is available in Apache HTTP Server 2.3.6 and later

Maybe you're getting a double slash on directories?

Here's what I use and it works fine:

<LocationMatch ^(.*\.php)$>
  ProxyPass fcgi://127.0.0.1:9000/home/DOMAINUSER/public_html$1
</LocationMatch>
1

One thing I came across in my dealing with this issue, is that if you use the combination of:

chroot = /path/to/site
chdir = /

In your fpm pool configuration, don't pass the full path to the ProxyPass directive.

ProxyPass fcgi://127.0.0.1:9020/$1

But -ONLY- if the pool on that port is chrooted.

1

I'm not sure if the problem is related, but i've found a partialy working solution here:

https://stackoverflow.com/questions/44054617/mod-rewrite-in-2-4-25-triggering-fcgi-primary-script-unknown-error-in-php-fpm

The trick seems to be adding a ? char in the .htaccess RewriteRule, eg using:

RewriteRule ^(.*)$ index.php?/$1 [L,NS]

instead of:

RewriteRule ^(.*)$ index.php/$1 [L,NS]

The source of the problem seems to be a change in mod_rewrite of Apache 2.4.25. I've used Apache trace1 log level to observe a "loop" that pass $1 to php-fpm after index.php/$1 has been passed. The $1 generate the "AH01071: Got error 'Primary script unknown\n'" error.

Hope this little tidbit help someone to solve their problems.

0
1

A slightly modified version of @FrancescoA's answer that does not require mod_rewrite

<IfModule proxy_fcgi_module>
    <FilesMatch \.php$>
       <If "-f '%{REQUEST_FILENAME}'">
           SetHandler "proxy:unix:/path/to/socket.sock|fcgi://unique-domain-name-string/"
       </If>
     </FilesMatch>

     <Proxy fcgi://unique-domain-name-string>
        ProxySet connectiontimeout=5 timeout=240
     </Proxy>
</IfModule>
0

Linode has a great tutorial on this subject

Basically you setup a handler for the entire server that will catch any php scripts and pass them to fast-cgi.

0

i have the error also after switch to php-fpm + apache 2.4.6 for drupal instances

but I am using mpm event mod

just insert

DirectoryIndex index.php works for me

then my Vhost settings looks like below

<VirtualHost *:8080>
  ServerAdmin webmaster@localhost
  ServerName sever.com
  DocumentRoot /var/www/html/webroot
    ErrorLog logs/web-error_log
    CustomLog logs/web-access_log common
<IfModule mpm_event_module>
    ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/var/www/html/webroot/$1
</IfModule>
  <Directory /var/www/html/webroot>
     Options FollowSymlinks
     DirectoryIndex index.php
     AllowOverride All
     Require all granted
  </Directory>
</VirtualHost>

thanks

no need to revise drupal's default .htaccess file

1
  • [Wed Apr 25 01:41:31.526781 2018] [proxy_fcgi:error] [pid 2012:tid 140181155772160] (70007)The timeout specified has expired: [client 127.0.0.1:60308] AH01075: Error dispatching request to :, referer: www/admin/reports Apr 24, 2018 at 18:10
0

I face the same problems on my server(centos 7.3.16 docker). After tracking the php-fpm log ,I found miss a sys lib. WARNING: [pool www] child 15081 said into stderr: "php-fpm: pool www: symbol lookup error: /lib64/libnsssysinit.so: undefined symbol: PR_GetEnvSecure" then,I restall the nspr,it works.If you cannot find the solutions after trying any methods,you may try this. yum -y install/reinstall nspr

0

This works with Wordpress 5.1.1 and newer together with PHP 7.3, FastCGI , proxy, also MariaDB/MySQL. Checked twice on my servers. Works like a charm.

First on CentOS/Fedora/Red Hat

sudo yum remove php*
sudo yum --enablerepo=extras install epel-release
sudo yum install php-fpm php-mysql php-gd php-imap php-mbstring 
sudo grep -E '(proxy.so|fcgi)' /etc/httpd/conf.modules.d/00-proxy.conf
sudo mv /etc/httpd/conf.d/php.conf /etc/httpd/conf.d/php.conf_bak

Edit this file:

sudo nano /etc/php-fpm.d/www.conf

Paste this:

[www]

; The address on which to accept FastCGI requests.
; Valid syntaxes are:
;   'ip.add.re.ss:port'    - to listen on a TCP socket to a specific address on
;                            a specific port;
;   'port'                 - to listen on a TCP socket to all addresses on a
;                            specific port;
;   '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
listen = 127.0.0.1:9000
listen = /run/php-fcgi.sock

sudo ll /run/php-fcgi.sock

Should give srw-rw-rw-.

Or how to set up on Debian/Ubuntu

Tutorial:

source: https://emi.is/?page=articles&article=php-7-installation-and-configuration-for-apache-2.4-using-php-fpm-(debian,-repository)


sudo apt purge 'php*' or sudo apt-get purge 'php*'
sudo add-apt-repository ppa:ondrej/php
sudo apt-get update
sudo apt install php7.3 php7.3-fpm php-mysql php-mbstring php-gd php-imap libapache2-mod-security2 modsecurity-crs
systemctl status php7.3-fpm
systemctl stop php7.3-fpm.service

sudo a2dismod php7.0 php7.1 php7.2 mpm_event mpm_worker
sudo a2enmod mpm_prefork
sudo a2enmod php7.3
sudo systemctl restart apache2 (httpd in CentOS)

The problem is that php 7.3 from Ondrej repo works only with mpm_prefork mode. He has git repo, so you can find him in net and ask him, will he make php 7.3 for mpm_worker and mpm_event. The rest of configuration for Debian family distros is below:


sudo apt --assume-yes install php7.3-fpm
sudo systemctl stop php7.3-fpm.service
sudo rm /var/log/php7.0-fpm.log
sudo mkdir /var/log/php7.3-fpm/
sudo touch /var/log/php7.3-fpm/error.log
sudo mkdir /var/log/php7.3/
sudo touch /var/log/php7.3/error.log
sudo mkdir /var/tmp/php7.3/
sudo > /etc/php/7.3/fpm/php.ini
sudo > /etc/php/7.3/fpm/php-fpm.conf
sudo rm /etc/php/7.3/fpm/pool.d/www.conf
sudo touch /etc/php/7.3/fpm/pool.d/example.com.conf
sudo useradd --comment "PHP" --shell "/usr/sbin/nologin" --system --user-group php

sudo nano /etc/php/7.3/fpm/php.ini

paste


[PHP]
date.timezone = Europe/Prague
display_errors = Off
error_log = /var/log/php7.3/error.log
error_reporting = 32767
log_errors = On
register_argc_argv = Off
session.gc_probability = 0
short_open_tag = Off
upload_tmp_dir = /var/tmp/php7.3/

sudo nano /etc/php/7.3/fpm/php-fpm.conf

paste


[global]
error_log = /var/log/php7.3-fpm/error.log
include = /etc/php/7.3/fpm/pool.d/*.conf

sudo nano /etc/php/7.3/fpm/pool.d/example.com.conf

paste


[example.com]
group = php
listen = 127.0.0.1:9000
pm = ondemand
pm.max_children = 5
pm.max_requests = 200
pm.process_idle_timeout = 10s
user = php

sudo nano /etc/logrotate.d/php7.3-fpm

copy this to txt file:

/var/log/php7.3-fpm.log {
    rotate 12
    weekly
    missingok
    notifempty
    compress
    delaycompress
    postrotate
            /usr/lib/php/php7.3-fpm-reopenlogs
    endscript
}

remove it and then paste this instead of above:

/var/log/php7.3/*.log /var/log/php7.3-fpm/*.log
{
copytruncate
maxage 365
missingok
monthly
notifempty
rotate 12
}

Add directive

sudo nano /etc/apache2/sites-available/example.com.conf


<VirtualHost *:80>
    ServerName www.example.com
    ServerAlias example.com
    ServerAdmin [email protected]
    DocumentRoot /var/www/html/example.com/public_html
    DirectoryIndex index.php index.htm index.html index.xht index.xhtml
    LogLevel info warn
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    <FilesMatch "^\.ht">
    Require all denied
    </FilesMatch>

    <files readme.html>
    order allow,deny
    deny from all
    </files>

    RewriteEngine on
    RewriteCond %{SERVER_NAME} =example.com
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
    ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/var/www/html/example.com/public_html

    <Directory /var/www/html/example.com/public_html>
        Options Indexes FollowSymLinks Includes IncludesNOEXEC SymLinksIfOwnerMatch
        AllowOverride None
    </Directory>
</VirtualHost>

Then enable site:

sudo a2ensite /etc/apache2/sites-available/example.com.conf

Next edit SSL site (In this case certbot from Let's Encrypt has been installed and configured previously at the beginning of the SSL cert configuration).

sudo nano /etc/apache2/sites-available/example.com-le-ssl.conf

<IfModule mod_ssl.c>
    #headers for security man in the middle attack find how to enable this mod in Google
    LoadModule headers_module modules/mod_headers.so
    <VirtualHost *:443>
        Header always set Strict-Transport-Security "max-age=15768000"
        SSLEngine On
        ServerName example.com
        ServerAdmin [email protected]
        DocumentRoot /var/www/html/example.com/public_html
        <Directory /var/www/html/example.com/public_html>
        Options Indexes FollowSymLinks Includes IncludesNOEXEC SymLinksIfOwnerMatch
        AllowOverride All
        Require all granted
        DirectoryIndex index.php
        RewriteEngine On
         <FilesMatch ^/(.*\.php(/.*)?)$>
           SetHandler "fcgi://example.com:9000/var/www/html/example.com/public_html"
          </FilesMatch>
        </Directory>
    # Log file locations
    #LogLevel info ssl:warn
    LogLevel debug
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    # modern configuration
    SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
    Include /etc/letsencrypt/options-ssl-apache.conf
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    #SSLCipherSuite HIGH:!aNULL:!MD5
    SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM$
    SSLHonorCipherOrder on
    SSLCompression off
    SSLSessionTickets off

    <FilesMatch "^\.ht">
    Require all denied
    </FilesMatch>

    <files readme.html>
       order allow,deny
       deny from all
    </files>

</VirtualHost>
    #Stapling OCSP for Let's Encrypt certs.
    SSLUseStapling          on
    SSLStaplingResponderTimeout     5
    SSLStaplingReturnResponderErrors        off
    SSLStaplingCache        shmcb:/var/run/ocsp(128000)
</IfModule>

sudo a2enmod proxy proxy_fcgi setenvif
sudo systemctl reload apache2.service
sudo chown --recursive root:adm /etc/php/
sudo chmod --recursive 0770 /etc/php/
sudo chown --recursive php:adm /var/log/php7.3/
sudo chown --recursive php:adm /var/log/php7.3-fpm/
sudo chmod --recursive 0770 /var/log/php7.3/
sudo chmod --recursive 0770 /var/log/php7.3-fpm/
sudo chown --recursive php:php /var/tmp/php7.3/
sudo chmod --recursive 0770 /var/tmp/php7.3/
sudo a2enconf php7.3-fpm
sudo systemctl enable php7.3-fpm.service
sudo systemctl start php7.3-fpm.service

Remember to add a port 9000 to a firewall on Debian/Ubuntu

sudo ufw allow 9000/tcp
sudo ufw status

On CentoOS / Fedora/ Red Hat

sudo firewall-cmd --zone=public --add-port=9000/tcp --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
sudo firewall-cmd --state 

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .