Actions

System Administrator's Guide/Installing Mahara/Mahara on fastcgi//nginx

From Mahara Wiki

< System Administrator's Guide‎ | Installing Mahara
Revision as of 13:04, 12 May 2011 by WikiSysop (talk | contribs)

Mahara does not require the fairly bloated apache/mod_php environment. It runs fine in fastcgi environments and therefore with all other webservers supporting fastcgi, e.g. lighttpd and nginx.

Please note however that this is not officially supported by the Mahara team, although we will try to fix bugs found and we will accept patches. If you encounter bugs in Mahara, make sure you point out you're running Mahara using fastcgi/nginx!

Setup Instructions

It's not too hard to set up. You need to install and configure nginx, set up fastcgi, and configure php.ini somewhat.

Here is a simple, tested nginx config that works with Mahara on an Ubuntu Lucid host:

server {
        listen   80 default;
        server_name  example.com;
        root   /var/www/example.com;
        index index.php;
        server_tokens off;

        access_log  /var/log/nginx/example.com.access.log;

        location / {
                try_files $uri $uri/;
                expires 3d;

                gzip  on;
                gzip_disable "MSIE [1-6]\.(?!.*SV1)";
                gzip_types text/css application/x-javascript;
        }

        location ~ \.php$ {
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_intercept_errors on;
                fastcgi_pass 127.0.0.1:9000;
        }

        location ~ /\.ht {
                deny  all;
        }
}

server {
        server_name www.example.com;
        rewrite ^ $scheme://example.com$request_uri? permanent;
}

Note the following points:

  • This configuration avoids many common Mahara and nginx configuration pitfalls. The location regex match for .php files applies first as it is most specific, and passes all PHP scripts through to a fastcgi server running at 127.0.0.1:9000. Naturally, this means you need fastcgi running there - this nginx config alone isn't enough to run Mahara, you should read below for more info about this.
  • The config blocks requests to .htaccess and friends, which nginx doesn't consider special. You'll do your PHP configuration entirely in php.ini and friends as part of the fastcgi server setup.
  • The location / block is the last block handled. try_files means that nginx tries to serve the file statically. If found, the files are served with an expires header of 3 days, gzipped in browsers that support it. Note that Mahara doesn't manage static file versioning at all, so you may want to reduce the expires time.
  • There are two server blocks, the second one is to redirect www.example.com to example.com. Doing it with two blocks is nice and clear, and works correctly with Mahara so you don't mess up Mahoodle integration.

Along with the nginx configuration, you need PHP running as fastcgi. Thankfully, this is quite easy - PHP ships with a cgi binary that can run in fastcgi mode. Under ubuntu, install the php5-cgi package to get it. Then, put the following into /etc/init.d/php or similar:

#! /bin/sh
 
 ### BEGIN INIT INFO
 # Provides:          php
 # Required-Start:    $local_fs $remote_fs $network $syslog
 # Required-Stop:     $local_fs $remote_fs $network $syslog
 # Default-Start:     2 3 4 5
 # Default-Stop:      0 1 6
 # Short-Description: starts the php fastcgi server
 # Description:       starts php using start-stop-daemon
 ### END INIT INFO
 
 BIND=127.0.0.1:9000
 USER=www-data
 PHP_FCGI_CHILDREN=5
 PHP_FCGI_MAX_REQUESTS=1000
 
 PHP_CGI=/usr/bin/php-cgi
 PHP_CGI_NAME=`basename $PHP_CGI`
 PHP_CGI_ARGS="- USER=$USER PATH=/usr/bin PHP_FCGI_CHILDREN=$PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS=$PHP_FCGI_MAX_REQUESTS $PHP_CGI -b $BIND"
 RETVAL=0
 
 start() {
       echo -n "Starting PHP FastCGI: "
       start-stop-daemon --quiet --start --background --chuid "$USER" --exec /usr/bin/env -- $PHP_CGI_ARGS
       RETVAL=$?
       echo "$PHP_CGI_NAME."
 }
 stop() {
       echo -n "Stopping PHP FastCGI: "
       killall -q -w -u $USER $PHP_CGI
       RETVAL=$?
       echo "$PHP_CGI_NAME."
 }
 
 case "$1" in
     start)
       start
   ;;
     stop)
       stop
   ;;
     restart)
       stop
       start
   ;;
     *)
       echo "Usage: php-fastcgi {start|stop|restart}"
       exit 1
   ;;
 esac
 exit $RETVAL
 
 

