Farewell MMG, Howdy A8C!

Today is my last day at Moguldom. I’ve decided that I need to disconnect from the business end of things for a bit and go back to my roots – writing code, innovating, and building a better Internet for everyone.

So what’s next for me? I’ve been invited to join the folks at Automattic, where I’ll be writing code and supporting users of Jetpack and WordPress.com! I’m super excited to take on the challenge and join a group of talented people that I have respected for a long time. This new adventure starts in Vienna, Austria in just a few days, at WordCamp Europe.

WordPress has always “felt like home” to me – so it’s only natural that I wanted to join A8C, whose products power a huge number of websites, from personal blogs to big media publishers. I’ll have a chance to reinvest in open source, work with the greatest minds in WordPress, and innovate once again. Maybe one day I can focus on scaling business again, but for now I can take a deep breath and go back to the big picture.

To my friends at Moguldom – I hope it goes without saying – I wish you all the very best that life, love, and business have to offer. You have been an amazing team to work with and you will forever hold a special place in my heart.

To my new friends at A8C – Thank you all for the warm welcome. I’m looking forward to the chaos. See you in Vienna!

WordPress Multisite on CentOS 7.2, Nginx 1.9.5, PHP7, batcache, and HTTP/2

It was time for an overhaul of my go-to WordPress stack. For a long while, I’ve been running PHP 5.4 on CentOS 6 with Varnish. Technical times are changing and it was overdue for an upgrade; so I bring you this!

My first impression of PHP 7 – WOW, Just WOW! The speed increase is phenomenal compared to PHP 5. It seems like the browser is no longer waiting on the server, but the other way around. WordPress is so fast on this setup that it feels like a native app. Perhaps even better/faster than a native app. I almost can’t believe it.

As for HTTP/2 – supposedly this new protocol is faster, but I don’t really see a huge difference with it on versus off. I’m sure its advantages will become apparent over time when we start to integrate more technology into our websites.

I decided to go away from varnish since I’m now running Batcache. Previously, I was a fan of W3TC, but have migrated away from that plugin (it’s become a bloated/buymebuyme nightmare now). This means you lose support for gzip, browser caching, and CDN settings from within WordPress, but you don’t really need to worry about those settings after they’re manually set up in Nginx anyways. Anyways, Batcache versus Varnish – I don’t really see a huge difference. If anything, the cache invalidation is much easier with Batcache. It’s a set-it-and-forget-it type of system and it “just works”.

On one of my production machines running this stack, I show load averages dropping from 3-4 down to less than 1. This is on a 16-core server, so that’s a significant performance gain. Again – I almost can’t believe it, but the numbers speak for themselves. As for memory usage, I don’t see much difference, but everything looks good and clean.

Specifications

Prerequisites

sudo yum update # update the system
sudo iptables -F # Flush iptables - we'll rebuild later
sudo yum install epel-release # Install EPEL repo
sudo yum install https://mirror.webtatic.com/yum/el7/webtatic-release.rpm
sudo yum install zlib-devel make gcc

Now, download the latest nginx source to a convenient location and compile it.

The Nginx Build

./configure \
--user=nginx \
--group=nginx \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--with-http_gzip_static_module \
--with-http_stub_status_module \
--with-http_ssl_module \
--with-pcre \
--with-file-aio \
--with-http_realip_module \
--with-http_v2_module \
--without-http_scgi_module \
--without-http_uwsgi_module

Then, of course, make and make install and nginx should compile. You can test this with /usr/sbin/nginx -c /etc/nginx/nginx.conf. You’ll want to kill that process after you test it.

Next, you need to be able to start/stop/reload nginx. Add this script to /etc/init.d/nginx:

#!/bin/sh
#
# nginx - this script starts and stops the nginx daemon
#
# chkconfig:   - 85 15
# description:  NGINX is an HTTP(S) server, HTTP(S) reverse \
#               proxy and IMAP/POP3 proxy server
# processname: nginx
# config:      /etc/nginx/nginx.conf
# config:      /etc/sysconfig/nginx
# pidfile:     /var/run/nginx.pid

# Source function library.
. /etc/rc.d/init.d/functions

# Source networking configuration.
. /etc/sysconfig/network

# Check that networking is up.
[ "$NETWORKING" = "no" ] & exit 0

nginx="/usr/sbin/nginx"
prog=$(basename $nginx)

NGINX_CONF_FILE="/etc/nginx/nginx.conf"

[ -f /etc/sysconfig/nginx ] & . /etc/sysconfig/nginx

lockfile=/var/lock/subsys/nginx

