At the Forge

nginx and WordPress

Reuven M. Lerner

Issue #267, July 2016

How to make your WordPress installation highly scalable using nginx.

In my last article, I took an initial look at nginx, the high-performance open-source HTTP that uses a single process and a single thread to service a large number of requests. nginx was designed for speed and scalability, as opposed to Apache, which was designed to maximize flexibility and configuration. But through the years, nginx has become increasingly flexible as well, with a growing number of plugins and modules that can be used to customize its configuration. Between the performance, increasingly good documentation and convenience, it's no wonder nginx has been increasingly popular.

It's also no surprise that WordPress, the open-source blogging and CMS platform, has become hugely popular. I've heard people say that 10% of websites are now run using WordPress. Even if that's not precisely true, there's no doubt that a huge number of sites are powered by WordPress. I'm a mostly satisfied WordPress user, having converted my main site and my two ebook sites to it in the past year after years of using it to power my blog.

So, I thought it would be interesting to demonstrate how easy it is to set up WordPress with nginx, given the popularity of each of these systems alone as well as together. In my last article, I described how you can set up a plain-vanilla PHP system with nginx; WordPress is a bit more complex, but less than you might think. Starting with a bare-bones Linux installation, let's walk through the configuration needed to get WordPress up and running.

The Basics

In order to install WordPress and nginx together, you're going to need three basic software systems installed: WordPress, nginx and MySQL. The first two are pretty obvious, given this article's goal; the third is a byproduct of using WordPress, which works exclusively with MySQL.

So, on my Ubuntu Linux machine, I would run the following:

$ sudo apt-get install mysql-server mysql-client nginx-core 
 ↪php5-cli php5-fpm php5-mysql

This installs a very large number of packages, but it will give you the core of what you need to get your system up and running. Notice that you're not installing WordPress here, so that you can install it manually, using the source code. Indeed, installing WordPress via apt-get also means installing Apache; although it's certainly possible to undo this choice, the benefits of installing WordPress on your own outweigh those of doing it via a package manager.

You will, as part of this installation, need to choose a password for your MySQL root user. This is an important part of security on your system, so do try to use a strong password.

Once the package manager completes the installation of the above packages, you'll have a working nginx installation. Try it; you can point your browser at your server's port 80, and you should get the default nginx page indicating that it installed correctly.

Installing WordPress

Installing WordPress is quite straightforward; the complex part will be hooking together nginx with FPM, the PHP version of FastCGI. As you saw if you read my last article (in the June 2016 issue), FPM is the method through which nginx can run PHP in a separate process, without bloating the entire nginx process or reducing performance by very much.

The default location for HTML files in my nginx configuration is /usr/share/nginx/html. Within that directory, there's an index.html file, whose contents provide the default “welcome” page to nginx that you saw earlier.

The thing is, it's probably easiest just to install WordPress in a separate directory. So, I download WordPress and open it up under /usr/share/nginx/wordpress, which is a directory that'll be created anyway, when I open the tarfile. Here's what I did:

$ cd /usr/share/nginx
$ wget https://wordpress.org/latest.tar.gz
$ tar -zvxf latest.tar.gz

Now that WordPress has been installed, you'll want to run it. But you can't do that until you have created a MySQL database, since part of the WordPress installation requires that your database be working and ready.

So, let's create a new MySQL database! There are several ways to do it. I typically prefer to use the mysqladmin program, which takes similar options to the MySQL client, including -u to indicate which user you want to use and -p to indicate that you want to enter a password. Both will be necessary:

$ mysqladmin create wordpress -u root -p

Note that when I say you want to use the “root” user here, I'm not referring to the UNIX-level root user. Rather, I'm talking about the MySQL “root” user, which has ultimate privileges on the database. When you installed MySQL earlier, you needed to choose a root password. It's this password that you must enter when prompted, thanks to the -p option above.

You can check that your database was created by entering MySQL as root (once again, with -p and after entering the root password):

$ mysql -u root -p

Then, issue the command SHOW DATABASES at the mysql> prompt. On my completely new system, I got the following response:

mysql> SHOW DATABASES;

+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| wordpress          |
+--------------------+
4 rows in set (0.01 sec)

Notice that there are three databases in the system in addition to the “wordpress” database I created earlier. These are used internally by MySQL. Indeed, you'll now connect from the UNIX shell to the “mysql” database, which is used to run your database:

$ mysql mysql -u root -p

If you prefer, you also can switch to the “mysql” database from within the MySQL client:

mysql> \u mysql

Either way, you should now be connected to the “mysql” database as root. Next, you'll create a “wordpress” user and then allow that user to connect to your MySQL “wordpress” database.

I should note that when I work with consulting clients, it's not unusual for them to use the “root” user for all of their database connections. After all, it's more convenient, right? However, this is almost always a bad idea; you really want to have and use a separate user name for security reasons.

Once connected, you'll create a user, assign it a password and indicate that this new user has full privileges on the “wordpress” database:

mysql> CREATE USER wordpress@localhost;
mysql> SET PASSWORD FOR wordpress@localhost = PASSWORD('my-wp-pw');
mysql> GRANT ALL PRIVILEGES ON wordpress.* TO  wordpress@localhost;
mysql> FLUSH PRIVILEGES;

Note that SQL commands are case-insensitive, so you don't need to use CAPITAL LETTERS when entering them. However, I've done so for years, following the advice of Joe Celko's SQL For Smarties books, and I've found that it helps to distinguish between other parts of my programs.

Also note that in the above scenario, you've created a “wordpress” database and a “wordpress” user. Actually, your user isn't named “wordpress” so much as “wordpress@localhost”; when connecting to MySQL, the hostname is taken into account.

