close

Endpoint Security Analyst

The blogging software used to power this site, , has just had its 10th anniversary. In the last decade it has become incredibly popular, and currently accounts for about 18% of all websites. Due to my job at (mt) Media Temple, I’ve gotten to admin at various times over the years, and thus have a pretty good idea of how to optimize everything for good efficiency. In this WordPress configuration tutorial, I’m going to walk you through the production quality setup that I have for this blog, which makes use of , , and .

UPDATE: I have recently switched over from using the plugin for caching to the plugin. My reasons are varied. While I don’t think it provides quite the same level of bare metal performance for page caching that WP Super Cache does, I still think it’s the better choice right now. It has significant additional options, is trivial to configure, and makes great use of the APC cache features if available. If there’s sufficient interest I’ll update this post or make another to talk about it.

I’ll be making several assumptions throughout this article. Specifically:

  • You are using Ubuntu Lucid (10.04) or a newer supported release.
  • You are using a “naked” install of Ubuntu (no control panels or anything of that sort).
  • You have the ability to run all the commands I’ll list as root using sudo.
  • You’re just going to use the server you’re using for a WordPress blog, or you have a good enough understanding of user permissions not to create any security risks.
  • You’ll be serving up a blog at example.com, and you’ll be running various services with user and group permissions of a user called example, which needs to exist on your system.

Step 1: Install Needed Software

There’s a variety of things you’ll want to install on your system. For starters, let’s install Nginx and MariaDB. Nginx is very easy as I have a with all the goodies you’ll need ready to go. In case you don’t already have it, install python-software-properties with:

1 sudo apt-get -y install python-software-properties

Now, you can add my Nginx PPA and install it:

1 2 3 sudo add-apt-repository -y ppa:chris-lea/nginx-devel sudo apt-get update sudo apt-get -y install nginx-full

MariaDB is also very easy to install. You’ll need to import their signing key first with the command:

1 sudo apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 0xcbcb082a1bb943db

When you install the server software, you’re going to have to enter a password for the root MariaDB user (which is NOT to be confused with the system root user, though they are used in similar ways here). Looking ahead, let’s pre-select a strong password with the apg password generator.

1 2 sudo apt-get -y install apg apg -x 10 -m 10

This will generate a half dozen reasonably strong 10 character passwords to pick from. Choose one and keep it handy. Now, to install MariaDB, use your favorite editor (I’m assuming it’s , obviously) and create the file:

/etc/apt/sources.list.d/mariadb.list

Put the following* into the file:

1 2 deb http://mirror.jmu.edu/pub/mariadb/repo/5.5/ubuntu lucid main deb-src http://mirror.jmu.edu/pub/mariadb/repo/5.5/ubuntu lucid main
*If you are using a distribution different than Ubuntu Lucid (10.04), replace lucid in the above two lines with the distro name you have installed.

Now, install MariaDB with the commands:

1 2 sudo apt-get update sudo apt-get -y install mariadb-server

The installer will prompt you for a password for the root user, so enter the password you got from the apg output earlier. For a pro tip, make a file at ~/.my.cnf with the following contents:

1 2 3  [Client] user = root password = <YOURPASSFROMAPG>

and change the permissions so only you can read it:

chmod 600 ~/.my.cnf

Now you won’t have to enter the username and password when you type mysql to use the MariaDB client.

The last thing to install is the needed PHP software. If you are running Ubuntu Lucid, use the following command to add a PHP5 PPA maintained by Ondřej Surý:

1 sudo add-apt-repository -y ppa:ondrej/php5

If you are using a release newer than Lucid you won’t need to do that step. Next, install all the PHP things we’ll need with:

1 2 3 4 sudo apt-get update sudo apt-get -y install php-apc php-geshi php-pear php5 php5-cli \ php5-common php5-curl php5-fpm php5-gd php5-imagick php5-mysqlnd \ php5-pspell

Step 2: Configuring the Software

We’ll need to configure the things we’ve just installed for optimal performance. Let’s start with the database. Configuring a database correctly is a career level amount of knowledge these days, so there’s nothing I can really tell you in a post like this that will cover every case. But there are a few things we can do to the default setup that will generally always help in this particular WordPress case.

First, open up the file

/etc/mysql/my.cnf

in your text editor. Look for a line that reads:

1 tmpdir = /tmp

and add the following three lines underneath it:

2 3 4 skip-external-locking skip-name-resolve character-set-server = utf8

These make sure that the default character set is UTF-8, that the server doesn’t try and do DNS based hostname lookups (which it shouldn’t need to do here) and that external locking isn’t enabled. Note that with locking disabled, you’ll need to make sure to shutdown the server if you ever need to run myisamchk to repair something.

