How To Set Up Apache2 With mod_fcgid And PHP5 On Ubuntu 12.04

Version 1.0
Author: Falko Timme
Follow me on Twitter

This tutorial describes how you can install Apache2 with mod_fcgid and PHP5 on Ubuntu 12.04. mod_fcgid is a compatible alternative to the older mod_fastcgi. It lets you execute PHP scripts with the permissions of their owners instead of the Apache user.

I do not issue any guarantee that this will work for you!

 

1 Preliminary Note

I'm using an Ubuntu 12.04 server in this tutorial with the hostname server1.example.com and the IP address 192.168.0.100.

I will create two Apache vhosts in this tutorial, www.example1.com and www.example2.com, to demonstrate the usage of mod_fcgid.

Make sure you're logged in as root:

sudo su

/bin/sh is a symlink to /bin/dash, however we need /bin/bash, not /bin/dash. Therefore we do this:

dpkg-reconfigure dash

Install dash as /bin/sh? <-- No

In addition to that, we must disable AppArmor:

/etc/init.d/apparmor stop
update-rc.d -f apparmor remove
apt-get remove apparmor apparmor-utils

 

2 Installing Apache2/mod_fcgi/PHP5

In order to install Apache2, mod_fcgid, and PHP5, run

apt-get install apache2 apache2-suexec libapache2-mod-fcgid php5-cgi

If Apache2 was already installed with PHP5 as an Apache module, disable the PHP5 module now:

a2dismod php5

Then enable the following modules...

a2enmod rewrite
a2enmod suexec
a2enmod include
a2enmod fcgid

... and open /etc/php5/cgi/php.ini:

vi /etc/php5/cgi/php.ini

Uncomment the line cgi.fix_pathinfo = 1:

[...]
; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI.  PHP's
; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok
; what PATH_INFO is.  For more information on PATH_INFO, see the cgi specs.  Setting
; this to 1 will cause PHP CGI to fix its paths to conform to the spec.  A setting
; of zero causes PHP to behave as before.  Default is 1.  You should fix your scripts
; to use SCRIPT_FILENAME rather than PATH_TRANSLATED.
; http://php.net/cgi.fix-pathinfo
cgi.fix_pathinfo=1
[...]

Open /etc/apache2/mods-available/fcgid.conf...

vi /etc/apache2/mods-available/fcgid.conf

... and add the line PHP_Fix_Pathinfo_Enable 1 (this line must not go into a <VirtualHost> section because then you would get this error: PHP_Fix_Pathinfo_Enable cannot occur within <VirtualHost> section):

<IfModule mod_fcgid.c>
  AddHandler    fcgid-script .fcgi
  FcgidConnectTimeout 20
  PHP_Fix_Pathinfo_Enable 1
</IfModule>

Then restart Apache:

/etc/init.d/apache2 restart

 

3 Creating Vhosts For www.example1.com And www.example2.com

I will now create two vhosts, www.example1.com (with the document root /var/www/web1/web) and www.example2.com (with the document root /var/www/web2/web). www.example1.com will be owned by the user and group web1, and www.example2.com by the user and group web2.

First we create the users and groups:

groupadd web1
groupadd web2
useradd -s /bin/false -d /var/www/web1 -m -g web1 web1
useradd -s /bin/false -d /var/www/web2 -m -g web2 web2

Then we create the document roots and make them owned by the users/groups web1 resp. web2:

mkdir -p /var/www/web1/web
chown web1:web1 /var/www/web1/web
mkdir -p /var/www/web2/web
chown web2:web2 /var/www/web2/web

We will run PHP using suExec; suExec's document root is /var/www, as the following command shows:

 /usr/lib/apache2/suexec -V

[email protected]:~# /usr/lib/apache2/suexec -V
 -D AP_DOC_ROOT="/var/www"
 -D AP_GID_MIN=100
 -D AP_HTTPD_USER="www-data"
 -D AP_LOG_EXEC="/var/log/apache2/suexec.log"
 -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
 -D AP_UID_MIN=100
 -D AP_USERDIR_SUFFIX="public_html"
[email protected]:~#

Therefore we cannot call the PHP binary (/usr/lib/cgi-bin/php) directly because it is located outside suExec's document root. As suExec does not allow symlinks, the only way to solve the problem is to create a wrapper script for each web site in a subdirectory of /var/www; the wrapper script will then call the PHP binary /usr/lib/cgi-bin/php. The wrapper script must be owned by the user and group of each web site, therefore we need one wrapper script for each web site. I'm going to create the wrapper scripts in subdirectories of /var/www/php-fcgi-scripts, e.g. /var/www/php-fcgi-scripts/web1 and /var/www/php-fcgi-scripts/web2.

mkdir -p /var/www/php-fcgi-scripts/web1
mkdir -p /var/www/php-fcgi-scripts/web2

vi /var/www/php-fcgi-scripts/web1/php-fcgi-starter
#!/bin/sh
PHPRC=/etc/php5/cgi/
export PHPRC
export PHP_FCGI_MAX_REQUESTS=5000
export PHP_FCGI_CHILDREN=8
exec /usr/lib/cgi-bin/php
vi /var/www/php-fcgi-scripts/web2/php-fcgi-starter
#!/bin/sh
PHPRC=/etc/php5/cgi/
export PHPRC
export PHP_FCGI_MAX_REQUESTS=5000
export PHP_FCGI_CHILDREN=8
exec /usr/lib/cgi-bin/php

