Published on June 5th, 2014 | by Sunit Nandi


Create a turbocharged WordPress installation with Nginx, PHP-FPM and Opcode caching (LEMP stack)

Are you tired of the sluggish performance of your WordPress blog or site on a shared hosting? Are you running the Apache web server and feeling disheartened with its resource usage under heavy strain? Facing timeouts or bad gateway errors too often? Cannot afford those specialized WordPress hosts?

Don’t worry as I’ll be explaining how to setup a lightning fast WordPress installation on Nginx web server (which some people lovingly call the LEMP stack), which you can set up on a VPS or a dedicated server. You can also use this guide in a similar manner to migrate existing installations to Nginx.

According to Nginx’s homepage:

Nginx (pronounced engine-x) is a free, open-source, high-performance HTTP server and reverse proxy, as well as an IMAP/POP3 proxy server. Igor Sysoev started development of Nginx in 2002, with the first public release in 2004. Nginx now hosts nearly 12.18% (22.2M) of active sites across all domains. Nginx is known for its high performance, stability, rich feature set, simple configuration, and low resource consumption.

Nginx is one of a handful of servers written to address the C10K problem. Unlike traditional servers, Nginx doesn’t rely on threads to handle requests. Instead it uses a much more scalable event-driven (asynchronous) architecture. This architecture uses small, but more importantly, predictable amounts of memory under load.
Even if you don’t expect to handle thousands of simultaneous requests, you can still benefit from Nginx’s high-performance and small memory footprint. Nginx scales in all directions: from the smallest VPS all the way up to clusters of servers.”

Nginx, in conjunction with PHP-FPM (FastCGI Process Manager) and an Opcode caching module like APC or XCache packs enough power to handle 100 times the number of website visitors Apache can and atleast 10 times that of LiteSpeed when running WordPress on the same hardware. However, the downside is that installing WordPress with Nginx is not as straightforward, and support/documentation is comparatively limited compared to Apache. But if you have the guts, its well worth trying it out.

At one point of time, I myself was going through several guides on the internet to achieve a proper WordPress setup. Most guides just dumped everything in a single file making everything inconvenient. Most of them did not work well with certain plugins. In some cases the permalinks were borked. The only guide that highlights the best practices was that of itself, but it too had some minor mistakes that weren’t so obvious and took me a good amount of time to figure out. So I put together this new and updated guide for all of you.

I have tested this on Ubuntu 14.04 LTS. Other distros should work fine, with the exception of a few commands and path differences.

Installing the requirements

First of all, lets do a software update. Type in the terminal:

sudo apt-get update && sudo apt-get upgrade

Then grab Nginx:

sudo apt-get install nginx-full

Try browsing your http://<your-server’s-IP-address-here>/

If you see a page that says “Welcome to Nginx!”, then Nginx is up and running.

Then lets install MySQL:

sudo apt-get install mysql-server-5.6 mysql-client-5.6

Alternatively, we can use MariaDB instead of MySQL;

sudo apt-get install mariadb-server mariadb-client

We need to set up the database directory structure:

sudo mysql_install_db

After that, let’s set up the database with the setup wizard:

sudo mysql_secure_installation

You will need to enter the MySQL root password that you selected during installation.

Next, it will ask if you want to change that password. If you are happy with your MySQL root password, type “N” for no and hit “ENTER”. Afterwards, you will be prompted to remove some test users and databases. You should just hit “ENTER” through these prompts to remove the unsafe default settings.

Once the script has been run, MySQL is ready to go.

Now lets install the PHP processor, that is, PHP-FPM.

Execute the following in the terminal:

sudo apt-get install php5-fpm php5-mysql php5-gd php5-json

Open the main php5-fpm configuration file with root privileges:

sudo nano /etc/php5/fpm/php.ini

What we are looking for in this file is the parameter that sets cgi.fix_pathinfo. This will be commented out with a semi-colon (;) and set to “1” by default.

This is an extremely insecure setting because it tells PHP to attempt to execute the closest file it can find if a PHP file does not match exactly. This basically would allow users to craft PHP requests in a way that would allow them to execute scripts that they shouldn’t be allowed to execute.

We will change both of these conditions by uncommenting the line and setting it to “0” like this:


Save and close the file when you are finished.

Now, we just need to restart our PHP processor by typing:

sudo service php5-fpm restart

This will implement the change that we made.

Now all the requirements are complete and we move on to installing WordPress.

Installing WordPress

Setting up the Nginx server block and configuration files

Before we install WordPress, we need to set up the server block for the site.

An example of such a block’s path is /etc/nginx/sites-available/wordpress

First lets switch to a root shell:

sudo -i

Type your password and hit ENTER.

Open it up in a text editor like nano:

nano /etc/nginx/sites-available/wordpress

“wordpress” can be replaced with any other name.

Paste or type the following in there:

server {
        listen 80;
        server_name; # Replace with your site's hostname
        root /srv/www/wordpress;
        index index.php;

        include conf.d/wp/restrictions.conf;

        # Additional rules go here.

        include conf.d/wp/wordpress.conf;

Replace “” with the real hostname on which your WordPress site is going to be hosted. And the “/srv/www/wordpress” must be changed the WordPress files are placed elsewhere.

Save with CTRL+O and exit with CTRL+X.

Make a new directory under /etc/conf.d/ named “wp” to store the WordPress configurations.

cd /etc/conf.d/
mkdir wp

Now lets make two files for the store the WordPress configurations.

The first file is the WordPress blog config:

nano /etc/nginx/conf.d/wp/wordpress.conf

Paste the following in there:

# WordPress single blog rules.
# Designed to be included in any server {} block.

# This order might seem weird - this is attempted to match last if rules below fail.
location / {
        try_files $uri $uri/ /index.php?$args;

# Add trailing slash to */wp-admin requests.
rewrite /wp-admin$ $scheme://$host$uri/ permanent;

# Directives to send expires headers and turn off 404 error logging.
location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
       access_log off; log_not_found off; expires max;

# Uncomment the line below for the W3 total caching plugin (if used).
#include conf.d/wp/wordpress-w3-total-cache.conf;

# Pass all .php files onto a php-fpm/php-fcgi server.
location ~ \.php$ {
        # Zero-day exploit defense.
        # Won't work properly (404 error) if the file is not stored on this server, which is entirely possible with php-fpm/php-fcgi.
        # Comment the 'try_files' line out if you set up php-fpm/php-fcgi on another machine.  And then cross your fingers that you won't get hacked.
        try_files $uri =404;

        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini

        include fastcgi_params;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
#       fastcgi_intercept_errors on;
        fastcgi_pass unix:/var/run/php5-fpm.sock;

Save and exit.

The second file is the restrictions file which prevents access to sensitive areas:

nano /etc/nginx/conf.d/wp/restrictions.conf

Paste the following:

# Global restrictions configuration file.
# Designed to be included in any server {} block.
location = /favicon.ico {
        log_not_found off;
        access_log off;

location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;

# Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~ /\. {
        deny all;

# Deny access to any files with a .php extension in the uploads directory
# Works in sub-directory installs and also in multisite network
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~* /(?:uploads|files)/.*\.php$ {
        deny all;

Save and exit.

In a similar manner, any new WordPress site can be added by adding a new server block for the site with a different “root” (document root) and “server_name” (hostname) keeping everything else same. The purpose of this method is to reuse the same wordpress.conf and restrictions.conf with multiple WordPress installations, thus saving a lot of hassle.

Now lets enable the server block by symlinking:

ln -s /etc/nginx/sites-available/wordpress /etc/nginx/sites-enabled/wordpress

And then restart the server:

service nginx restart

Setting up the MySQL database

Now we move on to creating the database users and tables.

Go ahead and log into the MySQL shell:

mysql -u root -p

Login using your MySQL root password, and then we need to create a WordPress database, a user in that database, and give that user a new password. Keep in mind that all MySQL commands must end with semi-colon.

First, let’s make the database (I’m calling mine wordpress for simplicity’s sake; feel free to give it whatever name you choose):

Query OK, 1 row affected (0.00 sec)

Then we need to create the new user. You can replace the database, name, and password, with whatever you prefer:

CREATE USER wordpressuser@localhost;
Query OK, 0 rows affected (0.00 sec)

Set the password for your new user:

SET PASSWORD FOR wordpressuser@localhost= PASSWORD("password");
Query OK, 0 rows affected (0.00 sec)

Finish up by granting all privileges to the new user. Without this command, the WordPress installer will not be able to start up:

GRANT ALL PRIVILEGES ON wordpress.* TO wordpressuser@localhost IDENTIFIED BY 'password';
Query OK, 0 rows affected (0.00 sec)

Then refresh MySQL:

Query OK, 0 rows affected (0.00 sec)

Exit out of the MySQL shell:


Installing the WordPress files

Now lets proceed to installing WordPress.

First navigate to the required directory. You can find the location from the “root” field of the Nginx server block file.

cd /srv
mkdir www
cd www

Now lets download the latest version of WordPress:


Lets extract it from the archive:

tar -xzvf latest.tar.gz

The WordPress files are successfully placed in /srv/www/wordpress

We can modify the permissions of /var/www/wordpress to allow future automatic updating of WordPress plugins and file editing with SFTP. If these steps aren’t taken, you may get a “To perform the requested action, connection information is required” error message when attempting either task.

Give ownership of the directory to the Nginx user, replacing the “username” with the name of your server user.

sudo chown -R www-data:www-data wordpress/
sudo usermod -a -G www-data username

You are done! All you need to do now is open up a browser and go to (replacing “” with your real website’s hostname) and complete the installation wizard. Your new WordPress site is now ready and even your fancy/custom permalinks will work.

Turbocharging WordPress with caching

Disk caching

From within the WordPress admin panel, install the W3 Total Cache plugin. Enable the plugin.

After that open up a root shell on your server and add a new config file:

nano /etc/nginx/conf.d/wp/wordpress-w3-total-cache.conf

Paste the following text into that file:

set $cache_uri $request_uri;

# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
        set $cache_uri 'null cache';
if ($query_string != "") {
        set $cache_uri 'null cache';

# Don't cache uris containing the following segments
if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)") {
        set $cache_uri 'null cache';

# Don't use the cache for logged in users or recent commenters
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") {
        set $cache_uri 'null cache';

# Mobile browsers section to server them non-cached version. COMMENTED by default as most modern wordpress themes including twenty-eleven are responsive. Uncomment config lines in this section if you want to use a plugin like WP-Touch
# if ($http_x_wap_profile) {
#        set $cache_uri 'null cache';

#if ($http_profile) {
#        set $cache_uri 'null cache';

#if ($http_user_agent ~* (2.0\ MMP|240x320|400X240|AvantGo|BlackBerry|Blazer|Cellphone|Danger|DoCoMo|Elaine/3.0|EudoraWeb|Googlebot-Mobile|hiptop|IEMobile|KYOCERA/WX310K|LG/U990|MIDP-2.|MMEF20|MOT-V|NetFront|Newt|Nintendo\ Wii|Nitro|Nokia|Opera\ Mini|Palm|PlayStation\ Portable|portalmmm|Proxinet|ProxiNet|SHARP-TQ-GX10|SHG-i900|Small|SonyEricsson|Symbian\ OS|SymbianOS|TS21i-10|UP.Browser|UP.Link|webOS|Windows\ CE|WinWAP|YahooSeeker/M1A1-R2D2|iPhone|iPod|Android|BlackBerry9530|LG-TU915\ Obigo|LGE\ VX|webOS|Nokia5800)) {
 #       set $cache_uri 'null cache';

#if ($http_user_agent ~* (w3c\ |w3c-|acs-|alav|alca|amoi|audi|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd-|dang|doco|eric|hipt|htc_|inno|ipaq|ipod|jigs|kddi|keji|leno|lg-c|lg-d|lg-g|lge-|lg/u|maui|maxo|midp|mits|mmef|mobi|mot-|moto|mwbp|nec-|newt|noki|palm|pana|pant|phil|play|port|prox|qwap|sage|sams|sany|sch-|sec-|send|seri|sgh-|shar|sie-|siem|smal|smar|sony|sph-|symb|t-mo|teli|tim-|tosh|tsm-|upg1|upsi|vk-v|voda|wap-|wapa|wapi|wapp|wapr|webc|winw|winw|xda\ |xda-)) {
  #      set $cache_uri 'null cache';

# Use cached or actual file if they exists, otherwise pass request to WordPress
location / {
        try_files /wp-content/w3tc/pgcache/$cache_uri/_index.html $uri $uri/ /index.php?$args ;

Then uncomment the line:

include conf.d/wp/wordpress-w3-total-cache.conf;

in the /etc/nginx/conf.d/wp/wordpress.conf file.

Then enable Disk Enhanced page caching in the W3 Total Cache configuration page. You’re done! Nginx can handle gzip & browser cache automatically so we better leave that part to Nginx. W3 Total Cache Minify rules will work with above configuration without any issues.

Opcode caching

There are two PHP modules to achieve opcode caching, namely APC and XCache. You can install and use only one of them at a time. Don’t ever make the mistake of installing two of them together as there will be a nasty conflict.

You can install PHP-APC by:

sudo apt-get install php5-apcu && sudo php5enmod apcu

And XCache by:

sudo apt-get install php5-xcache && sudo php5enmod xcache

After you install either of the above, restart PHP-FPM:

sudo service php5-fpm restart

Once done, you’ll have the option of choosing APC or XCache as the page caching, object caching and database caching method in W3 Total Cache. Just choose it and save your settings.

W3 Total Cache WordPress


After setting up your WordPress site on Nginx, you will notice a huge performance difference. The response times should improve considerably now and resource usage will be lower.

I hope this guide helped you out. In case you are looking for a server try it out, you can use a cheap VPS from DigitalOcean.

Any queries or suggestions? Feel free to leave a comment below.

If you face trouble while following this guide, feel free to open a discussion in our Facebook group or forum and I’ll try my best to help you out.

Thanks a lot for reading this. Have a great day!

Tags: , , , , , , , , , ,

About the Author

Avatar photo

I'm the leader of Techno FAQ. Also an engineering college student with immense interest in science and technology. Other interests include literature, coin collecting, gardening and photography. Always wish to live life like there's no tomorrow.

9 Responses to Create a turbocharged WordPress installation with Nginx, PHP-FPM and Opcode caching (LEMP stack)

        Leave a Reply

        Your email address will not be published. Required fields are marked *

        Back to Top ↑