Next, you will probably want to increase the default value for the key_buffer. What you use depends on how much free memory you have on your system, but increasing it from the default 16M value is a good idea if you can. This site currently uses the setting:

key_buffer = 128M

Finally, you may want to change the default value for the database query cache. Look for the line that reads:

query_cache_size = 16M

The default of 16M is probably fine for most blogs. If you get a lot of traffic you may wish to raise this value, but never raise it to more than 512M as it starts doing more harm than good at that point. Even for a blog with a lot of traffic, 128M or 256M is generally plenty. Restart the server to pick up these changes with

1 sudo /etc/init.d/mysql restart

Of course, you’ll want to create a database for your blog. I’ll assume it’s called example_com_wordpress. You’ll want to generate another password as we did before that’s specific to this database. Do NOT use the same password as you have for the root user. Once you have this, enter the MariaDB command prompt by simply typing

mysql

at the command line. Then, use the following commands to create your database and get it ready for use (note how we’re at the MariaDB prompt):

MariaDB [(none)]> CREATE DATABASE example_com_wordpress; Query OK, 1 row affected (0.00 sec)   MariaDB [(none)]> GRANT ALL PRIVILEGES ON example_com_wordpress.* TO `example`@`localhost` IDENTIFIED BY 'NEWPASSFROMAPG'; Query OK, 0 rows affected (0.04 sec)   MariaDB [(none)]> FLUSH PRIVILEGES; Query OK, 0 rows affected (0.04 sec)

Then just type exit to get out of MariaDB, and your database is ready to go.

Next, let’s configure php5-fpm. Open up the file at

/etc/php5/fpm/pool.d/www.conf

and look for the user and group directives. Change these so that PHP runs as your example user like this:

user = example group = example

Then, look for the listen directive and change it to read:

listen = /var/run/php5-fpm.sock

so that Nginx can communicate to PHP via a and thus bypass the TCP stack which isn’t needed. Finally, there are some directives for the process manager that could use some changes. The ideal configuration depends on your traffic and hardware, but this site is currently using:

pm = dynamic pm.max_children = 40 pm.start_servers = 4 pm.min_spare_servers = 4 pm.max_spare_servers = 5 pm.max_requests = 500

and that should be a good starting point for most blogs.

Lastly for PHP, you should make one quick edit to the file:

/etc/php5/fpm/php.ini

where you’ll want to make sure the following is set:

cgi.fix_pathinfo = 1

You should now be able to fire up your PHP processes with the command:

sudo /etc/init.d/php5-fpm start

The last, and most involved thing you’ll need to configure is Nginx. To begin, we’ll also want to run it as our example user. Open up the file:

/etc/nginx/nginx.conf

and set the user directive to

user example;

Generally speaking, you want to have as many worker processes as you do physical CPU cores. If you’re unsure how many cores you have, the command

grep -c 'processor' /proc/cpuinfo

will tell you. So, if you have a four core machine, set

worker_processes 4;

Next, we’ll do a virtual host configuration for our example.com site. If you’re going to run a production WordPress site, it’s generally recommended to use some sort of caching plugin if you expect any real traffic. Our configuration is going to be tailored to take advantage of the plugin, which works very well and is written by one of the key WordPress engineers.

Create a new file called example.com here:

/etc/nginx/sites-available/example.com

To begin with, let’s define an Nginx upstream for PHP listening to the Unix socket we specified earlier. Put the following into the beginning of our example.com config file:

1 2 3 4 # Upstream to abstract backend connection(s) for php upstream php { server unix:/var/run/php5-fpm.sock; }

After that comes some fairly straightforward Nginx directives to tell it what port to listen to, what the server name is, where to log, and so on:

5 6 7 8 9 10 11 12 13 14 15 server { listen 80; server_name example.com;   access_log /var/log/nginx/example.com-access.log; error_log /var/log/nginx/example.com-error.log;   add_header X-Frame-Options DENY;   root /var/www/example.com; index index.php index.html;

The only thing of possible note here is the add_header directive at line 12, which should keep your site from being embedded in a <frame> or <iframe> anywhere. If for some reason you want that to be possible, remove that line. After this, we have some rewrite directives to make the generated sitemap work correctly:

17 18 rewrite ^/sitemap_index\.xml$ /index.php?sitemap=1 last; rewrite ^/([^/]+?)-sitemap([0-9]+)?\.xml$ /index.php?sitemap=$1&sitemap_n=$2 last;

and then some specific location directives for the favicon, robots.txt file, and to keep people out of some common directories and files that shouldn’t be public:

20 21 22 23 24 25 26 27 28 29 30 31 32 33 location = /favicon.ico { log_not_found off; access_log off; }   location = /robots.txt { allow all; log_not_found off; access_log off; }   location ~ /\.svn|/\.ht|/\.git { deny all; }

Now on to the good part, where we set Nginx up to use WP Super Cache. It’s easier to explain how this works if you can already see the configuration, so here it is, and we’ll go over it next:

$supercache_file /wp-content/cache/supercache/$http_host$1index-http.html; }   if (-f $document_root$supercache_file) { rewrite ^ $supercache_file break; }   if (!-e $request_filename) { rewrite ^ /index.php last; }   }