Finally, the FLUSH PRIVILEGES command is necessary to tell MySQL that it should take the new privileges into account even without doing a restart of the database server.

Once this is in place, you'll want to test it to make sure you can connect to the “wordpress” database as the “wordpress” user. On the UNIX shell, type:

$ mysql wordpress -u wordpress -p

When prompted for the password, enter the password you used (which is hopefully not “my-wp-pw” from above). You should see the “welcome” message and a mysql> prompt. If that doesn't work, then double-check the user name and password that you created, and make sure you flushed the privileges.

Now that you know your configuration works, you'll set up your WordPress configuration in a file called wp-config.php. This file is in the directory /usr/share/nginx/wordpress, thanks to opening the WordPress tarfile earlier.

A new WordPress installation doesn't have a configuration file; you must copy it from the wp-config-sample.php that comes with the system:

$ cp wp-config-sample.php wp-config.php

Once that's done, open the file, and look for three lines that define DB_NAME, DB_USER and DB_PASSWORD. Change all three values to reflect the database, user name and password you have created here; this is how WordPress will connect to your database.

Configuring nginx

Next, you'll need to configure the UNIX-level permissions. Every process runs as a certain user, and nginx is no exception. On Ubuntu machines, both nginx and Apache run as the “www-data” user and group. Using a specific user ID to run such programs allows you to ensure that the correct permissions are in place. However, it also means you need to be sure that the WordPress directory and files are owned by that user.

So, you can say:

$ cd /usr/share/nginx
$ sudo chown -Rv www-data:www-data wordpress

The -R option tells chown to work recursively. The -v option turns on “verbose” mode, meaning that you get additional feedback from the program as it works. I generally prefer to run programs with -v to give me more feedback.

Now you have to configure your nginx server. When you installed it, the main configuration file was placed in /etc/nginx/nginx.conf. However, modern nginx configurations also include one file for each server it is running in /etc/nginx/sites-enabled/, with the file /etc/nginx/sites-enabled/default describing the default site.

For the purposes of simplicity, I'm going to assume here that you have a single site, which means you'll be able to modify just the individual “default” file, rather than the overall config file.

As is usual in nginx, the configuration is broken into individual blocks. server blocks describe how the HTTP requests coming in should be handled; in this case, you want anything that arrives on port 80 for any hostname to be passed to PHP. The following server block does the trick:


server {
        listen 80 default_server;

        root /usr/share/nginx/wordpress;
        index index.php index.html index.htm;

        server_name localhost;

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

        error_page 404 /404.html;

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

        location ~ \.php$ {
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass unix:/var/run/php5-fpm.sock;
                fastcgi_index index.php;
                include fastcgi_params;
        }
}

Let's go through the above, so you can understand what's happening.

First, you indicate that this server will listen on port 80. Unless you have a good reason to choose a different port, this is probably a good idea. Note that if and when you want to use SSL, that goes on port 443 and also requires a separate server block. For purposes of simplicity, let's ignore that here.

You also use default_server to indicate that if someone provides a hostname that does go to your IP address, but that is unhandled by any other hostname, then this server should be used. If your system handles only one HTTP server, this directive should be set in your configuration. If your system has multiple virtual HTTP servers, you'll need to decide which should be the default.

The root directive describes the directory containing the files you'll want to serve. After opening the tarfile into /usr/share/nginx/wordpress, you tell nginx that the directory contains the PHP programs you want to execute. Actually, it's just one PHP program in index.php that does all the heavy lifting.

The index command indicates what files should be read, and in what order, if you don't provide a filename. Note: indicate that index.php should be tried first to give WordPress a chance to run things before static alternatives are attempted.

The server_name directive tells nginx which name(s) should be recognized by this server. If you're using default_server and have only one virtual host, this doesn't matter all that much. However, if you have multiple servers, giving a name is a good idea.

You then indicate, using a location block, what you want to do when you receive a request to the “/” URL—meaning, a directory name. This directive tells nginx that it first should try the URL that you received, but if that doesn't work, then you should invoke index.php, passing it the URL and any arguments that you received with it. In this way, index.php becomes the gatekeeper for any and all requests you recieve.

You then indicate what to do in case of error, separating 404 (“file not found”) from more serious server-side errors (50x errors). nginx comes with static files for these errors; you can modify those files if you want something more informative or whimsical.

Finally, you connect nginx to FPM, the PHP back-end system that I discussed in my last column. FPM runs PHP in a separate process, but keeps it going continuously, so you don't have to start up a new process each time. If you find that php5-fpm isn't running, you might need to start it with:

$ sudo php5-fpm restart

Once the above is in place, you can restart nginx:

$ sudo nginx restart

Point your web browser to your WordPress system's IP address or hostname, and you should see a request to choose a language as part of the WordPress installation. If you do, then you've made it; your server is up and running. Move on to the next page to choose a site title, admin user name, password and email address, and you're all set!

Conclusion

As you can see, it's surprisingly easy to set up WordPress with nginx. Assuming that PHP is installed, and that PHP's FPM system is installed and running, you actually can get an nginx-powered WordPress blog up and running in just a few minutes. And although you could install WordPress via apt-get or a similar package manager, doing so means that your updates are at the mercy of the Linux distribution you're using, which inevitably will lag behind the WordPress distribution itself, not to mention plugins, which are perhaps one of the most important parts of the WordPress ecosystem.

Reuven M. Lerner offers training in Python, Git and PostgreSQL to companies around the world. He blogs at blog.lerner.co.il, tweets at @reuvenmlerner and curates DailyTechVideo.com. Reuven lives in Modi'in, Israel, with his wife and three children.