in Code, Tech Trends, Tutorials

Super-fast Secure WordPress Install on DigitalOcean with NGINX, PHP7, and Ubuntu 16.04 LTS

Updated: 2017-07-12

I’ve recently begun migrating websites from my old web host to DigitalOcean. Today, I’m documenting the steps I use to stand up a new server instance. Some of these technologies are still close to the bleeding edge, so if you’re really worried about stability, you may want to stick with some of the more battle tested (i.e. older) versions of these packages. However, I’ve had pretty solid results so far with these:

  1. Ubuntu 16.04 LTS
    “Xenial Xerus” was launched 21 April 2016, and is the most recent LTS version of Ubuntu.
  2. NGINX
    While Apache is still the predominant server of choice for WordPress installs, NGINX offers speed improvements, particularly related to not having to process .htaccess files in every directory–even uses NGINX.
  3. PHP7-FPM
    PHP released the latest major version of its server back in December 2015, and may more than double the speed of page loads, particularly when paired with NGINX.
  4. LetsEncrypt
    As I’ve blogged about before, setting up HTTPS by default for all pages is rapidly becoming the new norm, and I’ve got some updates to my earlier post on how to do this.

So let’s get started!

Step 1: Install and Configure your Droplet

While DigitalOcean already has a pre-configured droplet that comes with a LAMP stack and WordPress, I’m not going with that one for the following reasons:

  1. It won’t install on the smallest size droplet, meaning you have to spend at least $10/month on your site. While this may end up being necessary, I’d prefer to have the option to keep it to the smallest, cheapest size and scale up when I’m ready.
  2. Since we’re using NGINX, we don’t need Apache, and don’t have to worry about uninstalling it.
  3. We can install PHP7 instead of the version of PHP5 that comes on that droplet.

So, I would follow the instructions in this postexcept, choose Ubuntu 16.04 instead of 14.04. For a small droplet like this, you could go with either the 32-bit or 64-bit OS. DigitalOcean recommends 32-bit for smaller installs since some processes on 64-bit architecture require more RAM. I chose 64-bit for scalability.

IMPORTANT: when you name your droplet, be sure to name it using your domain name so that the hostname and reverse DNS will be set up properly

Next follow the Initial Server Setup with Ubuntu 16.04 tutorial.

Step 2: Point your Domain Name at your Server

At this point it’s also a good idea to set up your domain name to point to your server. Once you’ve set it up, it can take up to a couple of days for the change to take effect, but in practice it usually only takes a few minutes to a couple of hours. Log into the site where you registered your domain name and change the nameservers to point to , , and . After you do that, head over to your DigitalOcean account and go to Networking > Domains. In the form to “Add a Domain” enter your domain name and then select the droplet you created in Step 1. I typically set up my domains to route email through Mailgun’s servers. In case you do the same, once you get done editing your domain, it should look something like this (NOTE: recently I also had to explicitly add an A record for the www subdomain in order for letsencrypt to correctly install an SSL cert):

The correct DNS settings for the subdomain, using IP provided by Mailgun

The correct DNS settings for the subdomain, using IP provided by Mailgun

Step 3: Install NGINX

In order to use a WordPress plugin for purging the NGINX cache that I talk about below, you have to install a custom version of NGINX. From the command line:

This will download and install NGINX and set up the firewall to allow both HTTP (port 80) and HTTPS (port 443) traffic. After you do this, you’ll need to update  /etc/nginx/nginx.conf to comment out some lines that conflict with some of the SSL settings we’ll be creating later. Open the file:

Comment out ALL of the lines in the “SSL” section of the file by adding a # before them, then save and close the file.

Step 4: Install and Configure MariaDB

MariaDB is a drop-in replacement for MySQL. You can read about why people think it’s better, but I’m mostly convinced by the performance arguments. The MariaDB website has a convenient tool for configuring the correct repositories in your Ubuntu distro. Using the tool, I came up with the following steps for installing the DB:

When the following screen comes up, make sure you provide a good secure password that is different from the password you used for your user account.

Setting up root password for MariaDB

Setting up root password for MariaDB

Next, lock down your MariaDB instance by running:

Since you’ve already set up a secure password for your root user, you can safely answer “no” to the question asking you to create a new root password. Answer “Yes” to all of the other questions. Now we can set up a separate MariaDB account and database for our WordPress instance. At the command prompt type the following:

Type in your password when prompted. This will open up a MariaDB shell session. Everything you type here is treated as a SQL query, so make sure you end every line with a semicolon! This is very easy to forget. Here are the commands you need to type in to create a new database, user, and assign privileges to that user:

Note that although it’s customary to use ALL CAPS to write SQL statements like this, it is not strictly necessary. Also, where I’ve used “mywpdb” and “mywpdbuser” feel free to use your own database and user names.

Finally, it is recommended that you create a MariaDB sources.list file. This file will make sure that when your server periodically looks for updates, it will also check the MariaDB repository to keep it up to date. Create the file and open it for editing by typing the following at the command prompt:

Copy and paste the following code into that file:

Save and close the file by typing Ctrl + x.

Step 5: Install and Configure PHP7-FPM

One of the cool things about Ubuntu 16.04 is that it’s default PHP packages now default to version 7! Installing PHP is as simple as typing the following:

Note that this also installs the MySQL, XML, and GD packages so that WordPress can interact with the database, support XMLRPC (important if you use Jetpack), and also automatically crop and resize images.

Optionally, we can adjust our php.ini settings. Open  /etc/php/7.0/fpm/php.ini using nano as follows:

You can search for the line you want to edit by hitting  CTRL + W and then typing the text of the setting you’re looking for. I usually want to adjust the  post_max_size and  upload_max_filesize settings to something larger than their defaults of 8MB and 2MB, respectively (I set mine to 256MB). I also set the memory_limit property to 256M as well. I frequently find that in my WordPress sites I want to upload larger files. Once you’re done editing, hit  CTRL + X to exit nano, and follow the prompts to save your changes. To get PHP to load the changes you need to restart it by typing:

Step 6: Tell NGINX to use PHP7-FPM

Open up the configuration file for your default site for NGINX:

Edit the file so that it looks like this but change to reflect the URL for your website:

Save and exit this file, and then restart NGINX by typing the following:

In order to test out whether or not your changes worked, you can create a basic PHP file at the root of your web server by typing:

Then you can go to a web browser and type in  http://your-IP-address and you should get the auto-generated PHP Info page which looks something like this:

Default PHP Info page

Default PHP Info page

Woohoo!!! Now we’re getting somewhere. We’re going to be making a bunch of changes to our NGINX config in later steps for security and optimization, but this is the absolute minimum you need to do to get PHP7-FPM and NGINX playing well together.

Step 7: Set up SSL Certificates with LetsEncrypt

In the next step we’re going to add an SSL certificate to our site and then configure NGINX to use it. I recommend that you read DigitalOcean’s entire tutorial on securing NGINX on Ubuntu 16.04 with LetsEncrypt, but I’ll provide just the steps you need here. First, install LetsEncrypt:

Next, we’ll install our certs using:

Follow the instructions. Assuming you entered your domain name in the Nginx config file for your site above, the certbot tool should be able to automatically detect what domains you’d like to generate certificates for. Make sure you pick a reliable email address for receiving notifications. To increase security, DigitalOcean’s tutorial recommends setting up a strong Diffie-Hellman Group as follows:

Next we’ll edit the configuration snippet for NGINX that was created by Certbot and which will contain all of our SSL parameters. Open the file as follows:

Edit the file so that it looks like the one below. The top six or seven lines should have been created automatically for you by Certbot, and the ones below add the extra parameters we’ll need to take advantage of our heightened security profile:

Save and exit this file. Next we will update our NGINX configuration for our site again. Certbot has already configured it to redirect all traffic through HTTPS. My earlier blog post goes into why this is the desired behavior.  We’ll just clean up their auto-generated code for neatness sake. Open your config file with:

And modify it to look exactly like this (except of course for using your domain name instead of “” which needs editing in both server blocks):

If you do still want to allow non-secure HTTP traffic, please consult DigitalOcean’s blog post that I linked to above. Save and close this file. Then check the syntax and restart NGINX:

Finally, it is important to note that SSL certificates from LetsEncrypt expire every 90 days. In order that you don’t have to log into your server every 3 months to renew your certs, we’re going to set up a CRON job to autorenew them. From the command line:

Add the following lines:

This will update the LetsEncrypt client and then attempt to renew and load your certs (if necessary) every Monday. In case you’d like to test to make sure the automated renewal will work, you can use the following command to do a dry run:

Step 8: Install WordPress

Wow. All this work so far and we haven’t even installed WordPress yet! Let’s get to it.

There are a lot of opinions on the best way to download and maintain WordPress. My goals are:

  1. Check out the core files from some sort of version control repository
  2. Update core files from the same repo
  3. Have the ability to take advantage of automatic updates

I haven’t found a way to do this with Git yet, so for this tutorial I’m sticking with Subversion. As such the first step will be to install SVN:

Next, go to the web root directory, remove the index.php file if necessary, and check out the most recent version of WordPress (currently 4.8):

Make sure not to forget the “.” near the end of that last command! Otherwise the files will get exported out into a subfolder and you’ll have to move them. Next we have to update the ownership of the files so that our webserver can have full access:

Now you can visit your domain in a web browser and complete the basic WordPress installation as you normally would.

Step 9: Install WP Plugins and Set Up Email

In order to take advantage of nginx caching made available by the custom version of nginx that we installed, you’ll need to install the nginx helper plugin. Once you do that, go to Settings > Nginx Helper from the WP dashboard and check the box to “Enable Purge.” The default settings should be fine. Click “Save All Changes.”

In Step 2 above, I showed the DNS settings you should set up in order to have Mailgun handle all of your email. The nice thing about doing this is you can avoid having to set up and maintain your own SMTP server on your droplet. Setting up a mail server like postfix, sendmail, or exif can be a real pain as most email providers these days are extremely sensitive about preventing spam. After you’ve set up your domain at Mailgun, you can use the Mailgun for WordPress plugin to have all outgoing emails from your website routed through Mailgun. After you’ve installed it, go to Settings > Mailgun from the WP dashboard, copy and paste in your Mailgun domain name and API key, and then click “Save Changes” to get it set up. Click “Test Configuration” to make sure it is working. You may also want to use the Check Email plugin just to make sure that emails are being sent correctly.

If you want to install Jetpack, the PHP XML package was installed to support that back in Step 6.

Step 10: Securing and Optimizing WordPress

Here are some tips and strategies for securing and optimizing your WordPress install.

Enable and Configure Gzip Compression

Gzip compression shrinks files before sending them across the web, increasing the speed of transfer. Gzip is enabled by default in  /etc/nginx/nginx.conf but you should edit this section of the configuration file to specify the types of files that should be compressed. Here’s the list that I use, and I’ve found that in recent installs they are already there by default. Add the list of  gzip_types into the file:

Deny Access to Certain Files and Folders

Some files and folders that are inside your web root should not be directly accessible via the web. Add the following sections to your  /etc/nginx/sites-available/default file in the main server block:

Setup Caching and Purging

The custom version of NGINX that we installed earlier allows you to use some advanced caching features. First you’ll want to install the NGINX Helper plugin within WordPress. Next you’ll follow the instructions in this blog post to configure caching. At the end of it all the config file for my site looked like this (including all of the edits we’ve made above):

Step 11: Setup Admin Emails

In order to get email messages and notifications from the system itself, e.g. when there’s a problem with system updates (configured in Step 12 below), you need to have a program like postfix or sendmail running on your system. There are a number of different ways to configure this, but I’m going to show you one that will allow emails to be routed through your Mailgun SMTP server. It is based on this tutorial from the EasyEngine folks. First, install the necessary packages. When prompted about your server type, select “Internet Site”, and for your FQDN, the default should be acceptable. Then open the config file for editing:

You’ll need to edit the mydestination property and add a few properties. You can leave all of the rest of the defaults. I ended up adding the following:

Then open/create a file where you’ll store your SMTP credentials:

And into this file add a single line:

You’ll have to get the password for the postmaster account from your Mailgun dashboard. Next we need to lock down this file and tell postfix to use it by running the following:

Finally, you can test your setup by reloading postfix then running the following:

If all has gone well, you should receive an email from the server at the address you typed on that last line. You can also check the logs at Mailgun to confirm that the message was routed through their servers.

Step 12: Final Server Tweaks

Finally, there are two more things we should do to keep our server up to date and healthy. The first is to make sure unattended upgrades are enabled:

When I finished editing it, my file looked like this:

I also updated the  /etc/apt/apt.conf.d/10periodic file to look like:

Lastly, I ran the following commands to make sure everything was up to date:

One more thing I’ve found to be useful is setting up a swapfile to handle situations where the server might otherwise run out of memory. Here is a good tutorial to walk you through the process. I found I needed to do this for some installations where MariaDB was crashing due to not having enough memory.


There are more plugins and tweaks you can make to improve the performance of your site. For example, you could host your images and other static resources on a CDN, and we didn’t talk about combining and minifying the CSS and JS files on the site. However, with all of the above, you should have made a very good start at having a screaming fast and secure website. Enjoy!

Write a Comment



  1. Dude, this is great! I’m gonna give this a try soon… Question tho, what if I don’t wanna use Let’s encrypt? It’s still beta and I rather use an old-school but reliable certificate. Thanks!

    • @Ben, FWIW, I’ve found Let’s Encrypt to be pretty solid over the last 4-5 months that I’ve been using it. That being said, I’d consult the technical docs of the CA from which you buy your certificate. Sorry for the “RTM” response, but all of my past experience installing certificates has been automated by whatever web host I happened to be using at the time. I’m sure there are some pretty good tutorials for installing 3rd party certs on your own Ubuntu server out there somewhere. 🙂

  2. I sure have enjoyed working through your example. I’m impressed that you see the bleeding edge picture so well regarding performance enhancements. Thank you for taking the time to explain in such depth how you’ve developed WordPress into a fast experience.

    I’d like to suggest that upon completion of the steps here that once completed you should take a snapshot of your DigitalOcean image. This allows you to always go back to that point in time when you’ve got everything solid and working correctly. It’s like having that fall-back when or if everything goes ‘not good…’

    Once again, nice work. Thanks…

    • @John, yeah, given all the work that goes into one of these setups, it would be nice to have a base image to work from. The only drawback I can see is that each new install will have a different domain name, user creds, etc. Maybe this summer I’ll be feeling industrious enough to write a script that can automate the personalizable parts. Thanks for the comment!

  3. Hi! there the guide is pretty solid. I have few sites that I want to migrate my sites from shared hosting to Un-Managed DO VPS,this guide serves my purpose but all I want to Know is how can we add multiple sites to a single droplet with this setup it will be helpful if you can help me with this.

    TIA 🙂

    • Depends on how you want to add sites. If you want to use a WordPress multi-site setup, I don’t really have any experience with that. However, if you just want to have another, standalone WP site, the rough outline for doing that would be:

      1. Create a new directory for the site, e.g. /var/www/site2, and download WP into that directory as with the first site
      2. Create a copy of the NGINX config file: sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/site2
      3. Edit this file to point to the new WP install you created in step 1, and the new SSL certs that you’ll have to generate for the new site
      4. Create a symbolic link to this config file: sudo ln -s /etc/nginx/sites-available/site2 /etc/nginx/sites-enabled/site2
      5. Restart NGINX: sudo service nginx restart
      6. Create a new database with a new user like you did for the first site
      7. Visit the new URL and follow the WP installation and configuration steps as you did for the first site


      • Morgan you really done a very great article especially for newbies its really a very good start to find all the server configurations at one place.

        i simply followed all the steps mentioned on this page and was able to live my https site on vps easily.. but now i am facing tough time while moving another site to this vps.. i have exactly followed the steps you mentioned above for multiple sites, but unfortunately i think there is something missing you forgot to mention because of which the second site is not working.. whenever i open the second site, it redirects to the ist site..

        i am mostly getting errors/warnings regarding the fastcgi_cache settings

        fastcgi_cache_path /var/run/nginx-cache levels=1:2 keys_zone=WORDPRESS:100m inactive=60m;
        fastcgi_cache_key “$scheme$request_method$host$request_uri”;
        fastcgi_cache_use_stale error timeout invalid_header http_500;
        fastcgi_ignore_headers Cache-Control Expires Set-Cookie;

        can you please tell in detail whether we should add the above 4 fastcgi_cache statements in the vhost file of each site seperately or add these lines in the /etc/nginx/nginx.conf ..

        • Sorry for the delayed response. I have never tried to set up more than one site on the same VPS. That being said, the general approach is to add another config file for the second site in /etc/nginx/sites-available and symlink to that file in /etc/nginx/sites-enabled. Each of the site config files in /etc/nginx/sites-available/ needs to specify the specific domain names that are handled by that server config. Also note that the flag default_server should only go into one of those config files. That is the site that gets served, for example, if someone visits your site by typing in the IP address directly. I’m sure there’s a good tutorial on this out there. If you find it, please drop a link to it here! Thanks!!

  4. There seems to be an error in line 41 of your cache config file. It ends in g$ which makes no sense – it must surely end in { – maybe it got cut off.

  5. RE: code in the “At the end of it all the config file for my site looked like this” section.
    Please include a raw file. I can’t access line 41.

    • Oops, yeah, looks like it got cut off. Should be fixed now. There’s a “copy” link in the toolbar at the top of the code block.

  6. And last.
    I have Internal Server Error 500 on wp-admin/options.php.

    PHP message: PHP Fatal error: Uncaught Error: Call to undefined function utf8_decode()$

    Server was missing php-xml package. I ran below command to install it.
    sudo apt-get install php7.0-xml

    All working now.

  7. Hello:
    Thanks for the time to write the tutorial. I has helped me a lot.

    Regarding the installation of packages for WordPress using the FTPS method. It is working ( at least in my setting ) “even” when there is “no” wp-user. WordPress is basically using www-data to upload and install the software. So you might not need to modify the wp-config.php adding the FTPS configuration and it would work.


  8. I have been looking for a procedure for installing WordPress with Nginx. Thanks! However, I got an error in Step 3 after creating the /etc/apt/sources.list.d/nginx.list file:

    root@linode1: /root
    ==> apt-get update
    Hit:1 xenial InRelease
    o o o
    Fetched 103 kB in 0s (143 kB/s)
    Reading package lists… Done
    W: GPG error: Release: The following signatures couldn’t be verified because the public key is not available: NO_PUBKEY 3050AC3CD2AE6F03
    W: The repository ‘ Release’ is not signed.
    N: Data from such a repository can’t be authenticated and is therefore potentially dangerous to use.
    N: See apt-secure(8) manpage for repository creation and user configuration details.

    Is there a step missing for getting a public key?

    • Hi George, Sorry for the delayed response. Looks like you’re working at Linode instead of DigitalOcean. That shouldn’t matter for most of the steps, however, it looks like both hosts maintain their own mirrors for various Ubuntu distros. My guess is that the problem you’re experiencing is due to a difference in the way that Linode manages their Ubuntu mirror. That being said, I think getting the GPG keys is a relatively straightforward process and it is likely documented somewhere in the Linode user forums. Feel free to post the link back to the solution you found if you got one. Thanks!

  9. Hello,

    Thank you for this guide. I’m trying it for the first time, and have run into an error towards the end of Step 7, just before adding the cron job to renew the certificates.

    Where you’ve said:
    If you do still want to allow non-secure HTTP traffic, please consult DigitalOcean’s blog post that I linked to above. Save and close this file. Then check the syntax and restart NGINX:
    sudo nginx -t
    sudo service nginx restart

    Upon doing sudo nginx -t, I get the following error:
    nginx: [emerg] the size 10485760 of shared memory zone “SSL” conflicts with already declared size 20971520 in /etc/nginx/snippets/ssl-params.conf:11
    nginx: configuration file /etc/nginx/nginx.conf test failed

    Can you please help with this?

    • Running sudo nginx -t checks that the syntax of your nginx config files is correct. The error you’re seeing indicates that you’ve specified the size of the shared memory zone “SSL” in more than one place, and that the sizes indicated conflict with one another. My best guess is that you’ve got two lines that specify the memory size, one in /etc/nginx/nginx.conf and one in /etc/nginx/snippets/ssl-params.conf that conflict with one another. You need to delete or comment out one of them.

  10. Seems like the mariadb install process has changed and I wasn’t prompted during install to set a root password as you indicated. But you can set this when running the mysql_secure_installation so all good… However, then when trying to mysql -u root -p I got “Access Denied”. Seemingly this is another security issue and running mysql -u root -p worked (explained in answer here:

    Just sharing in case others experience the same. Fingers crossed the rest of my setup goes to plan. Great tutorial so far 🙂

  11. I don’t suppose you have any tips for pretty URLs? I’m completely new to nginx and following this I have the site working for admin and homepage but all of my posts 404 I think because of the permalink structure. Any tips appreciated.

  12. A++++ Step by step to up “Super-fast Secure WordPress Install on DigitalOcean with NGINX, PHP7, and Ubuntu 16.04 LTS” Morgan.

    With regard to the server blocks, Is there anything I can do about someone who has pointed their domain at my ip?.

    I mean the right code on the default file (I serve only one site), concretely, the server blocks part.

    What Happens: content, i’ve visited through ip) redirects to (with content)
    then my config(as this entry explains step by step) redirects to mydomain:443(with content), this is what i want block.

    I guess, this is a not legacy SEO practice.

    Great work @mcbenton.

      • Solved!

        Filled an abuse form to cloudfare.

        With regard to the server block I add another server block where the server_name contains the returing a 444 message.


    • MySQL and MariaDB are essentially the same thing. If you’ve already got MySQL installed on your system, it’s perfectly fine to just stick with that. I doubt you’ll notice any performance differences unless you have a very highly trafficked site. If you’re installing a fresh clean new install of WP on your server, though, I definitely recommend choosing MariaDB over MySQL. That being said, you do NOT want to have both of them running at the same time.

    • Glad you liked it! Those lines in the nginx config basically tell the server not to log or return a 404 for requests to and /robots.txt. Not every site has a favicon or robots.txt file, and in any case it’s not necessary for the server to log these requests. The “try_files…” line is already included in the config file in my tutorial and is important to make permalinks work correctly. Essentially, the config file in my tutorial is much more comprehensive and does a better job at preventing malicious attacks. I’ll go ahead and add in those two lines about favicon/robots. Thanks for the tip!

      • Cool, sounds good. Followup question: I’m setting up mailgun, do I edit the CNAME/MX/TXT records at my DigitalOcean droplet, my domain registrar, or both?

        Right now, my domain is registered with Bluehost, and I pointed the nameservers to my VPS. It has it’s own A/CNAME/MX/TXT records. I tried one of the A records, and it still works. So should I delete all the records at Bluehost? Thanks!

        • okay, I asked DO support and they said to just change the DNS records at DO.

          Last question (hopefully): Your mailgun TXT record in your first picture says “mailo._domainkey”. I’m following the Mailgun tutorial and it says “ Why the difference, and which one should I use?

          • Use the one that they specify. They use different subdomains for different accounts. If you don’t use the one that they give you, your DNS won’t verify with their system.

  13. Morgan, thank you for all of the hard work that you put into this post! It only took me about 4 hours start to finish, switching between the computer and cooking Thanksgiving dinner. I skipped two steps:

    (1) forcing file downloads through FTPS, because I wasn’t sure of the username/password: does it need to be the same wpuser creds that I used when setting up MariaDB & WordPress? And,
    (2) setting up mail (I’ll do that later.)

    Now I’ll start building a real site on it so I can test it out. Again, thanks for sharing all of your work. We all appreciate it. Happy Thanksgiving (if you’re in the US, if not, happy Thursday).

  14. This may have been my mistake, but I had to put in an additional command to allow ssh on the firewall around Step 9/10 — I’d gotten disconnected from my VPS it was blocking any non-local connections from port 22 (ssh).

    To fix this I needed to:

    1) Login to my server via the Digital Ocean console
    2) Type sudo ufw status (gets firewall status)
    3) Type sudo ufw allow ssh (allows ssh connections)

    Then I was able to connect using Terminal on my Mac. Again, might have been caused by some user error on my part, but this got me back in so I could complete the steps.

  15. Morphatic,
    I was dying bro…You Helped me…All the resouces on the internet even some of the digital ocean Tutorials seems un updated…Many functions has been deprecated and that for a guy like me is too troublesome..I had to destroy my droplet atleast 25 times.. Thank you so much..

  16. Hey morphatic, I have just setup multiple sites in a droplet, everthing went on good. But i cant use fastcgi, it says duplicate directive in both sites… How do i resolve this?
    thank you..

    • Well, each line in your nginx config file is referred to as a directive. Without looking at your code, I couldn’t say for sure, but my guess is that you somehow copied some of the directives into your config file more than once. I recommend posting your config file code over on Stack Overflow. You’ll likely get an answer to your problem in less than an hour.

  17. Hey morphatic,
    Thank you for your article, Because of you my website is amazingly fast. You mentioned it could be more optimized with ” host your images and other static resources on a CDN, and we didn’t talk about combining and minifying the CSS and JS files”.

    Please write a another post to do this as well. I cant find better resources than your site

    • Prashant,

      You might take a look at CloudFlare, or WP Super Cache, which are both plugins that will allow you to move static resources to a CDN. I don’t know when I might get around to writing a tutorial, but googling for one should find you several good ones.

    • I just updated the tutorial at the very end to link to a tutorial that will set up a swapfile so that your server should be a bit more resilient to these attacks. The cheapest server option from Digital Ocean doesn’t really come with that much memory, unfortunately.

    • I had never heard of brotli until you asked this question. Can’t answer definitely one way or the other, but I’ll look into it. Thanks for asking!

  18. Morgan,

    Awesome job. Thank you for posting this. Any idea how I may go about upgrading this custom version of NGINX? I stopped the NGINX service and then attempted to do so with the following: apt-get upgrade nginx-custom, to no avail. From what I can tell running apt-get update doesn’t update NGINX.

    • Hi Ryan, my guess is that the folks at rtcamp (who maintain the custom nginx version) just haven’t uploaded any updates in their repo. So your apt-get update may be working, but just not finding any updates to install. My experience is that the rtcamp folks don’t update their servers to reflect every release that the nginx folks put out. You can see the date of their latest update by browsing the repo.

  19. I’m using 40GB-2GB DO droplet. do i need to setup swapfile?
    how is easyengine simple setup with two commands different from this method?

    • Hi Maggi, with 2GB, I would think you would be okay, but of course it all depends on how much traffic your site gets. Setting up swap only takes a couple of minutes and doesn’t really cost you anything. As for your other question, I’ve actually never used the whole easyengine simple setup, so I don’t know. If you try it and like it, please come back and leave us another note. I may want to update this blog post to reflect that method. 🙂

  20. Hello!

    Thank you very much for the tutorial
    I’ve ran into an issue though
    I’m getting an ERR_TO_MANY_REDIRECTS error, and despite clearing out browser cookies, it still persists.

    Any ideas?


    • My guess is that the “Site Address (URL)” in your WP Admin > Settings > General section is pointing to the HTTP address for your site, but then NGINX is redirecting to the HTTPS address of your site, which creates an infinite loop. To fix this, you can either edit the Site URL manually in MySQL(MariaDB) or you can temporarily disable HTTPS in NGINX so that you can get into the admin section of your WP site and change the Site URL to HTTPS.

  21. Hey great post. I enjoyed reading it and implementing my instance. However, I installed LAMP and plan to install nginx infront of it as a reverse proxy. That is actually what WordPress site is using and what they recommend. So your statement “…even uses NGINX” might not be totally accurate in the way you are building it here. See –

    • Interesting read. Actually, I haven’t had any of the problems with permalinks that they discuss in that article and I’m not using Apache at all. Thanks for pointing this out, though.

  22. Hi! When I try to upgrade the server this is what it shows:
    The following packages have been kept back:
    nginx-custom nginx-ee
    […] 2 not upgraded.
    Is it something to be concerned?

    • Hmmm… Not sure. If I run into that problem on my own machines, I’ll be sure to update if I find a workaround.

  23. Hello!, Its a amazing tutorial you have here.

    I have implemented the exact steps on my website with million users per day. I am running latest version of wordpress on digital ocean. Everything seems to work fine. But one issue is with logged in users that the face “Connection lost” error. I tried to disable heartbeat also but to no use. They sometimes get that the connection was refused by the server.

    • Hi Faraz, you’re welcome. Usually the one-click-install is a script built by a particular hosting provider, e.g. DigitalOcean. Some of them have added the ability to add SSL via LetsEncrypt with one click, but you’d have to check with the specific provider.


  • VULTR VPS配置笔记 – Blog October 29, 2017

    Hi Morgan,

    What changes I need to do in nginx config or else for using this setup for WordPress Multisite?

    Sergey Komlev.