The PHPRC line contains the directory where the php.ini file is located (i.e., /etc/php5/cgi/ translates to /etc/php5/cgi/php.ini). PHP_FCGI_MAX_REQUESTS is the maximum number of requests before an fcgid process is stopped and a new one is launched. PHP_FCGI_CHILDREN defines the number of PHP children that will be launched.

The php-fcgi-starter scripts must be executable, and they (and the directories they are in) must be owned by the web site's user and group:

chmod 755 /var/www/php-fcgi-scripts/web1/php-fcgi-starter
chmod 755 /var/www/php-fcgi-scripts/web2/php-fcgi-starter
chown -R web1:web1 /var/www/php-fcgi-scripts/web1
chown -R web2:web2 /var/www/php-fcgi-scripts/web2

Now we create the Apache vhosts for www.example1.com and www.example2.com:

vi /etc/apache2/sites-available/web1
<VirtualHost *:80>
  ServerName www.example1.com
  ServerAlias example1.com
  ServerAdmin [email protected]
  DocumentRoot /var/www/web1/web/

  <IfModule mod_fcgid.c>
    SuexecUserGroup web1 web1
    <Directory /var/www/web1/web/>
      Options +ExecCGI
      AllowOverride All
      AddHandler fcgid-script .php
      FCGIWrapper /var/www/php-fcgi-scripts/web1/php-fcgi-starter .php
      Order allow,deny
      Allow from all
    </Directory>
  </IfModule>

  # ErrorLog /var/log/apache2/error.log
  # CustomLog /var/log/apache2/access.log combined
  ServerSignature Off

</VirtualHost>
a2ensite web1
vi /etc/apache2/sites-available/web2
<VirtualHost *:80>
  ServerName www.example2.com
  ServerAlias example2.com
  ServerAdmin [email protected]
  DocumentRoot /var/www/web2/web/

  <IfModule mod_fcgid.c>
    SuexecUserGroup web2 web2
    <Directory /var/www/web2/web/>
      Options +ExecCGI
      AllowOverride All
      AddHandler fcgid-script .php
      FCGIWrapper /var/www/php-fcgi-scripts/web2/php-fcgi-starter .php
      Order allow,deny
      Allow from all
    </Directory>
  </IfModule>

  # ErrorLog /var/log/apache2/error.log
  # CustomLog /var/log/apache2/access.log combined
  ServerSignature Off

</VirtualHost>
a2ensite web2

Make sure you fill in the right paths (and the correct user and group in the SuexecUserGroup line).

Reload Apache afterwards:

/etc/init.d/apache2 reload
Share this page:

3 Comment(s)

Add comment

Please register in our forum first to comment.

Comments

By: Nigel

not sure if anyone is here to read this... but is this config supposed execute an fcgi instance for subdirectories of the virtual server? I couldn't get it to do that for the life of me... so apparently for my development server, the only way I've been able to make it work is, I have to create separate wrapper scripts for EACH directory!

I have followed this tutorial AMOST to a T... (I had to do a few things differently, to get things to work at all... I'll try to outline the differences here):

==================================={{after the chown of /var/www/web1/web, I had to chmod because it wouldn't work until I did}}

sudo chmod -R g+rwX /var/www/web1/web

======={{I don't recall exactly why (obviously just to make it work, or at least the way I need it to...), but I also changed the /var/www/php-fcgi-scripts/web1/php-fcgi-starter file}}

#!/bin/sh#PHPRC=/var/www/web1/#export PHPRCexport PHP_FCGI_MAX_REQUESTS=5000export PHP_FCGI_CHILDREN=8exec /usr/lib/cgi-bin/php -c /var/www/web1/public_html

======={{for some reason I created the /etc/apache2/sites-available/web1 file with a .conf extension, and as for the contents of that file...}}

<Virtualhost _default_:80> {{don't know where that _default_ came from... clearly I've followed a few bits of advice here and there...}}ServerName web1.192.168.1.69.xip.io {{xip.io provides "wildcard DNS" for self-referential purposes}}ServerAlias 104.197.178.174 {{the public IP for the Google Cloud Platform I was previously testing on}}DocumentRoot /var/www/web1/public_html/

<IfModule...<Directory...Options +ExecCGI -Indexes +FollowSymLinks +MultiViewsAddHandler fcgid-script .php .htm .htmlFCGIWrapper /var/www/php-fcgi-scripts/web1/php-fcgi-starter .phpFCGIWrapper /var/www/php-fcgi-scripts/web1/php-fcgi-starter .htmFCGIWrapper /var/www/php-fcgi-scripts/web1/php-fcgi-starter .html

{{I've also uncommented the logging features and set "LogLevel warn"}}

===================================

and that's about all I have to say at this point. I got it work... the rest of the things I've done are things specific to my project.

Thankx for your time, whoever might answer this!

By: nigel

actuallly..... I must be tired... what I meant was:

is this config supposed to enable processing of php.ini files per-directory? I'm also unsure if php.ini settings are supposed to apply recursively... I'm not even trying to install multiple vhosts - just trying to mimic the environment that my live web host runs on.

 

By: Nigel

ok lol. reporting back. my very logical process, where I used a txt file to document every command and file contents as I went along... well my logic was a little faulty, and I must have forgotten to back out of a "branch of logic" as I was troubleshooting... long story short? my /var/www/php-fcgi-scripts/web1/php-fcgi-starter file custom edits did work, but the default also works, and apparently, loads the default php.ini just fine... now the only problem is that I want individual php.ini files in each folder to be loaded instead... is there a way to automatically make this happen, or an 'acceptable' way to deal with it?(and yeah... I messed up the line breaks on my first comment)