Nicholas Skinner

Freelance website and web application developer

Archive for December, 2009

Setting Up Apache / PHP For Virtual Hosting Using suEXEC

Sunday, December 20th, 2009

Apache LogoSetting up Apache securely for multiple system users each with their own domain name(s), and subdomains on CentOS 5.

This post is a follow on to: Setting Up Exim Mail Server For Multiple Domains, it describes the configuration I am currently running on my own server for web hosting.

I looked into various options, and in terms of balancing features (i.e. not restricting the functionality that can be used in PHP), security (not using e.g. PHP Safe Mode that is no longer recommended, or requiring 777 permissions on directories that need to be writable), install complexity, and maintainability for CentOS 5 this seemed to be the best approach. All software is sourced from standard CentOS repositories therefore can be installed / updated easily via “yum”. The trade-off of this approach is performance, PHP is running as CGI therefore will not be as fast as using PHP as a module or using something like FastCGI.

To keep things secure each person who is in control of a particular domain name/names will be setup with a single user account on the system. The hosting space for these domains will then be placed in that users home directory. This approach has the advantage that there is not a direct link between domains and user accounts, meaning that a (perhaps more traditional) one domain per user account approach can be taken but also has the advantage that if a single person has multiple domains they can all be setup in a single user account meaning there is no need to e.g. setup multiple SSH/SFTP/FTP logins on the client side, and on the server side those domains can access each others files/resources without any permissions issues, or alternate workaround being needed. Each user account has a single Apache configuration file meaning a single place to e.g. setup a WordPress MU install for a specific user on the system.

Install Packages

  1. Install Apache:

    yum install httpd

  2. Install PHP, and any additional PHP extensions you need:

    yum install php
    yum install php-gd
    yum install php-mysql
    yum install php-mbstring
    yum install php-imap
    yum install php-soap
    yum install php-xml