Lines 35 to 46 are fairly obvious. They tell Nginx where to look for an index file with the try_files directive and set up gzip compression. The gzip_static directive is somewhat interesting. It tells Nginx that if it sees a file that is supposed to be gzipped, it should look for a file in the same directory with the same name that ends in “.gz”, and if it’s there, to serve that directly. Doing this keeps the server from having to compress the file every time it is served.

The really interesting part, however, starts on line 48. This is where we get Nginx to play nicely with WP Super Cache. First, if the request corresponds to an actual file on the filesystem, we tell Nginx to just serve it and move on. Next we set some variables we’ll need. If the request type is a POST, or if there’s any sort of query string, we disable caching by setting $supercache_uri to be blank. If we’ve made it this far and that variable isn’t blank, then we look for a deterministically named file at

/wp-content/cache/supercache/<the domain name>/<the request uri>/index-http.html

For example, if you had a standard “About” page located at Nginx would look for the file

/wp-content/cache/supercache/example.com/about/index-http.html This exactly corresponds to how WP Super Cache writes out its “supercached” files to the filesystem. The beauty of this setup is that if a cached file exists, Nginx will serve it without ever having to talk to PHP whatsoever. This is great, because Nginx is amazingly good at serving up static content such as these cached files, and it keeps PHP from having to do work unless it actually needs to. If there’s any legitimate “secret sauce” in this configuration recipe, this is it. The last stanzas we need tell Nginx how to talk to PHP using FastCGI, and then just sets some expire headers for our static content types: 77 78 79 80 81 82 83 84 85 86 87 88 89 location ~ \.php$ { #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini include fastcgi_params; fastcgi_index index.php; fastcgi_intercept_errors on; fastcgi_pass php; }   location ~*\.(?:js|css|png|jpe?g|gif|ico|html|txt)$ { expires 7d; log_not_found off; } } We are now ready to turn Nginx on. We’ll need to remove the default configuration that Nginx ships with, symlink our configuration into the sites-enabled directory, and then fire it up. cd /etc/nginx/sites-enabled sudo rm -fv default sudo ln -s ../sites-available/example.com . sudo /etc/init.d/nginx start If that all works, we’re on to the third and final stretch. Step 3: Installing and Setting Up WordPress This is a tutorial about server configuration, so I’m going to assume that you can handle the basics of itself. You’ll want to put everything in the directory /var/www/example.com, which you’ll need to create and set to be owned by our example user: cd /var/www sudo mkdir example.com sudo chown -R example:example example.com Be sure when you put the WordPress files in there, you’re installing them as the example user. Edit the wp-config.php file to use our example_com_wordpress databasewith the credentials we set up earlier. Also, be sure to set up random values for the unique keys and salts. Automattic has to help you do this. Once you have WordPress up and running, there’s just a few more steps before you’re ready to start your blogging empire. First, set up pretty permalinks for your blog. In the WordPress admin, just go to Settings->Permalinks, choose the “custom” option, and put the following in for the link structure: /%year%/%monthnum%/%day%/%postname%/ This will give you the standard date-centric link structure that many popular blogs have. The final step is to set up WP Super Cache. Go to Plugins->Add New and search for “WP Super Cache”. Since PHP is running as the same user who owns the WordPress files and directory, it can install the plugin for you (it can also easily upgrade WordPress for you, as an added bonus). You’ll then need to configure it. Navigate to the settings page for the plugin, and click on the Advanced tab. Check the box to “Cache hits tothis website for quick access”, then click the radio button for “Use mod_rewrite to serve cache files”. You should ignore the warnings at the top of the page that complain about mod_rewrite not being installed. The plugin assumes you’re using Apache to serve the site, but you’re not, so this warning is superfluous. Finally, scroll down and click the “Update Status” button to save your changes. After this, log out of the admin, navigate to your home page, and reload it a few times. If you view the page source, down near the bottom, you should see some comments that indicate the page is a cached page. If so, congratulations, you’re all done! In Conclusion This is a setup I’ve refined over several years, starting from when keeping Techcrunch up and running was part of my job description. It’s worked well for me for a lot of WordPress sites (including this one), so I hope you find it useful. If there are questions or issues, please just let me know in the comments. Share: Like this: LikeLoading...

endpoint security controls     endpoint security console

TAGS

CATEGORIES