make_dirs() {
   # make required directories
   user=`$nginx -V 2>&1 | grep "configure arguments:" | sed 's/[^*]*--user=\([^ ]*\).*/\1/g' -`
   if [ -z "`grep $user /etc/passwd`" ]; then
       useradd -M -s /bin/nologin $user
   fi
   options=`$nginx -V 2>&1 | grep 'configure arguments:'`
   for opt in $options; do
       if [ `echo $opt | grep '.*-temp-path'` ]; then
           value=`echo $opt | cut -d "=" -f 2`
           if [ ! -d "$value" ]; then
               # echo "creating" $value
               mkdir -p $value & chown -R $user $value
           fi
       fi
   done
}

start() {
    [ -x $nginx ] || exit 5
    [ -f $NGINX_CONF_FILE ] || exit 6
    make_dirs
    echo -n $"Starting $prog: "
    daemon $nginx -c $NGINX_CONF_FILE
    retval=$?
    echo
    [ $retval -eq 0 ] & touch $lockfile
    return $retval
}

stop() {
    echo -n $"Stopping $prog: "
    killproc $prog -QUIT
    retval=$?
    echo
    [ $retval -eq 0 ] & rm -f $lockfile
    return $retval
}

restart() {
    configtest || return $?
    stop
    sleep 1
    start
}

reload() {
    configtest || return $?
    echo -n $"Reloading $prog: "
    killproc $nginx -HUP
    RETVAL=$?
    echo
}

force_reload() {
    restart
}

configtest() {
  $nginx -t -c $NGINX_CONF_FILE
}

rh_status() {
    status $prog
}

rh_status_q() {
    rh_status > /dev/null 2>&1
}

case "$1" in
    start)
        rh_status_q & exit 0
        $1
        ;;
    stop)
        rh_status_q || exit 0
        $1
        ;;
    restart|configtest)
        $1
        ;;
    reload)
        rh_status_q || exit 7
        $1
        ;;
    force-reload)
        force_reload
        ;;
    status)
        rh_status
        ;;
    condrestart|try-restart)
        rh_status_q || exit 0
            ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}"
        exit 2
esac

Okay, the hard part is over with. If you can start and stop nginx with service nginx start and service nginx stop, then you’re good to go. Time to have some fun. Let’s install some more software.

# Install memcached
sudo yum install -y memcached

# Install mariadb (mysql)
sudo yum install -y mariadb-server mariadb-client

# Install PHP 7
sudo yum install -y php70w php70w-fpm php70w-mysql php70w-opcache php70w-devel php70w-gd php70w-mbstring php70w-xml

# Install ntp, set timezone, set date
sudo yum install -y ntp ntpdate
sudo mv /etc/localtime /etc/localtime.bk
sudo ln -s /usr/share/zoneinfo/America/New_York /etc/localtime
sudo ntpdate pool.ntp.org

# Install some other tools that you'll want to have ready
sudo yum install -y vim htop screen

Configure Nginx

