Setting up PHP and WordPress on Nginx

Introduction

In this post we’ll install PHP5 and WordPress and serve it with Nginx. This post assumes we’ve already set up Nginx.

For our PHP projects, we want the same structure as our Python projects:

/var/www/
`- php-project/
  |- src/
  |- conf/
  |  |- php-fpm.conf
  |  `- nginx.conf
  `- logs/

Install PHP and FPM

We wanted to use uWSGI, but getting PHP running on it was too much trouble, so we settled on FPM.

$ apt-get install php5-fpm php5-cli

Let FPM know about our .conf files living in each of our project directories. Open up /etc/php5/fpm/php-fpm.conf and add the following line at the bottom:

include=/var/www/*/conf/php-fpm.conf

Sample PHP project

We can now try to create a new PHP project and see if it runs. Prepare our project directory:

$ cd /var/www
$ mkdir phptest
$ cd phptest
$ mkdir conf logs src

conf files

FPM provides a template for its conf files in /etc/php5/fpm/pool.d/. Let’s copy one into our conf/ folder and modify it for our project:

$ cp /etc/php5/fpm/pool.d/www.conf conf/php-fpm.conf

Set the conf’s pool name on the first line to the project’s name (default is [www]) and set the listen var to /tmp/$pool.sock ($pool holds the pool name which you set on the first line).

Here’s the full php-fpm.conf file with comments removed:

[phptest]
user = www-data
group = www-data
listen = /tmp/$pool.sock
access.log = /var/www/$pool/logs/php-fpm.log
listen.owner = www-data
listen.group = www-data
listen.mode = 0666


; kept at default values (unmodified) pm = dynamic pm.max_children = 5 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 chdir = / And nginx.conf: server { listen 80; server_name localhost; root /var/www/phptest/src; access_log /var/www/phptest/logs/access.log; error_log /var/www/phptest/logs/error.log;

index index.php index.html index.htm;

try_files $uri $uri/ @rewrite;

location ~ .php$ { fastcgi_pass unix:///tmp/phptest.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }

location @rewrite { rewrite ^/(.*)$ /index.php/$1; }

#include global/restrictions.conf; }

index.php

We are ready to test our PHP project. Let’s create the iconic phpinfo() page.

$ echo "<?php echo phpinfo(); ?>" > src/index.php

Restart Nginx and FPM:

$ /etc/init.d/nginx restart
$ /etc/init.d/php-fpm restart

Navigate to http://localhost:80 to see your phpinfo.

WordPress

Alright, we have PHP installed and running. Let’s set up WordPress in the same structure as our test project.
WordPress needs a bit of configuration to set up, but it is well documented in their Codex entry about Nginx.

First, let’s install MySQL for PHP:

$ apt-get install mysql-server php5-mysql

Then create a database for WordPress:

$ mysql
mysql> CREATE DATABASE my_wordpress_db;
mysql> exit

Global conf files

WordPress’ Codex entry for Nginx provided some conf files to be included for WordPress to work with Nginx. We are using the “single blog” rule (slightly modified) for our setup.

Place the following files in /etc/nginx/global/:

restrictions.conf

# http://codex.wordpress.org/Nginx
# Global restrictions configuration file.
# Designed to be included in any server {} block.
 location = /favicon.ico {
 log_not_found off;
 access_log off;
}


location = /robots.txt { allow all; log_not_found off; access_log off; }

# Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac). # Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban) location ~ /. { deny all; }

# Deny access to any files with a .php extension in the uploads directory # Works in sub-directory installs and also in multisite network # Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban) location ~* /(?:uploads|files)/.*.php$ { deny all; }

wordpress.conf

# WordPress single blog rules.
# Designed to be included in any server {} block.


# This order might seem weird - this is attempted to match last if rules below fail. # http://wiki.nginx.org/HttpCoreModule location / { try_files $uri $uri/ /index.php?$args; }

# Add trailing slash to */wp-admin requests. rewrite /wp-admin$ $scheme://$host$uri/ permanent;

# Directives to send expires headers and turn off 404 error logging. location ~* .(js|css|png|jpg|jpeg|gif|ico)$ { expires 24h; log_not_found off; }

wordpress-w3-total-cache.conf

# BEGIN W3TC Browser Cache
gzip on;
gzip_types text/css application/x-javascript text/richtext image/svg+xml text/plain text/xsd text/xsl text/xml image/x-icon;
location ~ .(css|js)$ {
 expires 31536000s;
 add_header Pragma "public";
 add_header Cache-Control "max-age=31536000, public, must-revalidate, proxy-revalidate";
 add_header X-Powered-By "W3 Total Cache/0.9.2.3";
}
location ~ .(html|htm|rtf|rtx|svg|svgz|txt|xsd|xsl|xml)$ {
 expires 180s;
 add_header Pragma "public";
 add_header Cache-Control "max-age=180, public, must-revalidate, proxy-revalidate";
 add_header X-Powered-By "W3 Total Cache/0.9.2.3";
}
location ~ .(asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|exe|gif|gz|gzip|ico|jpg|jpeg|jpe|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mpe|mpp|odb|odc|odf|odg|odp|ods|odt|ogg|pdf|png|pot|pps|ppt|pptx|ra|ram|swf|tar|tif|tiff|wav|wma|wri|xla|xls|xlsx|xlt|xlw|zip)$ {
 expires 31536000s;
 add_header Pragma "public";
 add_header Cache-Control "max-age=31536000, public, must-revalidate, proxy-revalidate";
 add_header X-Powered-By "W3 Total Cache/0.9.2.3";
}
# END W3TC Browser Cache
# BEGIN W3TC Skip 404 error handling by WordPress for static files
if (-f $request_filename) {
 break;
}
if (-d $request_filename) {
 break;
}
if ($request_uri ~ "(robots.txt|sitemap(_index|[0-9]+)?.xml(.gz)?)") {
 break;
}
if ($request_uri ~* .(css|js|html|htm|rtf|rtx|svg|svgz|txt|xsd|xsl|xml|asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|exe|gif|gz|gzip|ico|jpg|jpeg|jpe|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mpe|mpp|odb|odc|odf|odg|odp|ods|odt|ogg|pdf|png|pot|pps|ppt|pptx|ra|ram|swf|tar|tif|tiff|wav|wma|wri|xla|xls|xlsx|xlt|xlw|zip)$) {
 return 404;
}
# END W3TC Skip 404 error handling by WordPress for static files

wordpress-wp-super-cache.conf

# WP Super Cache rules.
# Designed to be included from a 'wordpress-ms-...' configuration file.


# Enable detection of the .gz extension for statically compressed content. # Comment out this line if static gzip support is not compiled into nginx. gzip_static on;

set $supercacheuri ""; set $supercachefile "$document_root/wp-content/cache/supercache/${http_host}${uri}index.html"; if (-e $supercachefile) { set $supercacheuri "/wp-content/cache/supercache/${http_host}${uri}index.html"; }

# If this is a POST request, pass the request onto WordPress. if ($request_method = POST) { set $supercacheuri ""; }

# If there is a query string, serve the uncached version. if ($query_string) { set $supercacheuri ""; }

# Logged in users and those who have posted a comment get the non-cached version. if ($http_cookie ~* comment_author_|wordpress_logged_in|wp-postpass_) { set $supercacheuri ""; }

# Mobile browsers get the non-cached version. # Wastes CPU cycles if there isn't a mobile browser WP theme for the site. if ($http_x_wap_profile) { set $supercacheuri ""; }

if ($http_profile) { set $supercacheuri ""; }

Configure WordPress

Alright, we’ve got our prerequisites ready, so let’s install and configure WordPress.

$ cd /var/www
$ mkdir my_blog
$ cd my_blog
$ mkdir src conf logs
$ curl -o latest.tar.gz http://wordpress.org/latest.tar.gz
$ tar -xzvf latest.tar.gz
$ mv wordpress src
$ cp src/wp-config-sample.php src/wp-config.php

Open up src/wp-config.php and change the following lines to match your MySQL info:

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'my_wordpress_db');
/** MySQL database username */
define('DB_USER', 'root');
/** MySQL database password */
define('DB_PASSWORD', 'my_root_password');
/** MySQL hostname */
define('DB_HOST', 'localhost');

Now all we need to do is set our project’s conf files.

conf/nginx.conf

server {
 listen 80;
 server_name localhost;
 root /var/www/my_blog/src/wordpress;
 access_log /var/www/my_blog/logs/access.log;
 error_log /var/www/my_blog/logs/error.log;


index index.php index.html index.htm;

try_files $uri $uri/ @rewrite;

include global/restrictions.conf; include global/wordpress.conf;

# Uncomment one of the lines below for the appropriate caching plugin (if used). #include global/wordpress-wp-super-cache.conf; #include global/wordpress-w3-total-cache.conf;

location ~ .php$ { # Zero-day exploit defense. # http://forum.nginx.org/read.php?2,88845,page=3 # Won't work properly (404 error) if the file is not stored on this server, which is entirely possible with php-fpm/php-fcgi. # Comment the 'try_files' line out if you set up php-fpm/php-fcgi on another machine. And then cross your fingers that you won't get hacked. try_files $uri =404;

fastcgi_split_path_info ^(.+.php)(/.+)$; #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini

include fastcgi_params; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # fastcgi_intercept_errors on; fastcgi_pass unix:///tmp/my_blog.sock; }

location @rewrite { rewrite ^/(.*)$ /index.php/$1; } }

conf/php-fpm.conf

[my_blog]
user = www-data
group = www-data
listen = /tmp/$pool.sock
access.log = /var/www/$pool/logs/php-fpm.log
listen.owner = www-data
listen.group = www-data
listen.mode = 0666


; kept at default values (unmodified) pm = dynamic pm.max_children = 5 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 chdir = /

Restart Nginx and FPM then navigate to http://localhost:80/wp-config/ to run WordPress’ configuration process and start blogging!

Making CollabSpot Boards available to Gmail users via Auth

Introduction

CollabSpot Boards (CS Boards) is project management tool integrated with Google Apps. It was first offered only to Google Apps users. But months after the launch, it was also made available to Gmail users. In principle, opening the service to Gmail users wasn’t supposed to be difficult since it was already available for Google Apps users. But for someone like me who did not know much about OAuth, authorization, and authentication, things can get quite complicated. This post aims to introduce some of the terms and different ways for applications such as CS Boards to gain access to a user’s Google Account and possibly to get some feedback in those places that can still be improved.

Auth: authentication and authorization

The CS Boards back-end is written in Python and runs on the Google App Engine platform. It has the concept of cards that represent tasks in a project. Each card can have a due date set to them and CS Boards will automatically create Google Calendar event entry for all members assigned to that particular card. This is one of the examples of how CS Boards integrates with Google Apps.

This kind of access control demonstrated above, i.e. managing user’s calendar data, requires authorization and authentication. Authentication services allow users to sign in to an application using a Google Account, while authorization lets users provide an application with access to the data they have stored in Google Apps. Collectively, authorization and authentication services are often referred as Auth.

Google provides a number of Auth mechanisms:

  • OAuth 2.0 is a new and simplified authorization protocol for all Google APIs. OAuth 2.0 relies on SSL for security instead of requiring your application to do cryptographic signing directly. This protocol allows your application to request access to data associated with a user’s Google Account.
  • OAuth 1.0 requires cryptographic signing on Auth transactions. It provides authorization for all Google APIs.
  • Hybrid protocol provides both authentication and authorization for web applications, allowing users to log in and authorize access to their data in a single step. This protocol uses OpenID to provide authentication services, and OAuth to provide authorization to Google APIs.
  • OpenID authenticates users with their Google Accounts. This allows users to log in to your website without having to sign up for a new account.
  • AuthSub and ClientLogin – Google’s proprietary authorization APIs, available as an alternative to OAuth for most Google APIs.

CS Boards is using OAuth 1.0 2-legged protocol and GData-Python-Client library for Google Apps users (Installed Applications).

Google Data API vs. Google API

API Client libraries provide methods to help developers implement Auth mechanisms and data access (CRUD) simpler. I knew from the start that there are Google APIs for each of their applications, Google Calendar API, Contacts API, and the Drive API. All of these have detailed documentations on how developers can use them. I also knew that each API has older versions. What I didn’t know is that the latest versions of these APIs use different client libraries than the older ones. Different, not in terms of a newer version derived from older versions, but having a totally different library architecture.

These are GData-Python-Client and Google-API-Python-Client. The Google Calendar API, for example, already has three versions. Version 3.0, the latest among them, uses the Google API Client while the older ones use the Google Data Client.

The main differences between the two:

  • Google-API-Python-Client – for CalendarAPI v3, uses JSON data objects for CRUD methods and OAuth 2.0 for Auth.
  • GData-Python-Client – for CalendarAPI v1-2, uses GData Protocol and OAuth 1.0 for Auth. It has two main modules: GDClient and GDataService.

OAuth 1.0’s 2-legged and 3-legged flows

CS Boards uses the OAuth 1.0 Auth mechanism. There are two ways to do OAuth 1.0: 2-legged and 3-legged. The main difference between the two is that 2-legged allows domain-wide delegation of authority and is done only by the domain administrator during app installation. As for 3-legged, also known as the normal authorization flow, it is done per Gmail user where every authorization results to an ACCESS TOKEN which the application can save to the database for future and offline use.

OAuth 2.0 is the latest, simpler, and more convenient way of doing authorizations. It relies on SSL for security instead of requiring your application to do cryptographic signing directly as in OAuth 1.0. But at the time of writing, OAuth 2.0 still does not provide domain-wide authorizations which are needed for Google Apps users and installed applications. We ultimately chose to stick with OAuth 1.0 for now with CS Boards to ensure consistency of doing things both for Google Apps and Gmail users.

Conclusion

Integrating Applications with Google Services provided endless possibilities, we just have used Calendar, Drive, Contacts, and Mail. Yet there is still a long list of APIs that we can integrate with. We’re currently entertaining the idea of integrating CS Boards with Google Hangouts so users can manage their projects while having a live discussion. If you are both a heavy CS Boards user and a heavy Google Apps user, let us know of any ideas you might have to make your project management experience better.