Note some points about this file:

  • You'll need to chmod +x it, and you probably want to rig it up to start on boot. (fixme: someone put instructions here for doing that)
  • You seriously don't need as many children as you think. Nginx will handle all of the static files, so you only need enough to handle all the concurrent PHP requests you think you'll get. If you run PHP with eaccelerator or similar, you should be able to push through most pages in 250 milliseconds or less, meaning one child can handle four requests per second. Running too many children will actually cause more pain than it's worth, as the children will contend for CPU and hold RAM without actually speeding things up. If you have a single-CPU machine, it actually makes sense to just have one child. YMMV - benchmark on your hardware to work out the minimum number of children that get the best result for you.

Finally - remember to configure your php.ini. Check Mahara's .htaccess and configure the php.ini the way the .htaccess does. Also make sure you set cgi.fix_pathinfo to 0, else you'll potentially be vulnerable to arbitrary code injection attacks (Mahara should already protect you against these, but cgi.fix_pathinfo is unnecessary at best, and dangerous at worse).

With the above setup on a VPS with 600MB of RAM, Mahara can push through 20 reqests per second. That's 20 PHP pages, so well more than 20 concurrent users, and that's just on a VPS. Dedicated hardware should easily handle far higher loads.

SSL

This example demonstrates a setup using both plain http as well as ssl in multiple domains. At the time of writing, mahara (1.13) works nicely with this kind of setup. Multi-Domain setups will however break the communication toward moodle.

server {
        listen          443;
        listen           80;

        server_name     www.example.de, www.example.net, www.example.org ;
        optimize_server_names on;

        # Note: single domain setups obviously need only one listen directive
        # and one single domain declaration in server_name

        # ssl setup
        ssl             on;
        ssl_certificate      /etc/nginx/example.crt;
        ssl_certificate_key  /etc/nginx/example.key;
        add_header           Front-End-Https    on;

        # logging
        access_log      /var/log/nginx/example/access.log;
        error_log       /var/log/nginx/example/error.log;

        # enable for debugging purpose
        # error_log     /var/log/nginx/example/error.log debug;

        location / {
            root        /var/www/example;
            index       index.html index.php;
        }

        location ~ .php {
            include     /etc/nginx/fastcgi_params;
            fastcgi_param            HTTPS on;
            fastcgi_param SCRIPT_FILENAME /var/www/example$fastcgi_script_name;
            fastcgi_param QUERY_STRING    $query_string;
            if (-f $request_filename) {
                break;
            }
                fastcgi_pass  127.0.0.1:9999;
        }

#
# these declarations are taken from a similar setup for CMSIMPLE and have yet to be adopted
# mahara, which should be straightforward. Highly recommended for production sites.
#

#
# security: deny access to all places which only the mahara scripts may see

#        location ~ /(classes|functions|misc|modules|includes|db|locale|lib)/ {
#            deny       all;
#        }

#      
# serve static files directly

#        location ^/.*+.(jpg|jpeg|gif|css|png|js|ico|htm|html)$ {
#           root /var/www/example;
#            access_log        off;
#            expires           30d;
#        }

    }
}


In addition, the following settings have to be present /etc/nginx/fastcgi_params


fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

## bea ++
fastcgi_pass_header Authorization;
fastcgi_intercept_errors off;

And finally the code snippet required in Mahara's config.php to enable multiple domains if desired:

$cfg->wwwroot = 'http'
  . (isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] == 'on' ? 's' : '' : '');
$cfg->wwwroot .= '://' .$_SERVER['HTTP_HOST'] .'/';