Advanced PHP configuration

From DreamHost
Jump to: navigation, search
The instructions provided in this article or section are considered advanced.

You are expected to be knowledgeable in the UNIX shell.
Support for these instructions is not available from DreamHost tech support.
Server changes may cause this to break. Be prepared to troubleshoot this yourself if this happens.
We seriously aren't kidding about this.

Introduction

This article is intended to bundle up some of the more complex scenarios of PHP configuration into a single place.

If you follow the steps outlined here you will optionally end up with:

  1. The ability to have a local php binary with a custom php.ini file and to keep that binary and ini file synced with the current Dreamhost php.ini
  2. The ability to share that local php custom install amongst all your domains in your home directory
  3. For PHP 5, the ability to wrap that custom install in a fastcgi wrapper
  4. The ability to run PHP 4 and PHP 5 in a single domain (in different directories)

Instructions

These instructions will walk through steps to setup PHP5, with options along the way to also set up PHP4 at the same time.

Step 1 - Set up local PHP directory

First SSH into your home directory, then issue the following commands relevant to your target version/s of PHP.

PHP 4

mkdir -m 771 ~/php

PHP 5

mkdir -m 771 ~/php5

PHP 4 and 5

Run both the above commands.

Step 2 (optonal) - Create FastCGI wrapper

PHP 5 only.

PHP 5

cat > ~/php5/php5-wrapper.fcgi << "EOF"
#!/bin/bash
export PHP_FCGI_CHILDREN=3
exec ./php5.cgi
EOF

Then set it to be executable:

chmod 0750 ~/php5/php5-wrapper.fcgi

Step 3 - Create php-update shell script

Create a new file for the shell script.

PHP 4

touch ~/php/php-update.sh

PHP 5

touch ~/php5/php-update.sh

PHP 4 and 5

Copy the following code into a the new file using a text editor.

#!/bin/bash

# CLI script for updating a local copy of php in Dreamhost accounts.
#
# Copyright (c) 2008, Sam Bauers
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the copyright holder nor the names of any
#       contributors may be used to endorse or promote products derived from
#       this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# @author Sam Bauers
# @version 0.1.7b
# @copyright Sam Bauers, 2008
# @license BSD

echo
echo "=========================================================="
echo "php-update for Dreamhost version 0.1.7b"
echo "Copyright (C) 2006 Sam Bauers"
echo "----------------------------------------------------------"
echo "php-update for Dreamhost comes with ABSOLUTELY NO WARRANTY"
echo "This is free software, and you are welcome to redistribute"
echo "it under certain conditions."
echo
echo "See source code for details."
echo "=========================================================="
echo

PATH=/usr/local/php5/bin/:$PATH
php -version

UPDATEDIR=$(dirname $0)

php $UPDATEDIR/php-update.php

if [[ -e "$UPDATEDIR/php.cgi" ]]
then
    echo
    echo "----------------------------------------------------------"
    echo "Finally - attempting to make php.cgi executable"
    echo "----------------------------------------------------------"
    
    chmod 0750 $UPDATEDIR/php.cgi
fi

if [[ -e "$UPDATEDIR/php5.cgi" ]]
then
    echo
    echo "----------------------------------------------------------"
    echo "Finally - attempting to make php5.cgi executable"
    echo "----------------------------------------------------------"
    
    chmod 0750 $UPDATEDIR/php5.cgi
fi

Step 4 - Create php-update PHP script

Create a new file for the PHP script.

PHP 4

touch ~/php/php-update.php

PHP 5

touch ~/php5/php-update.php

PHP 4 and 5

Copy the following code into a the new file using a text editor.

#!/usr/local/php5/bin/php -q
<?php
// Note "-q" in shebang used to suppress header output

/**
 * CLI script for updating a local copy of php in Dreamhost accounts.
 *
 * Copyright (c) 2008, Sam Bauers
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the copyright holder nor the names of any
 *       contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @author Sam Bauers
 * @version 0.1.7
 * @copyright Sam Bauers, 2008
 * @license BSD
 */

// Determine the version of PHP to update (4 or 5) based on the directory
$directories = explode('/', dirname(__FILE__));
$directories = array_reverse($directories);
$directory = $directories[0];

switch ($directory) {
    case 'php':
        $php = 'php';
        break;
    case 'php5':
    default:
        $php = 'php5';
        break;
}

