in Code, Tech Trends, Tutorials

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

FacebookTwitterRedditEmail

Updated: 2016-11-21

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 wordpress.com 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  ns1.digitalocean.com ,  ns2.digitalocean.com , and  ns3.digitalocean.com . 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.

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.

Now we need to adjust a php.ini setting that would allow a clever hacker to execute scripts they shouldn’t. 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, in this case  cgi.fix_pathinfo . In this case you want to remove the semicolon at the beginning of the line and set this setting to look like  cgi.fix_pathinfo=0 . While you’re in this file, you may also want to adjust the  post_max_size and  upload_max_filesize settings to something larger than their defaults of 8MB and 2MB, respectively. 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 exactly like this:

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:

During installation, LetsEncrypt has to add some files to your web root in order to confirm that you actually own your domain. In order to do that, we need to update the NGINX configuration for your site. Open up the config in nano using:

And then add this code inside the server block:

Save and exit. Then you can check to make sure you have no typos and restart NGINX with the following:

Now you will switch to the directory where we cloned LetsEncrypt and run the tool to actually create and install our certificate:

Follow the prompts that pop up. 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 create a configuration snippet for NGINX that will contain all of our SSL parameters. Create and open the file as follows:

And add this code into the file, making sure you add your domain at the top in the file paths for the ssl_certificate and ssl_certificate_key:

Save and exit this file. Next we will update our NGINX configuration for our site again to redirect all traffic through HTTPS. My earlier blog post goes into why this is the desired behavior. Open your config file with:

And modify it to look exactly like this (except of course for using your domain name instead of “yourdomain.com” 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.

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.6.1):

Make sure not to forget the “.” at the end of that last command! Otherwise the files will get checked 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:

Force File Downloads Through FTPS

Whenever you add or update a theme or plugin, you can require that under the hood WordPress will use an encrypted transport like FTPS. To do so, follow this tutorial at DigitalOcean. It’s a little old, but still works. The only change to it that I had to make was to NOT change the ownership of the files in  /var/www/html . Here is a condensed list of the commands you’ll run:

And then you need to add the following lines to your  /var/www/html/wp-config.php file:

Add that right about the line:  /* That's all, stop editing! Happy blogging. */ . And you may want to change the user name from “wp-user” to something else more personalized, just in case someone who’s reading about this technique doesn’t try to find a hole in your setup based on what username you picked.

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: 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:

Conclusion

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!

FacebookTwitterRedditEmail

Write a Comment

Comment

45 Comments

  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

      HTH!

      • 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.

    /var/log/nginx/error.log
    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.

    Cheers

  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 http://mirrors.linode.com/ubuntu xenial InRelease
    o o o
    Fetched 103 kB in 0s (143 kB/s)
    Reading package lists… Done
    W: GPG error: http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/xUbuntu_16.04 Release: The following signatures couldn’t be verified because the public key is not available: NO_PUBKEY 3050AC3CD2AE6F03
    W: The repository ‘http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/xUbuntu_16.04 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: http://stackoverflow.com/a/35748657/79935)

    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: notdesireddomain.com(without content, i’ve visited through ip) redirects to mydomain.com:80 (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.

    Thanks.
    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 baddomain.com returing a 444 message.

        Thnaks.

    • 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 http://yoursite.com/favicon.ico 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 “k1._domainkey.mg..com. 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.

Webmentions

  • VULTR VPS配置笔记 – Blog December 3, 2016

    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.