Hosting WordPress Yourself Part 8 – Complete Nginx Configuration


In the previous post of this series, I covered security enhancements, automatic server updates, WooCommerce caching and automated server tasks. In this final post I will demonstrate a complete Nginx configuration tuned for WordPress powered sites. In addition to amalgamating all information from the previous 7 articles, best practices from various sources, such as the WordPress Codex and H5BP are included. The following example domains are also included, which each demonstrate a different scenario:

  • – A basic WordPress install
  • – WordPress on HTTPS
  • – WordPress with FastCGI page caching
  • – WordPress on HTTPS with FastCGI page caching
  • – WordPress Multisite using subdomains
  • – WordPress Multisite using subdirectories

Although this article may appear relatively short compared to previous articles, I hope the accompanying GitHub repo will provide a wealth of information. The configuration files contain inline documentation throughout and are structured in a way to reduce duplicate directives, which are common across multiple sites. This should allow you to quickly create new sites with sensible defaults out of the box, which can be customized as required.


You can use the GitHub repo as a reference for creating your own configuration, or directly by cloning the repo into your etc directory. Follow the steps below to replace your existing Nginx configuration.

Backup any existing config:

sudo mv /etc/nginx /etc/nginx.backup

Clone the repo:

sudo git clone /etc/nginx

Copy one of the example configurations from sites-available to sites-available/

sudo cp /etc/nginx/sites-available/ /etc/nginx/sites-available/`

Edit the site accordingly, paying close attention to the server name and server paths. You will also need to create any directories used within the configuration and ensure Nginx has read/write permissions.

To enable the site, symlink the configuration into the sites-enabled directory:

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

Test the configuration:

sudo nginx -t

If the configuration passes, restart Nginx:

sudo /etc/init.d/nginx reload

The following configuration will be loaded, which includes sensible defaults for security, SSL and static file caching:

server {
    # Ports to listen on, uncomment one.
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    # Server name to listen for

    # Path to document root
    root /sites/;

    # Paths to certificate files.
    ssl_certificate /etc/ssl/;
    ssl_certificate_key /etc/ssl/;

    # File to be used as index
    index index.php;

    # Overrides logs defined in nginx.conf, allows per site logs.
    access_log /sites/;
    error_log /sites/;

    # Default server block rules
    include global/server/defaults.conf;

    # SSL rules
    include global/server/ssl.conf;

    location / {
        try_files $uri $uri/ /index.php?$args;

    location ~ \.php$ {
        try_files $uri =404;
        include global/fastcgi-params.conf;

        # Change socket if using a different PHP version
        fastcgi_pass unix:/run/php/php7.1-fpm.sock;
        #fastcgi_pass unix:/run/php/php7.0-fpm.sock;
        #fastcgi_pass unix:/var/run/php5-fpm.sock;

# Redirect http to https
server {
    listen 80;
    listen [::]:80;

    return 301$request_uri;

# Redirect www to non-www
server {
    listen 443;
    listen [::]:443;

    return 301$request_uri;

View on GitHub

Job done! I encourage you to explore the repo further and read through the documented configuration to get a feel for what’s going on. It should hopefully feel familiar as it follows the same conventions used throughout this series. Over time I hope to improve upon the configuration and add new best practices as they emerge. It’s also a public repo, so please open a pull request for any improvements you may have.

That concludes this article and the series as a whole. It’s been quite a journey, but one that I hope you’ve found enlightening. Please feel free to leave your questions below and any ideas you may have for future articles. Thanks for reading!

About the Author

Ashley Rich

Ashley is a PHP and JavaScript developer with a fondness for solving complex problems with simple, elegant solutions. He also has a love affair with WordPress and learning new technologies.

  • Daylighter

    Thanks for the guide!

    They were very simple to follow and still explains why each step is done.

  • Selçuk Vardar

    Ashley, you tutorial is great! Thanks a million above all.

    I have done all the steps and my tests were perfect as expected with twentyfifiteen but after I installed my WP theme they became terrible:

    This rush generated 3 successful hits in 60 seconds and we transferred 1.22 MB of data in and out of your app. The average hit rate of 0.05/secondtranslates to about 4,320 hits/day.

    The average response time was 656 ms.

    You’ve got bigger problems, though: 99.88% of the users during this rushexperienced timeouts or errors!
    I really appriciate if you have any idea or suggestion.

  • Jhonis

    Thumbs up Ashley! Thanks for the great, and easy to follow tutorial.

    And a million thanks for the 2017 update!

  • Jhonis

    Hey Ashley,

    Everything’s been working out great up until I switched the nginx directory to match the git repository example on this page.

    Now I’m getting a 502 Bad Gateway page on a site that was working before the change.

    Did I miss something?

    • I get 502 Bad Gateway as well.

      • Jhonis

        make sure your nginx.conf has your user set, and not www-data;

        I’f you’ve been following the series, most likely you have set up your own user.

        That was my problem, and was easy to fix

        • Thanks, I reverted to the old nginx. I will try this again later 🙂

  • Why isn’t there an http2 in the listen 443?

    • josh

      Perhaps because no contents will be served, and it is just a redirect.

  • benpli host

    which should i place my root wordpress folder, in /www/.. or /www/html/..
    i’am confused about this.

    and also, the best way to redicect www or https://www

    thank you.

  • Scavenger

    hi Ashley

    I am young on nginx but I already got some php7-fpm configuration working with it, one WP domain, and multiple subdomains for admin, cdn etc but it got messy very rapidly.

    So I forked this nginx conf repo (which has been forked so many times already) to give it a try.
    I like the clear organization of files and directories offered in this solution. I added a locations/ directory, and updated some code here and there.

    Now I have a bug. After creating the ssl conf and updating it with real paths and addresses, this setting kind of works real fast for me, but I still have an issue with location aliases within a server declaration.
    I tried the solutions described here (location + rewrite of (jpe?g|gif|css|png|js|ico|pdf|m4a|mov|mp3|docx?|xlsx?|pptx?) to cdn subdomain) but it works only for documents (doc, xls), not for images!
    I really am confused. Why do I see a GET in the logs for the documents (good), and an open() for the images (bad: files doesn’t not exist)?
    It’s like nginx has trouble identifying locations within a server directive, depending on the type of files. Doesn’t make much sense, does it?