// The full paths to the source cgi and ini
$sourcecgi = '/dh/cgi-system/' . $php . '.cgi';
$sourceini = '/etc/' . $php . '/cgi/php.ini';

// Get the HOME environment variable
$home = $_SERVER['HOME'];

// The full paths to the target cgi and ini
$targetcgi = $home . '/' . $php . '/' . $php . '.cgi';
$targetini = $home . '/' . $php . '/php.ini';

// The full path to the ini file kept for comparison with the source ini
$unmodifiedini = $targetini . '.unmodified';
$updatetargetini = FALSE;

// Modifications to be made to the ini file after it is updated
// - If you need to change these then you can do so here, then
//   delete your target ini files and run the script again
$inimodifications = array(
    'output_buffering = 0',
    'post_max_size = 100M',
    'upload_max_filesize = 100M'
);

// Update the CGI
echo("\n");
echo('----------------------------------------------------------' . "\n");
echo('Attempting CGI update' . "\n");
echo('----------------------------------------------------------' . "\n");
if (!file_exists($sourcecgi)) {
    echo('!!! SOURCE CGI does not exist - aborting CGI update' . "\n");
} else {
    echo('--> SOURCE CGI exists' . "\n");
    if (!file_exists($targetcgi)) {
        echo('    --> TARGET CGI does not exist - make the initial copy' . "\n");
        if (copy($sourcecgi, $targetcgi)) {
            echo('        --> SOURCE CGI copied to TARGET CGI' . "\n");
        } else {
            echo('        !!! copy from SOURCE CGI to TARGET CGI failed - check permissions on target directory' . "\n");
        }
    } else {
        echo('--> TARGET CGI exists' . "\n");
        echo('--> compare source to target using md5' . "\n");
        $sourcecgimd5 = md5_file($sourcecgi);
        $targetcgimd5 = md5_file($targetcgi);
        if ($sourcecgimd5 !== $targetcgimd5) {
            echo('    --> they are different - update the TARGET CGI' . "\n");
            if (copy($sourcecgi, $targetcgi)) {
                echo('        --> SOURCE CGI copied to TARGET CGI' . "\n");
                /*
                if (chmod($targetcgi, 0750)) {
                    echo('            --> execute permission set on TARGET CGI' . "\n");
                } else {
                    echo('            !!! could not set execute permission on TARGET CGI - try manual chmod' . "\n");
                }
                */
            } else {
                echo('        !!! copy from SOURCE CGI to TARGET CGI failed - check permissions on target directory' . "\n");
            }
        } else {
            echo('    --> they are the same - take no action' . "\n");
        }
    }
}
echo('----------------------------------------------------------' . "\n");
echo("\n");

// Update the INI
echo('----------------------------------------------------------' . "\n");
echo('Attempting INI update' . "\n");
echo('----------------------------------------------------------' . "\n");
if (!file_exists($sourceini)) {
    echo('!!! SOURCE INI does not exist - aborting INI update' . "\n");
} else {
    echo('--> SOURCE INI exists' . "\n");
    if (!file_exists($unmodifiedini)) {
        echo('    --> UNMODIFIED INI does not exist - make the initial copy' . "\n");
        if (copy($sourceini, $unmodifiedini)) {
            echo('        --> SOURCE INI copied to UNMODIFIED INI' . "\n");
            $updatetargetini = TRUE;
        } else {
            echo('        !!! copy from SOURCE INI to UNMODIFIED INI failed - check permissions on target directory' . "\n");
        }
    } else {
        echo('--> UNMODIFIED INI exists' . "\n");
        echo('--> compare source to unmodified using md5' . "\n");
        $sourceinimd5 = md5_file($sourceini);
        $unmodifiedinimd5 = md5_file($unmodifiedini);
        if ($sourceinimd5 !== $unmodifiedinimd5) {
            echo('    --> they are different - update the UNMODIFIED INI' . "\n");
            if (copy($sourceini, $unmodifiedini)) {
                echo('        --> SOURCE INI copied to UNMODIFIED INI' . "\n");
                $updatetargetini = TRUE;
            } else {
                echo('        !!! copy from SOURCE INI to UNMODIFIED INI failed - check permissions on target directory' . "\n");
            }
        } else {
            echo('    --> they are the same - take no action' . "\n");
        }
    }
}
echo('----------------------------------------------------------' . "\n");