Now you’ll want to set up your nginx.conf and conf.d/* files how you want them. Some key things to remember:

  • If you want to use HTTP/2, you’ll need SSL. If you want free (and easy) SSL certificates, check out LetsEncrypt.
  • On WordPress Multisite (subdomain installs), you’ll want a certificate for each subdomain and each mapped domain. That means you will need a minimum of two certificates for your root domain, and two certificates for each subsequent mapped domain. Each *.yourdomain.com subdomain requires it’s own certificate, and each domain you map to a subdomain requires yet another.

The default nginx.conf is probably okay, though nginx may complain about one or more listen directives (if so, just change whatever it says to change). Here a sample default.conf that includes WordPress rewrite support, concatenation, browser caching, and gzip:

server {
	listen 80;
	server_name {{ actual_hostname }};

	root {{ actual_webroot }};
	index index.php index.html index.htm;

	client_max_body_size 100M;

	gzip on;
	gzip_disable "msie6";
	gzip_vary on;
	gzip_proxied any;
	gzip_comp_level 6;
	gzip_buffers 16 8k;
	gzip_http_version 1.1;
	gzip_types text/plain application/json application/x-javascript application/xml application/xml+rss text/javascript;

	error_page 404 /404.html;
	location /404.html {
		root {{ actual_webroot }};
	}

	error_page 500 502 503 504 /50x.html;
	location = /50x.html {
		root {{ actual_webroot }};
	}

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

	# Allow access to script concatenation engine
	location /_static/ {
		fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
		include /etc/nginx/fastcgi_params;
		fastcgi_param SCRIPT_FILENAME $document_root/wp-content/mu-plugins/http-concat/ngx-http-concat.php;
		include fastcgi_params;
	}

	# Browser cache static assets and do not access log
	location ~* \.(jpg|jpeg|gif|png|css|js|ico|svg)$ {
		expires max;
		access_log off;
		log_not_found off;
	}

	# Block access to PHP files in uploads
	location ~* /(?:uploads|files)/.*\.php$ {
		deny all;
	}

	# Whitelist IPs for nginx status
	location /nginx_status {
		stub_status on;
		access_log off;
		allow 127.0.0.1;
		deny all;
	}

	# All PHP files
	location ~ \.php$ {
		try_files $uri $uri/ /index.php?q=$uri&$args;
		fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
		fastcgi_index index.php;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
		include fastcgi_params;
	}
}

This file came from my ansible install.yml so you’ll need to make a few obvious string replacements. You’ll also want to duplicate this into ssl.conf and modify accordingly to accommodate your domain(s) and SSL requirements. Activating HTTP/2 is as easy as modifying the listen directive to reflect listen 443 ssl http2;.

There are a ton of other configuration items to address and tweak, such as php.ini and /etc/php-fpm.d/www.conf, and don’t forget to set up /etc/sysconfig/memcached… but I’ll leave those fine details to you. Remember to build your firewall also.

As always, if you need pointers, feel free to hit me up in the comments.

Create WordPress Test Users From Breaking Bad Characters

Obviously, you should only use this in development environments. This is used to create an abundance of test authors (named after popular TV show characters) in WordPress for testing.

Run this from bash terminal:

wp user create wwhite wwhite@breakingbad.com --role=author --display_name="Walter White" --first_name="Walter" --last_name="White" --send-email=false
wp user create swhite swhite@breakingbad.com --role=author --display_name="Skyler White" --first_name="Skyler" --last_name="White" --send-email=false
wp user create fwhite fwhite@breakingbad.com --role=author --display_name="Flynn White" --first_name="Flynn" --last_name="White" --send-email=false
wp user create hwhite hwhite@breakingbad.com --role=author --display_name="Holly White" --first_name="Holly" --last_name="White" --send-email=false
wp user create jpinkman jpinkman@breakingbad.com --role=author --display_name="Jesse Pinkman" --first_name="Jesse" --last_name="Pinkman" --send-email=false
wp user create gfring gfring@breakingbad.com --role=author --display_name="Gustavo Fring" --first_name="Gustavo" --last_name="Fring" --send-email=false
wp user create hschrader hschrader@breakingbad.com --role=author --display_name="Hank Schrader" --first_name="Hank" --last_name="Schrader" --send-email=false
wp user create mschrader mschrader@breakingbad.com --role=author --display_name="Marie Schrader" --first_name="Marie" --last_name="Schrader" --send-email=false
wp user create sgoodman sgoodman@breakingbad.com --role=author --display_name="Saul Goodman" --first_name="Saul" --last_name="Goodman" --send-email=false
wp user create mehrmantraut mehrmantraut@breakingbad.com --role=author --display_name="Mike Ehrmantraut" --first_name="Mike" --last_name="Ehrmantraut" --send-email=false
wp user create lrodarte-quayle lrodarte-quayle@breakingbad.com --role=author --display_name="Lydia Rodarte-Quayle" --first_name="Lydia" --last_name="Rodarte-Quayle" --send-email=false
wp user create talquist talquist@breakingbad.com --role=author --display_name="Todd Alquist" --first_name="Todd" --last_name="Alquist" --send-email=false
wp user create sgomez sgomez@breakingbad.com --role=author --display_name="Steve Gomez" --first_name="Steve" --last_name="Gomez" --send-email=false
wp user create tsalamanca tsalamanca@breakingbad.com --role=author --display_name="Tuco Salamanca" --first_name="Tuco" --last_name="Salamanca" --send-email=false
wp user create tbeneke tbeneke@breakingbad.com --role=author --display_name="Ted Beneke" --first_name="Ted" --last_name="Beneke" --send-email=false
wp user create bwolynetz bwolynetz@breakingbad.com --role=author --display_name="Bogdan Wolynetz" --first_name="Bogdan" --last_name="Wolynetz" --send-email=false
wp user create gboetticher gboetticher@breakingbad.com --role=author --display_name="Gale Boetticher" --first_name="Gale" --last_name="Boetticher" --send-email=false
wp user create tkitt tkitt@breakingbad.com --role=author --display_name="Tyrus Kitt" --first_name="Tyrus" --last_name="Kitt" --send-email=false
wp user create deladio deladio@breakingbad.com --role=author --display_name="Don Eladio" --first_name="Don" --last_name="Eladio" --send-email=false
wp user create jbolsa jbolsa@breakingbad.com --role=author --display_name="Juan Bolsa" --first_name="Juan" --last_name="Bolsa" --send-email=false
wp user create hsalamanca hsalamanca@breakingbad.com --role=author --display_name="Hector Salamanca" --first_name="Hector" --last_name="Salamanca" --send-email=false
wp user create lsalamanca lsalamanca@breakingbad.com --role=author --display_name="Leonel Salamanca" --first_name="Leonel" --last_name="Salamanca" --send-email=false
wp user create msalamanca msalamanca@breakingbad.com --role=author --display_name="Marco Salamanca" --first_name="Marco" --last_name="Salamanca" --send-email=false
wp user create acantillo acantillo@breakingbad.com --role=author --display_name="Andrea Cantillo" --first_name="Andrea" --last_name="Cantillo" --send-email=false
wp user create bcantillo bcantillo@breakingbad.com --role=author --display_name="Brock Cantillo" --first_name="Brock" --last_name="Cantillo" --send-email=false
wp user create jmargolis jmargolis@breakingbad.com --role=author --display_name="Jane Margolis" --first_name="Jane" --last_name="Margolis" --send-email=false
wp user create dmargolis dmargolis@breakingbad.com --role=author --display_name="Donald Margolis" --first_name="Donald" --last_name="Margolis" --send-email=false
wp user create bmayhew bmayhew@breakingbad.com --role=author --display_name="Brandon Mayhew" --first_name="Brandon" --last_name="Mayhew" --send-email=false
wp user create cortega cortega@breakingbad.com --role=author --display_name="Christian Ortega" --first_name="Christian" --last_name="Ortega" --send-email=false
wp user create spete spete@breakingbad.com --role=author --display_name="Skinny Pete" --first_name="Skinny" --last_name="Pete" --send-email=false
wp user create apinkman apinkman@breakingbad.com --role=author --display_name="Adam Pinkman" --first_name="Adam" --last_name="Pinkman" --send-email=false
wp user create jpinkman jpinkman@breakingbad.com --role=author --display_name="Jake Pinkman" --first_name="Jake" --last_name="Pinkman" --send-email=false
wp user create hbabineaux hbabineaux@breakingbad.com --role=author --display_name="Huell Babineaux" --first_name="Huell" --last_name="Babineaux" --send-email=false
wp user create pkuby pkuby@breakingbad.com --role=author --display_name="Patrick Kuby" --first_name="Patrick" --last_name="Kuby" --send-email=false
wp user create harchilleya harchilleya@breakingbad.com --role=author --display_name="Hugo Archilleya" --first_name="Hugo" --last_name="Archilleya" --send-email=false
wp user create lcorbett lcorbett@breakingbad.com --role=author --display_name="Louis Corbett" --first_name="Louis" --last_name="Corbett" --send-email=false
wp user create cmolina cmolina@breakingbad.com --role=author --display_name="Carmen Molina" --first_name="Carmen" --last_name="Molina" --send-email=false
wp user create gschwartz gschwartz@breakingbad.com --role=author --display_name="Gretchen Schwartz" --first_name="Gretchen" --last_name="Schwartz" --send-email=false
wp user create eschwartz eschwartz@breakingbad.com --role=author --display_name="Elliott Schwartz" --first_name="Elliott" --last_name="Schwartz" --send-email=false
wp user create dsharp dsharp@breakingbad.com --role=author --display_name="Drew Sharp" --first_name="Drew" --last_name="Sharp" --send-email=false
wp user create jwelker jwelker@breakingbad.com --role=author --display_name="Jack Welker" --first_name="Jack" --last_name="Welker" --send-email=false
wp user create dmolina dmolina@breakingbad.com --role=author --display_name="Domingo Molina" --first_name="Domingo" --last_name="Molina" --send-email=false
wp user create ekoyama ekoyama@breakingbad.com --role=author --display_name="Emilio Koyama" --first_name="Emilio" --last_name="Koyama" --send-email=false
wp user create dwachsberger dwachsberger@breakingbad.com --role=author --display_name="Dan Wachsberger" --first_name="Dan" --last_name="Wachsberger" --send-email=false
wp user create dmarkowski dmarkowski@breakingbad.com --role=author --display_name="Dennis Markowski" --first_name="Dennis" --last_name="Markowski" --send-email=false
wp user create bgoodman bgoodman@breakingbad.com --role=author --display_name="Barry Goodman" --first_name="Barry" --last_name="Goodman" --send-email=false
wp user create rforenall rforenall@breakingbad.com --role=author --display_name="Ron Forenall" --first_name="Ron" --last_name="Forenall" --send-email=false
wp user create dchow dchow@breakingbad.com --role=author --display_name="Duane Chow" --first_name="Duane" --last_name="Chow" --send-email=false
wp user create marciniega marciniega@breakingbad.com --role=author --display_name="Maximino Arciniega" --first_name="Maximino" --last_name="Arciniega" --send-email=false
wp user create troberts troberts@breakingbad.com --role=author --display_name="Tim Roberts" --first_name="Tim" --last_name="Roberts" --send-email=false
wp user create gmerkert gmerkert@breakingbad.com --role=author --display_name="George Merkert" --first_name="George" --last_name="Merkert" --send-email=false

This is also available on GitHub.

For extra credit, here is one for Prison Break characters:

wp user create mscofield mscofield@prisonbreak.com --role=author --display_name="Michael Scofield" --first_name="Michael" --last_name="Scofield" --send-email=false
wp user create lburrows lburrows@prisonbreak.com --role=author --display_name="Lincoln Burrows" --first_name="Lincoln" --last_name="Burrows" --send-email=false
wp user create vdonovan vdonovan@prisonbreak.com --role=author --display_name="Veronica Donovan" --first_name="Veronica" --last_name="Donovan" --send-email=false
wp user create lburrows lburrows@prisonbreak.com --role=author --display_name="LJ Burrows" --first_name="LJ" --last_name="Burrows" --send-email=false
wp user create fsucre fsucre@prisonbreak.com --role=author --display_name="Fernando Sucre" --first_name="Fernando" --last_name="Sucre" --send-email=false
wp user create tbagwell tbagwell@prisonbreak.com --role=author --display_name="Theodore Bagwell" --first_name="Theodore" --last_name="Bagwell" --send-email=false
wp user create jabruzzi jabruzzi@prisonbreak.com --role=author --display_name="John Abruzzi" --first_name="John" --last_name="Abruzzi" --send-email=false
wp user create bbellick bbellick@prisonbreak.com --role=author --display_name="Brad Bellick" --first_name="Brad" --last_name="Bellick" --send-email=false
wp user create stancredi stancredi@prisonbreak.com --role=author --display_name="Sara Tancredi" --first_name="Sara" --last_name="Tancredi" --send-email=false
wp user create pkellerman pkellerman@prisonbreak.com --role=author --display_name="Paul Kellerman" --first_name="Paul" --last_name="Kellerman" --send-email=false
wp user create amahone amahone@prisonbreak.com --role=author --display_name="Alexander Mahone" --first_name="Alexander" --last_name="Mahone" --send-email=false
wp user create jwhistler jwhistler@prisonbreak.com --role=author --display_name="James Whistler" --first_name="James" --last_name="Whistler" --send-email=false
wp user create nst.john nst.john@prisonbreak.com --role=author --display_name="Norman St.John" --first_name="Norman" --last_name="St.John" --send-email=false
wp user create slugo slugo@prisonbreak.com --role=author --display_name="Sofia Lugo" --first_name="Sofia" --last_name="Lugo" --send-email=false
wp user create gmorgan gmorgan@prisonbreak.com --role=author --display_name="Gretchen Morgan" --first_name="Gretchen" --last_name="Morgan" --send-email=false
wp user create dself dself@prisonbreak.com --role=author --display_name="Donald Self" --first_name="Donald" --last_name="Self" --send-email=false
wp user create jkrantz jkrantz@prisonbreak.com --role=author --display_name="Jonathan Krantz" --first_name="Jonathan" --last_name="Krantz" --send-email=false
wp user create bkim bkim@prisonbreak.com --role=author --display_name="Bill Kim" --first_name="Bill" --last_name="Kim" --send-email=false
wp user create creynolds creynolds@prisonbreak.com --role=author --display_name="Caroline Reynolds" --first_name="Caroline" --last_name="Reynolds" --send-email=false
wp user create cscofield cscofield@prisonbreak.com --role=author --display_name="Christina Scofield" --first_name="Christina" --last_name="Scofield" --send-email=false
wp user create dapolskis dapolskis@prisonbreak.com --role=author --display_name="David Apolskis" --first_name="David" --last_name="Apolskis" --send-email=false
wp user create cwestmoreland cwestmoreland@prisonbreak.com --role=author --display_name="Charles Westmoreland" --first_name="Charles" --last_name="Westmoreland" --send-email=false
wp user create cpatoshik cpatoshik@prisonbreak.com --role=author --display_name="Charles Patoshik" --first_name="Charles" --last_name="Patoshik" --send-email=false
wp user create gfiorello gfiorello@prisonbreak.com --role=author --display_name="Gus Fiorello" --first_name="Gus" --last_name="Fiorello" --send-email=false
wp user create msanchez msanchez@prisonbreak.com --role=author --display_name="Manche Sanchez" --first_name="Manche" --last_name="Sanchez" --send-email=false
wp user create abalz-johnson abalz-johnson@prisonbreak.com --role=author --display_name="Avocado Balz-Johnson" --first_name="Avocado" --last_name="Balz-Johnson" --send-email=false
wp user create shoffner shoffner@prisonbreak.com --role=author --display_name="Seth Hoffner" --first_name="Seth" --last_name="Hoffner" --send-email=false
wp user create ctrokey ctrokey@prisonbreak.com --role=author --display_name="Christopher Trokey" --first_name="Christopher" --last_name="Trokey" --send-email=false
wp user create jbuchanon jbuchanon@prisonbreak.com --role=author --display_name="Jason Buchanon" --first_name="Jason" --last_name="Buchanon" --send-email=false
wp user create hpope hpope@prisonbreak.com --role=author --display_name="Henry Pope" --first_name="Henry" --last_name="Pope" --send-email=false
wp user create epavelka epavelka@prisonbreak.com --role=author --display_name="Ed Pavelka" --first_name="Ed" --last_name="Pavelka" --send-email=false
wp user create rgeary rgeary@prisonbreak.com --role=author --display_name="Roy Geary" --first_name="Roy" --last_name="Geary" --send-email=false
wp user create lpatterson lpatterson@prisonbreak.com --role=author --display_name="Louis Patterson" --first_name="Louis" --last_name="Patterson" --send-email=false
wp user create kstolte kstolte@prisonbreak.com --role=author --display_name="Keith Stolte" --first_name="Keith" --last_name="Stolte" --send-email=false
wp user create rgreen rgreen@prisonbreak.com --role=author --display_name="Rizzo Green" --first_name="Rizzo" --last_name="Green" --send-email=false
wp user create mandrews mandrews@prisonbreak.com --role=author --display_name="Mack Andrews" --first_name="Mack" --last_name="Andrews" --send-email=false
wp user create thudson thudson@prisonbreak.com --role=author --display_name="Tyler Hudson" --first_name="Tyler" --last_name="Hudson" --send-email=false
wp user create kwelch kwelch@prisonbreak.com --role=author --display_name="Katie Welch" --first_name="Katie" --last_name="Welch" --send-email=false
wp user create ksklar ksklar@prisonbreak.com --role=author --display_name="Kenny Sklar" --first_name="Kenny" --last_name="Sklar" --send-email=false
wp user create nsavrinn nsavrinn@prisonbreak.com --role=author --display_name="Nick Savrinn" --first_name="Nick" --last_name="Savrinn" --send-email=false
wp user create sabruzzi sabruzzi@prisonbreak.com --role=author --display_name="Sylvia Abruzzi" --first_name="Sylvia" --last_name="Abruzzi" --send-email=false
wp user create havila havila@prisonbreak.com --role=author --display_name="Hector Avila" --first_name="Hector" --last_name="Avila" --send-email=false
wp user create dbelle dbelle@prisonbreak.com --role=author --display_name="Debra Belle" --first_name="Debra" --last_name="Belle" --send-email=false
wp user create pcordero pcordero@prisonbreak.com --role=author --display_name="Petey Cordero" --first_name="Petey" --last_name="Cordero" --send-email=false
wp user create mdelgado mdelgado@prisonbreak.com --role=author --display_name="Maricruz Delgado" --first_name="Maricruz" --last_name="Delgado" --send-email=false
wp user create tdelgado tdelgado@prisonbreak.com --role=author --display_name="Theresa Delgado" --first_name="Theresa" --last_name="Delgado" --send-email=false
wp user create pfalzone pfalzone@prisonbreak.com --role=author --display_name="Philly Falzone" --first_name="Philly" --last_name="Falzone" --send-email=false
wp user create ofibonacci ofibonacci@prisonbreak.com --role=author --display_name="Otto Fibonacci" --first_name="Otto" --last_name="Fibonacci" --send-email=false
wp user create dfranklin dfranklin@prisonbreak.com --role=author --display_name="Dede Franklin" --first_name="Dede" --last_name="Franklin" --send-email=false
wp user create kfranklin kfranklin@prisonbreak.com --role=author --display_name="Kacee Franklin" --first_name="Kacee" --last_name="Franklin" --send-email=false
wp user create shollander shollander@prisonbreak.com --role=author --display_name="Susan Hollander" --first_name="Susan" --last_name="Hollander" --send-email=false
wp user create dmorgan dmorgan@prisonbreak.com --role=author --display_name="Darius Morgan" --first_name="Darius" --last_name="Morgan" --send-email=false
wp user create arix arix@prisonbreak.com --role=author --display_name="Adrian Rix" --first_name="Adrian" --last_name="Rix" --send-email=false
wp user create lrix lrix@prisonbreak.com --role=author --display_name="Lisa Rix" --first_name="Lisa" --last_name="Rix" --send-email=false
wp user create gsmallhouse gsmallhouse@prisonbreak.com --role=author --display_name="Gavin Smallhouse" --first_name="Gavin" --last_name="Smallhouse" --send-email=false
wp user create dsweeney dsweeney@prisonbreak.com --role=author --display_name="Derek Sweeney" --first_name="Derek" --last_name="Sweeney" --send-email=false
wp user create nvolek nvolek@prisonbreak.com --role=author --display_name="Nika Volek" --first_name="Nika" --last_name="Volek" --send-email=false
wp user create rglenn rglenn@prisonbreak.com --role=author --display_name="Roland Glenn" --first_name="Roland" --last_name="Glenn" --send-email=false
wp user create flang flang@prisonbreak.com --role=author --display_name="Felicia Lang" --first_name="Felicia" --last_name="Lang" --send-email=false
wp user create mwheeler mwheeler@prisonbreak.com --role=author --display_name="Mark Wheeler" --first_name="Mark" --last_name="Wheeler" --send-email=false

Again, available on GitHub.

Get Up and Running with LetsEncrypt.org Certificates on CentOS 6 and nginx

If you don’t have git installed, use Yum to install it.

sudo yum install git

Stop nginx, if it’s running. LetsEncrypt needs to bind to port 80 and/or 443 to verify your web server.

sudo service nginx stop

Switch to root and git clone the letsencrypt repository.

sudo su
cd ~
git clone https://github.com/letsencrypt/letsencrypt

Now cd to letsencrypt and run letsencrypt-auto.

cd letsencrypt
./letsencrypt-auto certonly --debug

You’ll be greeted with several questions, first to enter your email address, second to agree to the TOS, and third to enter the domain name that you wish to encrypt.

letsencrypt0

letsencrypt1

letsencrypt2

Note: At this time, even though the prompt specifies you can input multiple domain names, it only works with a single domain name.

LetsEncrypt will do it’s thing for about 10-20 seconds and you’ll be dropped back to the command line with several cryptic looking messages. Don’t stress, do this:

cd /etc/letsencrypt/live/
ls -lha

You should now be looking at a directory for your domain name. If you cd into it, you should be looking at several pem files:

  • cert.pem – This is your certificate file
  • chain.pem – This is your certificate chain
  • fullchain.pem – This is the full certificate chain
  • privkey.pem – This is your private key file

If you see all of those files, you’re in good shape! Time to configure nginx.

Now, nginx configurations come in many flavors, and mine is probably much different than yours, so I’ll just give you a crash course here. Feel free to ask specific questions in the comments below and I’d be happy to help you along.

Navigate to /etc/nginx/conf.d and create a copy of your NON-SSL configuration file (call it whatever you want, just append .conf to it). Depending on how you’re set up, it could be default.conf or virtual.conf. You want to modify whichever configuration file that contains your server blocks. Now vi/vim/nano your copy, modify the listen directive inside your server block to reflect the following:

listen 443 ssl;

Now add a few lines below your server_name directive as follows:

ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

Obviously use the correct domain name in the ssl_certificate and ssl_certificate_key directives. The entire thing should look something like this:

server {
	listen 443 ssl;
	server_name yourdomain.com;
	ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
	...

Save the file, take a deep breath, and issue the following command:

sudo service nginx configtest

If you see syntax is ok and test is successful, start nginx by typing

sudo service nginx start

Now your domain should be accessible via HTTPS! Check it out at https://yourdomain.com.

Now, this is only the beginning of your journey to SSL integration. There are likely several tasks left that I suggest you take care of:

  1. Don’t disable vanilla HTTP yet, but maybe redirect HTTP traffic to it’s corresponding HTTPS URL. There are several ways to do this, but I suggest an nginx approach.
  2. If you’re on WordPress or another CMS, you will likely need to reconfigure your site’s URL within that system. On WordPress, there are a number of ways to do this, my favorite is define( 'wp_home', 'https://yourdomain.com' ); and define( 'wp_siteurl', 'https://yourdomain.com' ); which can be placed inside wp-config.php in your sites root. You may also want to install a plugin to handle all the fancy SSL stuff (like str_replace()ing all your content urls so you don’t get mixed content warnings). I recommend Really Simple SSL by Rogier Lankhorst – it worked right out of the box on my WP Multisite.
  3. If you’re on a WordPress multisite, or another site that has more than one domain name, you should generate a certificate for each of those domain names. For instance, on WP Multisite with subdomains enabled and domain mapping, you will have two domain names which need SSL protection, one for the back end and one for the front end.

In future releases of the LetsEncrypt.org libraries, generation of SSL certificates will be far more automated. This is what worked for me in the public beta phase, but there are probably other methods that work as well. Feel free to share what works for you below.

Footnotes:

  • LetsEncrypt.org certificates currently have a 3-month lifetime. This means you’ll need to renew your certificate quarterly for now. But it’s very easy to do so, and as mentioned above, there will be some pretty cool automation coming soon to make this seamless.
  • If you are indeed running WordPress, get excited – Zack Tollman is working on a plugin to integrate WP-CLI with LetsEncrypt, which will make generating and configuring certificates with WordPress as easy as wp cert new!

Now go forth and be secure!

Truncate a WordPress Database To X-Number of Posts

I regularly work with huge MySQL database files (10G-100G) so making copies and importing them locally for use in VVV is extremely cumbersome (at best). Here are some MySQL statements that allow you to truncate all but the most recent 1000 posts in your WordPress database. These are useful because they allow you to truncate the database to a manageable size before copying to your local environment or development server. Unless you have something unusual, these statements should retain all post-to-postmeta associations, comment-to-post associations, and term-to-post associations.

WARNING: DO NOT run these on your production database. You must make a copy of the whole database first and then run these statements on the copy. You have been warned!

Delete all but the most recent 1000 posts:

DELETE FROM wp_posts WHERE ID NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts WHERE post_type="post" AND post_status="publish" ORDER BY ID DESC LIMIT 1000 ) foo );

Delete all post meta that is not associated with the remaining posts:

DELETE FROM wp_postmeta WHERE post_id NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts ) foo );

Delete all comments that are not associated with a remaining post:

DELETE FROM wp_comments WHERE comment_post_ID NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts ) foo );

Delete all comment meta that is not associated with a remaining post:

DELETE FROM wp_commentmeta WHERE comment_ID NOT IN ( SELECT comment_ID FROM ( SELECT comment_ID FROM wp_comments ) foo );

Delete term relationships that don’t associate to a remaining post:

DELETE FROM wp_term_relationships WHERE object_id NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts ) foo );

Delete terms that are no longer related to an existing post:

DELETE FROM wp_terms WHERE term_id NOT IN ( SELECT term_taxonomy_id FROM ( SELECT term_taxonomy_id FROM wp_term_relationships ) foo );

As an added bonus, delete transients from the options table:

DELETE FROM wp_options WHERE option_name like '%_transient_%';

And here’s the whole thing in one shot – script it in if you wish:

DELETE FROM wp_posts WHERE ID NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts WHERE post_type="post" AND post_status="publish" ORDER BY ID DESC LIMIT 1000 ) foo );
DELETE FROM wp_postmeta WHERE post_id NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts ) foo );
DELETE FROM wp_comments WHERE comment_post_ID NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts ) foo );
DELETE FROM wp_commentmeta WHERE comment_ID NOT IN ( SELECT comment_ID FROM ( SELECT comment_ID FROM wp_comments ) foo );
DELETE FROM wp_term_relationships WHERE object_id NOT IN ( SELECT ID FROM ( SELECT ID FROM wp_posts ) foo );
DELETE FROM wp_terms WHERE term_id NOT IN ( SELECT term_taxonomy_id FROM ( SELECT term_taxonomy_id FROM wp_term_relationships ) foo );
DELETE FROM wp_options WHERE option_name like '%_transient_%';

Easy Automatic Facebook Share Counts (Fully Javascript)

Drop this little gem in your site’s header:

<script>
function numberFormat(x) {
	return x.toString().replace(/B(?=(d{3})+(?!d))/g, ',');
}

jQuery(document).ready(function($){
	$('.trigger_facebook_count').each(function(){
		var url = $(this).attr('data-url');
		var that = $(this);

		$.get('http://graph.facebook.com/' + url, function(data) {
			that.html(numberFormat(data.shares));
		});	
	});
});
</script>

Then, add elements to your page like so:

<div class="trigger_facebook_count" data-url="http://bossip.com"></div>

Remember to change the data-url to whatever URL you are referencing, and viola! You’re done.