PHP-FPM on Apache with split configuration per site

While configuring this very server I kept in mind that I would want to host multiple sites on it. I wanted to be able to configure PHP settings on a per site basis instead of modifying the global PHP configuration for the entire server. I also wanted to make sure that every site/user had its own permissions and thus wasn’t able to screw things up on my server in case of a successful break-in attempt. And of course I also kept the performance of my server in mind.

Therefor I chose PHP-FPM (over mod_php) which I configured with an application pool per site that has its own configuration file and handler. Just follow the next steps if you would like to duplicate my configuration.

Installation

First of all we install the required packages

~$ apt-get install apache2-mpm-worker libapache2-mod-fastcgi php5-fpm

and enable the needed apache modules.

~$ a2enmod actions alias fastcgi

Configure PHP-FPM

Now we have to create the configuration file /etc/apache2/conf.d/php5-fpm with the following content:

# Configure everything needed to use PHP-FPM as FastCGI
# Set handlers for PHP files.
# application/x-httpd-php                        phtml pht php
# application/x-httpd-php3                       php3
# application/x-httpd-php4                       php4
# application/x-httpd-php5                       php
 
<FilesMatch ".+\.ph(p[345]?|t|tml)$">
    SetHandler application/x-httpd-php
 
# application/x-httpd-php-source                 phps
 
<FilesMatch ".+\.phps$">
    SetHandler application/x-httpd-php-source
    # Deny access to raw php sources by default
    # To re-enable it is recommended to enable access to the files
    # only in specific virtual host or directory
    Order Deny,Allow
    Deny from all
 
# Deny access to files without filename (e.g. '.php')
<FilesMatch "^\.ph(p[345]?|t|tml|ps)$">
    Order Deny,Allow
    Deny from all
 
# Define Action and Alias needed for FastCGI external server.
Action application/x-httpd-php /fcgi-bin/php5-fpm virtual
Alias /fcgi-bin/php5-fpm /fcgi-bin-php5-fpm
 
  # here we prevent direct access to this Location url,
  # env=REDIRECT_STATUS will let us use this fcgi-bin url
  # only after an internal redirect (by Action upper)
  Order Deny,Allow
  Deny from All
  Allow from env=REDIRECT_STATUS
 
FastCgiExternalServer /fcgi-bin-php5-fpm -socket /var/run/php5-fpm.sock -pass-header Authorization

A handler will be set for every PHP file and for every handler a virtual Action is defined. Virtual means that there will be no check that the file exists.
The Alias was created because we will need this to run PHP-FPM as another user than the default www-data user.

Separate configuration per user

Let’s start by creating a new user and adding www-data into its group. Then create the home dir for this user, correct the permissions and link the new home dir to this user.

~$ adduser --disabled-login dimitri
~$ adduser www-data dimitri
~$ mkdir /var/www/dimitrieu
~$ chown -R dimitri:dimitri /var/www/dimitrieu
~$ chmod 750 /var/www/dimitrieu

We can now create the PHP-FPM configuration file for this user. Just copy the default file /etc/php5/fpm/pool.d/www.conf to /etc/php5/fpm/pool.d/dimitri.conf and modify the following lines.

; Start a new pool named 'dimitri'.
; the variable $pool can we used in any directive and will be replaced by the
; pool name ('dimitri' here)
[dimitri] 
; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user group
; will be used.
user = dimitrigroup = dimitri 
; 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 = /var/run/php5-fpm-dimitri.sock

Now restart PHP-FPM

~$ /etc/init.d/php5-fpm restart

and verify if the new PHP-FPM instances are running.

~$  ps -efH | grep php-fpm
root     30258 30162  0 12:01 pts/0    00:00:00         grep php-fpm
root     10409     1  0 Sep27 ?        00:00:22   php-fpm: master process (/etc/php5/fpm/php-fpm.conf)
www-data 10415 10409  0 Sep27 ?        00:00:00     php-fpm: pool www
www-data 10416 10409  0 Sep27 ?        00:00:00     php-fpm: pool www
1000     18944 10409  0 Oct07 ?        00:02:13     php-fpm: pool dimitri1000     18995 10409  0 Oct07 ?        00:02:03     php-fpm: pool dimitri1000     19142 10409  0 Oct07 ?        00:01:38     php-fpm: pool dimitri

We can now modify our apache vhost for this site (/etc/apache2/sites-enabled/dimitrieu)

        ServerAdmin webmaster@dimitri.eu
        ServerName dimitri.eu
        ServerAlias www.dimitri.eu
 
        DocumentRoot /var/www/dimitrieu/public_html
 
                Options Indexes FollowSymLinks MultiViews
                AllowOverride All
                Order allow,deny
                allow from all
 
        Alias /fcgi-bin/php5-fpm /fcgi-bin-php5-fpm-dimitri
        FastCgiExternalServer /fcgi-bin-php5-fpm-dimitri -socket /var/run/php5-fpm-dimitri.sock -pass-header Authorization         ErrorLog /var/log/apache2/dimitrieu/error.log
 
        # Possible values include: debug, info, notice, warn, error, crit,
        # alert, emerg.
        LogLevel warn
 
        CustomLog /var/log/apache2/dimitrieu/access.log combined

and restart apache.

~$ /etc/init.d/apache2 restart

Small test

We’re done, just create a test.php script in the document root for this site to verify the correct PHP-FPM user. Be aware that the test.php script should have the correct ownership on the server.

<!--?php system('whoami'); phpinfo(); ?-->
Tagged on: , ,