Configure Apache

  1. Set the daemon to start on bootup, and start the service:

    chkconfig httpd on
    service httpd start

  2. Create directory to hold virtual host configuration files:

    mkdir /etc/httpd/virtualdomains
    chmod 700 /etc/httpd/virtualdomains

  3. Create directory to hold virtual host log files:

    mkdir /var/log/domlogs
    chmod 701 /var/log/domlogs

  4. Add the following in at the end of your “/etc/httpd/conf/httpd.conf” file:

    #Turn off default following of symlinks (will turn it back on later as SymLinksIfOwnerMatch)
    <directory />
    Options -FollowSymLinks
    </directory>

    #Provide default install page
    <LocationMatch "^/+$">
    Options -Indexes
    ErrorDocument 403 /error/noindex.html
    </LocationMatch>

    #Allow system users to make use of various options such as SymLinksIfOwnerMatch (for mod_rewrite etc) in their .htaccess files
    <Directory /home>
    Options +Indexes +SymLinksIfOwnerMatch
    AllowOverride FileInfo AuthConfig Limit Indexes
    </Directory>

    #Enable PHP (only for .php extension), and add it to pages considered as index
    <FilesMatch \.php$>
    SetHandler application/x-httpd-php5
    </FilesMatch>
    AddType text/html .php
    DirectoryIndex index.php

    #Virtual hostnames
    NameVirtualHost *:80

    #Default page for domains not setup
    <VirtualHost *:80>
    DocumentRoot /var/www/html
    </VirtualHost>

    #Include all the individual virtual host configuration files for processing
    Include virtualdomains/*.conf

  5. Comment out everything in “/etc/httpd/conf.d” this default file runs PHP as a module.
  6. Restart Apache such that it reads in the new configuration:

    service httpd restart

User Account Setup

Follow this setup process for each system user account that will be hosting a website / websites.

Note: The system user account “nstech” is used in this example, it is assumed this system user has already been setup using e.g. “useradd nstech”.

  1. Create a file (will be blank initially) to hold Apache virtual host configuration:

    touch /etc/httpd/virtualdomains/nstech.conf
    chmod 644 /etc/httpd/virtualdomains/nstech.conf

  2. Allow all system user accounts (inclusing the “apache” user account) to recurse the directory tree:

    chmod 711 /home/nstech

  3. For security reasons suEXEC has a number of restrictions on what it will allow to be executed, therefore each user will need a copy of the PHP binary that has its “user” and “group” set to that accounts username, has appropriate permissions, and is placed in a suitable directory such as “/var/www/cgi-bin” (suEXEC has a hard coded path that it is not possible to change without recompiling). However copying PHP for each user is inefficient, therefore e.g. a bash script can be used that then calls the PHP binary itself:

    mkdir /var/www/cgi-bin/nstech
    chown nstech:nstech /var/www/cgi-bin/nstech
    chmod 755 /var/www/cgi-bin/nstech

    /var/www/cgi-bin/nstech/php-cgi.bash
    ------------------------------------
    #!/bin/bash

    /usr/bin/php-cgi "$@"

    (script from Stuart Herbert's PHP Blog)

    chown nstech:nstech /var/www/cgi-bin/nstech/php-cgi.bash
    chmod 755 /var/www/cgi-bin/nstech/php-cgi.bash

  4. Create a directory to hold all log files for the users domains:

    mkdir /var/log/domlogs/nstech
    chmod 750 /var/log/domlogs/nstech
    chown root:nstech /var/log/domlogs/nstech

Domain / Subdomain Setup

Follow this process for each new domain or subdomain that will be setup in a users account.

Note: The domain name “www.ns-tech.co.uk” and user account “nstech” are used in this example

  1. Create a directory on the file system that will serve as the webspace for the domain being setup, and set appropriate permissions:

    mkdir /home/nstech/www.ns-tech.co.uk
    chmod 750 /home/nstech/www.ns-tech.co.uk
    chown nstech:apache /home/nstech/www.ns-tech.co.uk

  2. Create (initially blank) log files for the user, and give the user permissions to view them (e.g. read them via PHP scripts or SSH).

    touch /var/log/domlogs/nstech/www.ns-tech.co.uk-error_log
    chmod 750 /var/log/domlogs/nstech/www.ns-tech.co.uk-error_log
    chown root:nstech /var/log/domlogs/nstech/www.ns-tech.co.uk-error_log

    touch /var/log/domlogs/nstech/www.ns-tech.co.uk-access_log
    chmod 750 /var/log/domlogs/nstech/www.ns-tech.co.uk-access_log
    chown root:nstech /var/log/domlogs/nstech/www.ns-tech.co.uk-access_log

    touch /var/log/domlogs/nstech/www.ns-tech.co.uk-bytes_log
    chmod 750 /var/log/domlogs/nstech/www.ns-tech.co.uk-bytes_log
    chown root:nstech /var/log/domlogs/nstech/www.ns-tech.co.uk-bytes_log

  3. Append the virtual host configuration onto the existing virtual host configuration file for this user:

    /etc/httpd/virtualdomains/nstech.conf
    -------------------------------------
    <VirtualHost *:80>
    SuexecUserGroup nstech nstech
    DocumentRoot /home/nstech/www.ns-tech.co.uk
    ServerName www.ns-tech.co.uk
    ServerAlias ns-tech.co.uk
    ErrorLog /var/log/domlogs/nstech/www.ns-tech.co.uk-error_log
    CustomLog /var/log/domlogs/nstech/www.ns-tech.co.uk-access_log combined
    CustomLog /var/log/domlogs/nstech/www.ns-tech.co.uk-bytes_log "%I %O"
    Action application/x-httpd-php5 "/cgi-bin/nstech/php-cgi.bash"
    </VirtualHost>

  4. Restart Apache such that it reads in the new configuration:

    service httpd restart

Note: As far as I am aware the setup process described is reasonably secure however as with securing anything somewhat complex such as PHP, with Apache, and multiple system users but still allowing a reasonable amount of functionality there are a number of thing to consider. You are advised to test the security of this setup yourself in case I have missed something. Any issues please leave a comment below.