Road Trip to Key Largo

Road Trip

Margate, FL to Key Largo, FL

I Open Sourced My Twenty Sixteen Child Theme

Today, I open sourced my Twenty Sixteen child theme. It includes an empty resume page template and a clean travel shortcode that uses Google Maps. You’ll need to check out the source code for usage, but it’s very simple ūüôā

This child theme is meant to be only very small improvements to the WordPress Twenty Sixteen default theme, but they were improvements that I felt were very valuable.

View the code:

Pull requests are welcome.

Tuesdays at WPCOM

I’ve now worked¬†support for for every day of the week, and Tuesdays seem to be the most difficult.

Maybe it’s the crushing hangover from Sunday night still bringing me down, or maybe it’s that our users got to work on Monday and started submitting more difficult tickets. I’ll assume the latter.

I know, I know – I should be a responsible adult and stay away from behavior that could cause such a hangover. But here’s the thing – my wife and I went out and partied, which we have only done a handful of times in our relationship. Between careers, kids, careers, general life tasks, and careers, we don’t get that opportunity often. So when the chance arose, we jumped head first. And we had an amazing time. So was it worth it? Absolutely. Do I regret it? Hell no. Thanks to Automattic for being a part of making that possible.

As for the Tuesday blues, I’ll make it through. I’ve¬†set a personal record for ticket volume already. Perhaps difficulty, productivity, and chaos really do coexist in perfect harmony.

Traveling to Fort Lauderdale

Flight: Vienna to Fort Lauderdale

5,374 miles

Traveling to Vienna

Flight: Fort Lauderdale to Vienna

5,313 miles

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!¬†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!

Memorial Day 2016 in Key Largo

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.



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
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/ \
--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 \

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:

# 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/

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

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

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

prog=$(basename $nginx)


[ -f /etc/sysconfig/nginx ] & . /etc/sysconfig/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
   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

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

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

restart() {
    configtest || return $?
    sleep 1

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

force_reload() {

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

rh_status() {
    status $prog

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

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

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

# 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 * 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;
		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.