18 thoughts on “PHP-FPM on Apache with split configuration per site

  1. Paul

    When an unexisting php file is called I correctly get a 404 and see “File not found” but the apache error.log is populated with error:

    FastCGI: server “/fcgi-bin-php5-fpm” stderr: Primary script unknown

    is this expected?
    How can I have the default “File does not exist: file.php” error?

    1. Dimitri Steyaert Post author

      Hi, from what I can see I have these same messages in my apache logs and they seem to be “normal behaviour”.
      I can find some workarounds for Nginx but not for Apache. I’ll look some further and update you when I find out more about this.

  2. Omry

    Can you please update the blog post to fix the code escaping?
    it’s pretty difficult with things like:
    <FilesMatch “^\.ph(p[345]?|t|tml|ps)$”>

    1. Omry

      wow, your site is actually escaping the html for me.
      this smells like a huge security hole, if it’s not escaping user input.
      I will resist sticking a javascript block with alert.

      in any case, if you look at the actual post, you will see that the code is escaped there which makes it rather difficult to read.

      1. Omry Yadan

        Thanks for fixing it.
        I ended up using something like:

        ProxyPassMatch ^/(.*\.php(/.*)?)$ unix:/var/run/php5-fpm-omry.sock|fcgi://127.0.0.1/path_to_php_files/

        Any thought about this approach?

        Did you manage to get things to work with a chroot per user?
        I am not sure what is the best way to setup a chroot that will be shared (read only) between users were each user also gets their own vhosts available.

        If you want to chat about this please send me an email (I didn’t a notification on your response here).

  3. Victor Porton

    I did everything of the above manual, but my .php nevertheless run as www-data instead of confirgured correct user.

    # ps -efH | grep php-fpm
    root 5248 4317 0 17:16 pts/1 00:00:00 grep php-fpm
    root 4502 1 0 17:04 ? 00:00:00 php-fpm: master process (/etc/php5/fpm/php-fpm.conf)
    theses 4504 4502 0 17:04 ? 00:00:00 php-fpm: pool theses
    theses 4505 4502 0 17:04 ? 00:00:00 php-fpm: pool theses
    www-data 4506 4502 0 17:04 ? 00:00:00 php-fpm: pool www
    www-data 4507 4502 0 17:04 ? 00:00:00 php-fpm: pool www

    So fpm part seems to work. It is probably wrong Apache config (despite I’ve checked everything).

    # cat theses.conf

    ServerName theses.portonvictor.org

    SuexecUserGroup theses theses

    ServerAdmin webmaster@localhost

    DocumentRoot /var/www/theses/web

    Options Indexes SymlinksIfOwnerMatch
    AllowOverride All
    Order allow,deny
    allow from all

    #ScriptAlias /cgi-bin/ /var/www/homepage/cgi-bin
    #
    # AllowOverride None
    # Options +ExecCGI -MultiViews
    # Order allow,deny
    # Allow from all
    #

    ErrorLog ${APACHE_LOG_DIR}/error.log

    # Possible values include: debug, info, notice, warn, error, crit,
    # alert, emerg.
    LogLevel warn

    CustomLog ${APACHE_LOG_DIR}/access.log combined

    AddType application/x-httpd-fastphp5 .php
    Action application/x-httpd-fastphp5 /php5-fcgi virtual
    Alias /php5-fcgi /usr/lib/cgi-bin/php5-fcgi-theses
    FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi-theses -socket /var/run/php5-fpm-theses.sock -pass-header Authorization
    #
    # Require all granted
    #

    What is wrong?!!

    1. Victor Porton

      Note that `sudo a2dismod php5` makes 500 Internal Server Error when trying to load a .php file from my server.

      Thus it looks like that FPM is not used in some reason. I wonder, why it is not used, it looks like that I configured everything correctly.

      1. Dimitri Steyaert Post author

        Could you check your main Apache errorlog because if Apache fails to start you should find something in your logs.
        You may post the error if you don’t immediately find an answer.

  4. Joan

    Very nice post, I’ve been looking into this for quite a lot of time, at the end having the proper parts highlighted showed me the right track. Thanks a lot

  5. Info,

    My brother recommended I might like this web site.
    He was entirely right. This post actually made my day.
    You can not imagine simply how much time I had spent for this
    information! Thanks!

    Look at my website – Info,

  6. ld

    Very helpful. Thank you.

    I think you may have a small mistake:

    Alias /fcgi-bin/php5-fpm /fcgi-bin-php5-fpm-dimitrieu
    FastCgiExternalServer /fcgi-bin-php5-fpm-dimitri -socket /var/run/php5-fpm-dimitri.sock -pass-header Authorization

    The last 2 chars ‘eu’ in the first line don’t make sense. Thanks again.

  7. Pingback: How to purge the Varnish cache | Dimitri.eu

  8. Dave

    Nice config. Seems more concise than others I’ve seen.

    Just wanted to note while getting this to work on Apache 2.4., you just need to change the require directives to the new syntax (Require all granted or Require all denied). The REDIRECT_STATUS one can be changed to Require env REDIRECT_STATUS.

    Have you tried using the new mod_proxy_fcgi with 2.4?

    1. Dimitri Steyaert Post author

      Hi, thank you for the information, I didn’t install apache 2.4 so my answer would be no :). But this will be helpful for people visiting my site and even for me when I switch over to apache 2.4 in the future.

Leave a Reply

Your email address will not be published. Required fields are marked *