// Modify the target INI if required
if ($updatetargetini) {
    echo("\n");
    echo('----------------------------------------------------------' . "\n");
    echo('Attempting INI modification' . "\n");
    echo('----------------------------------------------------------' . "\n");
    if (copy($unmodifiedini, $targetini)) {
        echo('--> UNMODIFIED INI copied to TARGET INI' . "\n");
        if (count($inimodifications) <= 0) {
            echo('    !!! no modifications specified' . "\n");
        } else {
            echo('    --> attempt to append modifications to TARGET INI' . "\n");
            $appendstring = "\n\n\n";
            $appendstring .= ';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;' . "\n";
            $appendstring .= '; Dreamhost php-update script ;' . "\n";
            $appendstring .= ';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;' . "\n";
            $appendstring .= ';' . "\n";
            $appendstring .= '; Script location -> ' . __FILE__ . "\n";
            $appendstring .= "\n";
            $appendecho = '            --> appended the following ini settings:' . "\n";
            foreach ($inimodifications as $iniline) {
                $appendstring .= $iniline . "\n";
                $appendecho .= '                * ' . $iniline . "\n";
            }
            $appendedbytes = file_put_contents($targetini, $appendstring, FILE_APPEND);
            if ($appendedbytes) {
                echo('        --> modification successful - appended ' . $appendedbytes . ' bytes to TARGET INI' . "\n");
                echo($appendecho);
            } else {
                echo('        !!! modification failed - check permissions on TARGET INI' . "\n");
            }
        }
    } else {
        echo('!!! copy from UNMODIFIED INI to TARGET INI failed' . "\n");
    }
    echo('----------------------------------------------------------' . "\n");
}
?>

Step 5 - make the php-update scripts executable

The scripts need to be executable to run.

PHP 4

chmod 0700 ~/php/php-update.sh
chmod 0700 ~/php/php-update.php

PHP 5

chmod 0700 ~/php5/php-update.sh
chmod 0700 ~/php5/php-update.php

PHP 4 and 5

Run both the above commands.

Step 6 - run the php-update script

Execute the php-update shell script to create your local versions.

If you want to make any customisations to the php.ini, they should be set in the php-update.php file as described on line 62 (version 0.1.4) prior to execution. If you don't setup your customisations in the php script, then they will be overwritten if Dreamhost's source php.ini file changes.

If you change your customisations at any time, you will need to delete the files php.ini and php.ini.unmodified, and then execute the php-update shell script again.

PHP 4

cd ~/php
./php-update.sh

After the script has executed read the output and act on any errors. If there were no errors then check the file listing.

ls -lh ~/php

The output should be like:

-rw-r--r--  1 username pg123456  18K 2004-09-23 06:31 LICENSE
-rwx------  1 username pg123456 7.4K 2004-09-23 06:31 php-update.php
-rwx------  1 username pg123456 2.0K 2004-09-23 06:36 php-update.sh
-rwxr-x---  1 username pg123456 3.3M 2004-09-23 06:36 php.cgi
-rw-rw-r--  1 username pg123456  22K 2004-09-23 06:36 php.ini
-rw-rw-r--  1 username pg123456  22K 2004-09-23 06:36 php.ini.unmodified

PHP 5

cd ~/php5
./php-update.sh

After the script has executed read the output and act on any errors. If there were no errors then check the file listing.

ls -lh ~/php5

The output should be like:

-rwx------  1 username pg123456 7.4K 2004-09-23 06:31 php-update.php
-rwx------  1 username pg123456 2.0K 2004-09-23 06:31 php-update.sh
-rw-rw-r--  1 username pg123456  45K 2004-09-23 06:38 php.ini
-rw-rw-r--  1 username pg123456  45K 2004-09-23 06:38 php.ini.unmodified
-rwxr-x---  1 username pg123456   53 2004-09-23 06:31 php5-wrapper.fcgi      <--- *** FastCGI ONLY ***
-rwxr-x---  1 username pg123456 5.4M 2004-09-23 06:38 php5.cgi

PHP 4 and 5

Run both the above commands.

Step 7 - periodically update the php binary and ini files

The php-update script should be run at some interval to check if there is a newer version of the Dreamhost php binary and ini files.

The script actually checks whether the files have changed and only copies them across to your local version if they have.

Add the script to crontab and check for a new file weekly.

To edit your crontab file type:

crontab -e

Paste the following line/s into the editor that appears:

PHP 4

@weekly /bin/bash $HOME/php/php-update.sh

PHP 5

@weekly /bin/bash $HOME/php5/php-update.sh

PHP 4 and 5

@weekly /bin/bash $HOME/php/php-update.sh
@weekly /bin/bash $HOME/php5/php-update.sh

Close the editor by pressing CTRL-x, follow the prompts it gives you to save and exit.

Step 8 - alias the local php directories into your website domains

You can alias this local install into the base directory of as many websites as you like.

Let's assume the website is setup at ~/example.com

PHP 4

cd ~/example.com
ln -s ~/php .

PHP 5

cd ~/example.com
ln -s ~/php5 .

PHP 4 and 5

cd ~/example.com
ln -s ~/php .
ln -s ~/php5 .

Step 9 - setup .htaccess files

The .htaccess file will point php scripts in your domain/s to the local versions we have setup.

Assume that you are still working on the domain example.com and that it has not got an .htaccess file setup already. If it does, then you simply need to edit the existing file and not run these commands.

PHP 4

cat > ~/example.com/.htaccess << "EOF"
Options +ExecCGI
AddHandler php-cgi .php
Action php-cgi /php/php.cgi
EOF

PHP 5

cat > ~/example.com/.htaccess << "EOF"
Options +ExecCGI
AddHandler php5-cgi .php
Action php5-cgi /php5/php5.cgi
EOF

PHP 4 and 5

This method only allows running of one of the two versions in the base directory, then you can over-ride that version on a per directory basis using another .htaccess file.

These instructions will setup PHP 5 for the whole site and then setup PHP 4 in the subdirectory "testPHP4".

Create the .htaccess file for PHP 5 in the site root directory

cat > ~/example.com/.htaccess << "EOF"
Options +ExecCGI
AddHandler php5-cgi .php
Action php5-cgi /php5/php5.cgi
EOF

Create a test file for PHP 5

cat > ~/example.com/info.php << "EOF"
<?php
phpinfo();
?>
EOF

Create the PHP 4 test directory

mkdir ~/example.com/testPHP4

Create the .htaccess file for PHP 4 in the PHP 4 test directory

cat > ~/example.com/testPHP4/.htaccess << "EOF"
AddHandler php-cgi .php
Action php-cgi /php/php.cgi
EOF

Create a test file for PHP 4 in the PHP 4 directory

cat > ~/example.com/testPHP4/info.php << "EOF"
<?php
phpinfo();
?>
EOF

Visiting the address http://example.com/info.php will show the PHP 5 info page.

Visiting the address http://example.com/testPHP4/info.php will show the PHP 4 info page.

Step 10 - Secure php.ini and php cgi

Protect your new php.ini and php cgi's

In the file ~/example.com/.htaccess add

<FilesMatch "^php5?\.(ini|cgi)$">
Order Deny,Allow
Deny from All
Allow from env=REDIRECT_STATUS
</FilesMatch>

OR: For even better protection, protect the entire directory instead.

Create the file ~/php5/.htaccess and add:

<FilesMatch ".*">
Order Deny,Allow
Deny from All
Allow from env=REDIRECT_STATUS
</FilesMatch>

Step 11 (optional) - FastCGI setup

PHP 5 only.

PHP 5

To use FastCGI with PHP 5 you need to modify any .htaccess that specify use of the php5.cgi binary this:

Remove the following lines from the relevant .htaccess files...

AddHandler php5-cgi .php
Action php5-cgi /php5/php5.cgi

And replace them with these...

AddHandler php5-fastcgi .php
Action php5-fastcgi /php5/php5-wrapper.fcgi

Be careful not to wreck any earlier FastCGI settings in your .htaccess file.

Also make sure that FastCGI is enabled for this domain in the Dreamhost Control Panel.

Troubleshooting

If you get an error about the function "file_put_contents" not being available, this indicates that you are running php-update.php using PHP4 rather than PHP5. Dreamhost defaults to PHP4 for CLI usage.

To correct this, upgrade your php-update.sh script to version 0.1.7b (see above). This will cause php-update.php to run under PHP5. The version of PHP will also be displayed so that you can confirm this. After updating your script, remove the file php.ini.unmodified, and run php-update.php. Removing that file will force your php.ini to be rebuilt.

See also

References

Much of the information used to produce this set of instructions came from the following pages:

Thanks go to their authors and contributors.