Split your Nginx config files up, in order to host multiple WordPress domains

If you are hosting multiple domain on your VPS, then you don’t want to have to copy and paste all the configuration lines every time. Instead keep each domain’s config file small and compact. This makes it easy to see what is going on, makes it easier to change a setting across the board (e.g. increasing the cache period), and in my opinion makes it easier to build a script to do the job for you.

To this end, I thought I’d share an example Nginx WordPress configuration, split into 4 parts that allows you to easily add another domain.

1. The main Nginx server config file (nginx.conf)

user www-data;
worker_processes 4;

error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

events {
  worker_connections  1024;
}

http {
  server_names_hash_max_size 512;
  server_names_hash_bucket_size 128;

  index index.php index.html index.htm;

  include       mime.types;
  default_type  application/octet-stream;

  # Configure FastCGI cache
  fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=WORDPRESS:15m inactive=15m;

  access_log /var/log/nginx/access.log main;
  sendfile   on;

  keepalive_timeout  65;

  gzip  on;
  gzip_disable "MSIE [1-6]\.(?!.*SV1)";

  client_max_body_size       25m;
  client_body_buffer_size    256k;

  include /etc/nginx/conf.d/*.conf;
  include /etc/nginx/sites-enabled/*;
}

2. Handling static resources (css, js, images and so on)

  rewrite /wp-admin$ $scheme://$host$uri/ permanent;

  location ~ /favicon.ico {
    log_not_found off;
    access_log    off;
  }

  location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
    expires       30m;
    access_log    off;
    log_not_found on;
  }

  location ~ /\.ht {
    deny  all;
  }

3. FastCGI handling (and caching)

    try_files $uri =404;

    set $nocache "";
    if ($http_cookie ~ (comment_author_.*|wordpress_logged_in.*|wp-postpass_.*)) {
      set $nocache "Y";
    }

    fastcgi_pass  unix:/var/run/php5-fpm.sock;
    fastcgi_index index.php;
    fastcgi_param CONTENT-LENGTH  $content_length;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO       $fastcgi_script_name;
    include fastcgi_params;

    fastcgi_cache_use_stale error timeout invalid_header http_500;
    fastcgi_cache_key       $request_method$host$request_uri;
    fastcgi_cache           WORDPRESS;
    fastcgi_cache_valid     200 301 302 10m;
    fastcgi_cache_valid     404 30m;
    fastcgi_ignore_headers  Expires Cache-Control;
    fastcgi_cache_bypass    $nocache;
    fastcgi_no_cache        $nocache;

4. Example domain

server {
  listen      178.79.142.110:80;
  server_name www.tcbarrett.com tcbarrett.com;

  error_log  /var/log/nginx/tcbarrett/www.tcbarrett.com-error.log;
  access_log /var/log/nginx/tcbarrett/www.tcbarrett.com-access.log;

  root  /home/tcbarrett/sites/www.tcbarrett.com;

  include static-resources.conf;

  location / {
    try_files $uri $uri/ /index.php?$args;
  }

  location ~ \.php$ {
    include wordpress-fastcgi-cache.conf;
  }

  include purge.conf;
}

As you can see, the 4th config file is small but pertinent. Only the information about the domain itself is in here.

WordPress – redirect traffic to your primary domain

This is a snippet that allows you to redirect all your incoming traffic to a single primary domain.

You may end up with web sites that have multiple domains. Most commonly, site owners will want both their www and non-www domain names to end up in the same place. Later version of WordPress handle this automatically. However, web sites can change domain name for many reasons (e.g company name change, development work on a sub-domain, site restructure and so on).

It generally, and usually, best to control your traffic by configuring your web server to handle any such change. There are times though when this is not possible, or simple that you want (as a WordPress developer) the speed and control that goes with managing it from with WordPress itself.

function tcb_redirect_to_primary_domain() {
  $schema           = is_ssl() ? 'https://' : 'http://';
  $requested_domain = strtolower($schema . $_SERVER['HTTP_HOST']);

  $primary_domain = get_bloginfo('siteurl');
  if( defined('WP_SITEURL') && '' != WP_SITEURL )
    $primary_domain = WP_SITEURL;

  if( empty($primary_domain) ) return; // Something is really wrong.

  $primary_domain = strtolower($primary_domain);

  // strip subdirectories.
  if( preg_match('|^(https?://)([^/]+)/.+|', $primary_domain, $matches) )
    $primary_domain = $matches[1] . $matches[2];

  $primary_domain    = rtrim($primary_domain, '/');
  $requested_domain  = rtrim($requested_domain, '/');

  if( $primary_domain !== $requested_domain ){
    $redirect = $primary_domain . $_SERVER['REQUEST_URI'];
    wp_redirect( $redirect, 302 );
    exit;
  }
}
add_action('template_redirect', 'tcb_redirect_to_primary_domain');

This is pretty much a follow up to Divy Dovy’s post on WordPress domain redirection.

After Dave asked me about it, I realised that what I use at work is at times more simple, and others more complex, but was never really relevant to any other company or person. So I thought I would strip out the irrelevant bits and see what was left. and as you can see it is really not much more than Dave’s excellent snippet. He should give himself more credit!

jQuery AJAX enable your WordPress forms

I’m often building forms for client projects, and they nearly always prefer them have some AJAX goodness. To this end I’ve built a small jQuery file that gets added to all my projects. It depends on Malsup’s AjaxForm and Bassistance’s Form Validation.

/**
 * Generic *SIMPLE* Ajax Form handling
 */
jQuery(document).ready(function($){
 $('.simpleajaxform').each(function(){
  $(this).attr('method', 'post');
  var target = $(this).attr('target');
  var func   = $(this).attr('function');
  options    = {};
  if( target || func ){
   if( target ) $('#'+target).html('').hide();
   options = {
    success:      simpleajaxform_success,
    beforeSubmit: simpleajaxform_submit
   };
  }

  $(this).ajaxForm(options);
 });
});

/**
 * On submit: clear any previous update and tell the user
 *  that we're trying to update
 */
function simpleajaxform_submit(formData, jqForm, options) {
 if( !jqForm.valid() ) return false;
 target = jqForm.attr('target');

 if( target )
  jQuery('#'+target).html('Updating, please wait...').removeClass('updated').addClass('updating').show();
 return true;
}

/**
 * Response: show message in target div.
 */
function simpleajaxform_success(responseText, statusText, xhr, jQForm){
 if( jQForm === undefined )
  jQForm = xhr;
 if( jQForm === undefined ){
  alert('Cannot handle response properly');
  return;
 }
 target = jQForm.attr('target');
 if( target )
  jQuery('#'+target).removeClass('updating').addClass('updated').html(responseText);

 hide = jQForm.attr('hide');
 if( hide )
  jQuery('#'+hide).hide();

 handler = jQForm.attr('function');
 if( handler )
  eval( handler+'(responseText, jQForm)' );
}

Take any form that submits to an AJAX handler (see below for notes on WordPress and ‘action’), add the ‘simpleajaxform‘ class and either a ‘target‘ or ‘function‘ property. Job done.

Here’s a stripped down example using it:

<form class="simpleajaxform" action="<?php echo admin_url('admin-ajax.php');?>" target="targetdiv">
 <input type="hidden" name="action" value="my_wp_action" />
 <input class="required" type="text" name="message" value="" />
 <input type="submit" name="submit" value="DO STUFF" />
</form>

WordPress, Javascript (jQuery) and ACTION

If you add a field named ‘action‘ in a form, then jQuery can get a bit confused by the syntax ‘jQuery(‘#someformid’).attr(‘action’,ajaxurl)‘. I’ll leave figuring that out as an exercise for the reader (or for a later post).

Download showcase plugin here: simple-ajax-form

Remove gravity forms capabilities

The Gravity Forms plugin adds capabilities at the same level as roles (as opposed to adding capabilities to roles). This means that your users (subscribers, contributors e.t.c) may end up with access to your forms in the back end. This is because the plugin checks for that capability, rather than examine the user’s role’s capabilities.

I wrote a filter to check for and strip away this capability on authentication:

/**
 * Removes the capablity, added by Gravity Forms, from all
 *  non administrators when they log in
 *
 * This hook removes that access on login. To trigger it,
 *  the member must log out first, then back in.
 */
add_action('wp_authenticate', 'tcbarrett_authentication');
function tcbarrett_authentication($username){
  remove_gravityform_caps_from_non_admin($username);
}
function remove_gravityform_caps_from_non_admin($username){
  global $wpdb;
  $user_id = username_exists($username);
  if(!$user_id) return;

  $userinfo = get_userdatabylogin($username);
  $property = $wpdb->prefix."capabilities";
  $caps     = $userinfo->$property;
  if( $caps['administrator'] ) return;
  if( $caps['gform_full_access'] ){
    $member = new WP_User($user_id);
    $member->remove_cap("gform_full_access");
  }
}