Web Server Load Balancing with NGINX Plus

WordPress is one of the most popular open source content management systems today, used by more than 30% of all websites to host online web applications. WordPress is written in PHP, and both PHP and WordPress can run on NGINX Unit, the new dynamic application server from NGINX, Inc.

Formerly, deploying WordPress with NGINX required the use of a separate application server, such as Apache or PHP‑FPM. NGINX Unit is more flexible, supporting Go, Perl, Python, and Ruby along with PHP.

With NGINX Unit, you set up and make changes to your application server dynamically – without service disruption or configuration reloads – using the RESTful JSON API. This makes hands‑on management easier, and also makes it much easier to automate some or all of your management tasks.

In this blog post, we describe how to set up WordPress on a “LEMU” stack (Linux, NGINX Open Source, MySQL or MariaDB, and NGINX Unit) on a host running Ubuntu 16.04.

Editor – We also offer a bash script that automates WordPress installation on Ubuntu with TLS certificates, NGINX Open Source for web serving, and NGINX Unit for application serving. For details, see Automating Installation of WordPress with NGINX Unit on Ubuntu.

The instructions are written from the bottom up. First, we describe the architecture. Then, we show how to install the database, application language, application server, and finally, the web server and load balancer. This way, you can validate every step of your installation, simplifying troubleshooting in case of errors.


  • A host running one of the distributions that NGINX Unit supports (we’re using Ubuntu 16.04 in this blog)
  • root privilege, or equivalent access via sudo

Architecture Overview

WordPress is a fairly standard three‑tier web application. It includes PHP scripts that have to be executed by a PHP processor and static files that have to be delivered by a web server.

Diagram of WordPress with NGINX Unit
Architecture of a simple WordPress application

However, WordPress has two different URL schemes:

  • Direct URLs. For users who request a PHP file directly (for example, with GET /wp-admin/admin.php), an application server needs to open the required PHP file and process it.
  • User‑friendly URLs. Most WordPress admins prefer to have meaningful URLs such as /blog, /products/software, or /store instead of /index.php?p=123, /index.php?p=234, or /index.php?p=4567.

WordPress does not create user‑friendly files and folders in the filesystem. Instead, it expects a web server or an application server to send all requests for unknown files to /index.php.

With NGINX Open Source and NGINX Unit, the two URL schemes are configured as two separate applications, running at separate locations.

Installing MySQL

One of the key required components of a fresh WordPress installation is a database to store user accounts and site data. In this blog post, we’re using MySQL.

Note: For a detailed description of each action, see the MySQL documentation about secure installation.

  1. Install and configure MySQL:

    $ sudo apt-get install mysql-server
  2. Enter a new MySQL root password when prompted:

  3. Run the MySQL configuration tool and respond to the prompts:

    $ sudo mysql_secure_installation
    VALIDATE PASSWORD PLUGIN can be used to test passwords and improve security. It
    checks the strength of password and allows the users to set only those passwords
    which are secure enough. Would you like to setup VALIDATE PASSWORD plugin?
    Press y|Y for Yes, any other key for No:
    Using existing password for root.
    Change the password for root ? ((Press y|Y for Yes, any other key for No) :
    By default, a MySQL installation has an anonymous user, allowing anyone to log
    into MySQL without having to have a user account created for them. This is
    intended only for testing, and to make the installation go a bit smoother.
    You should remove them before moving into a production environment.
    Remove anonymous users? (Press y|Y for Yes, any other key for No) :
    Normally, root should only be allowed to connect from 'localhost'. This ensures
    that someone cannot guess at the root password from the network.
    Disallow root login remotely? (Press y|Y for Yes, any other key for No) :
    By default, MySQL comes with a database named 'test' that anyone can access. This
    is also intended only for testing, and should be removed before moving into a
    production environment.
    Remove test database and access to it? (Press y|Y for Yes, any other key for No) :
    Reloading the privilege tables will ensure that all changes made so far will take
    effect immediately.
    Reload privilege tables now? (Press y|Y for Yes, any other key for No) :
    All done!

Creating a MySQL Database and a WordPress User

Now that MySQL is installed, we create a database to store the WordPress content and a user account that has permission to manage the database.

The values in orange are examples we’re using in this blog; substitute the values appropriate to your deployment.

  1. Log in to the MySQL root account:

    $ sudo mysql -u root -p
  2. Create a database for WordPress to use:

    mysql> CREATE DATABASE wordpress;
  3. Create a WordPress user and password:

    mysql> CREATE USER user@localhost IDENTIFIED BY 'secure_password';
  4. Grant privileges to the newly created user:

    mysql> GRANT ALL PRIVILEGES ON wordpress.* TO user@localhost;
  5. Make MySQL recognize the changes:

  6. Exit MySQL:

    mysql> Exit

Installing WordPress

In this blog, we’re preparing the WordPress installation in a temporary directory, and later moving the files to the document root.

  1. Change directory to /var/www:

    $ cd /var/www/
  2. Download the latest version of WordPress and unpack the files:

    $ sudo wget
    $ sudo tar xzvf latest.tar.gz

Configuring and Securing WordPress

As a quick and simple way to configure WordPress, we start with a copy of the sample configuration file provided by WordPress and make a few modifications.

  1. Create a copy of the sample configuration file, naming it wp-config.php:

    $ cd /var/www/wordpress
    $ sudo cp wp-config-sample.php wp-config.php
  2. To strengthen security, use the WordPress salt function to randomly generate new secret keys. Changing out the keys allows the administrator to force all users to log in again.

    The output is similar to the following; in Step 4 you’ll copy values from the output into wp-config.php.

    $ curl -s
    define('AUTH_KEY',         '3LJ|w/!Fit|/mo]>XLxWU+dG+7N+)64Y.KVVNoQ#X}1.s[os');
    define('SECURE_AUTH_KEY',  ')%C_q0{RNEe1+A{>C=#|y3c2} e J)AsxXq}z0H;x#$0J{o{');
    define('LOGGED_IN_KEY',    '@hD(g;-os^||uVI%6&`U1WI3YIz)F:7&Y%[jW]@DawoP{]A[');
    define('NONCE_KEY',        'fVv:A3(XNG`fXNi6Pmg#4,UnX)K|t8+jO{iv7g2Fay&kDJzV');
    define('AUTH_SALT',        ']s2/a`+2z~5+c6g)f-^h~D,@7C(eNr63x}Zz[)H]:!>=Q5(f');
    define('SECURE_AUTH_SALT', 'Xw8x`IO.,@Y6l5)NK9)#!8V?s=&nzwXLRIwiBc,k];k3_%Fo');
    define('LOGGED_IN_SALT',   '3<.D-+I#Lr6+~We@1+~%LO=s9FHkgxV+w-l-S8g%BVC:dMD<');
    define('NONCE_SALT',       'UVc/gj1Mjh*Tcl|9aq)YR|1!045=-2VpxNXrDNl>})9-Q>[x');
  3. Using your preferred text editor, open wp-config.php. Here we’re using nano:

    $ sudo nano wp-config.php
  4. Find the following lines in wp-config.php and replace the text in orange with the database name and password you defined in Steps 2 and 3 of Creating a MySQL Database and a WordPress User. For the username used in that section, substitute wpuser or a similar value:

    // ** MySQL settings - You can get this info from your web host ** //
    /** The name of the database for WordPress */
    define('DB_NAME', 'wordpress');
    /** MySQL database username */
    define('DB_USER', 'wpuser');
    /** MySQL database password */
    define('DB_PASSWORD', 'secure_password');
  5. Find the following lines in wp-config.php and in each one copy in the value generated by salt in Step 2:

    define('AUTH_KEY',         'value generated by salt');
    define('SECURE_AUTH_KEY',  'value generated by salt');
    define('LOGGED_IN_KEY',    'value generated by salt');
    define('NONCE_KEY',        'value generated by salt');
    define('AUTH_SALT',        'value generated by salt');
    define('SECURE_AUTH_SALT', 'value generated by salt');
    define('LOGGED_IN_SALT',   'value generated by salt');
    define('NONCE_SALT',       'value generated by salt');
  6. Save and exit the file.

  7. Set the following read‑write permissions for your user (substituting the appropriate name for wpuser):

    $ sudo chown -R wpuser:www-data /var/www/wordpress
    $ sudo find /var/www/wordpress -type d -exec chmod g+s {} \;
    $ sudo chmod g+w /var/www/wordpress/wp-content
    $ sudo chmod -R g+w /var/www/wordpress/wp-content/themes
    $ sudo chmod -R g+w /var/www/wordpress/wp-content/plugins

Installing PHP

We recommend that you install PHP and relevant extensions prior to installing NGINX Unit. For WordPress, you need several extensions that are not in NGINX Unit’s list of dependencies.

When you install a prebuilt NGINX Unit package as instructed in the next section, the package manager downloads the correct dependencies. For the OS version we’re using, Ubuntu 16.04, the dependencies are based on PHP 7.0, but substitute the correct values for your OS version. For example, for CentOS 7.0 the dependencies are for PHP 5.4; for Ubuntu 18.04, they’re for PHP 7.3.

With Ubuntu 16.04, install the PHP 7.0 extensions most commonly used with WordPress (adjust as necessary for your OS version):

$ sudo apt-get install -y php7.0 php7.0-common php7.0-mbstring php7.0-gd php7.0-intl php7.0-xml php7.0-mysql php7.0-mcrypt

Installing NGINX Unit

  1. Install the precompiled NGINX Unit package for your operating system, following the instructions in the NGINX Unit documentation.

  2. Install the additional NGINX Unit module for PHP:

    $ sudo apt-get install unit-php
  3. Run these commands to verify that NGINX Unit and PHP are functioning as expected:

    $ sudo service unit restart
    $ sudo curl -X PUT --data-binary @/usr/share/doc/unit-php/examples/unit.config --unix-socket /run/control.unit.sock http://localhost/config 
    $ curl http://localhost:8300/

    If the phpinfo page appears, NGINX Unit was installed correctly. If not, see the NGINX Unit Troubleshooting Guide.

Configuring NGINX Unit

The following instructions create a file of JSON‑formatted configuration for WordPress and use the NGINX Unit API to load it into NGINX Unit. This instantaneously updates the initial NGINX Unit test configuration we loaded in Step 3 of the previous section.

  1. Change directory to the location where you want store the file of WordPress configuration (we’re using /var/www/wordpress):

    $ cd /var/www/wordpress
  2. Using your preferred text editor, create a new file called wordpress.config. As before, we’re using nano:

    $ sudo nano wordpress.config
  3. Copy in the following contents and save the file:

        "listeners": {
            "": {
                "application": "script_index_php"
            "": {
                "application": "direct_php"
        "applications": {
            "script_index_php": {
                "type": "php",
                "processes": {
                    "max": 20,
                    "spare": 5
                "user": "www-data",
                "group": "www-data",
                "root": "/var/www/wordpress",
                "script": "index.php"
            "direct_php": {
                "type": "php",
                "processes": {
                    "max": 5,
                    "spare": 0
                "user": "www-data",
                "group": "www-data",
                "root": "/var/www/wordpress",
                "index": "index.php"

    This configuration creates two NGINX Unit applications, one for each URL scheme – the web application and the administration panel.

    Using the NGINX Unit script parameter instead of the index parameter means that requests for pages that are not found use the main index.php script in WordPress. For more information, see the NGINX Unit documentation for PHP application objects.

    To scale your application, change the IP addresses and ports accordingly.

  4. Run this curl command to load the configuration:

    $ curl -X PUT --data-binary @/var/www/wordpress/wordpress.config --unix-socket /run/control.unit.sock http://localhost/config

Installing NGINX Open Source

We recommend installing NGINX Open Source as a prebuilt package from the mainline branch in our official repository. The packages available from other sources (operating system vendors, for example) are often several releases behind. You can also build NGINX Open Source from source.

For large‑scale and production WordPress deployments, NGINX Plus includes enhanced capabilities that improve site performance and make management easier. You can try it for free for 30 days. See Enhanced Capabilities in NGINX Plus for more information.

  1. Install NGINX Open Source.

  2. Start NGINX:

    $ sudo service nginx start
  3. In a browser, navigate to the IP address or hostname of the NGINX host. The appearance of this page confirms that NGINX is running.

    Welcome to NGINX screen

    You can also run the following command on the NGINX host and confirm that the raw HTML code for the page appears in the terminal.

    $ curl localhost

    If the curl command works, but browser access does not, check your routing configuration, firewalls, and any network settings that affect traffic between the NGINX host and your client machine.

Configuring NGINX Open Source

Now we configure NGINX Open Source to support both of the URL schemes described in Architecture Overview, by defining location blocks that forward traffic to our two NGINX Unit application servers as appropriate.

  1. Create a backup of the NGINX default configuration file:

    $ cd /etc/nginx/conf.d/
    $ sudo mv default.conf default.conf.bak
  2. Using your preferred text editor, create a new default configuration file (again, we’re using nano):

    $ sudo nano default.conf
  3. Copy the following contents into the file and save it:

    upstream index_php_upstream {
        server; # NGINX Unit backend address for index.php with
                               # 'script' parameter
    upstream direct_php_upstream {
        server; # NGINX Unit backend address for generic PHP file handling
    server {
        listen      80;
        server_name localhost;
        root        /var/www/wordpress/;
        location / {
            try_files $uri @index_php;
        location @index_php {
            proxy_pass       http://index_php_upstream;
            proxy_set_header Host $host;
        location /wp-admin {
            index index.php;
        location ~* .php$ {
            try_files        $uri =404;
            proxy_pass       http://direct_php_upstream;
            proxy_set_header Host $host;

    The first location block handles requests for the root URL (/). The try_files directive searches for the exact URI. If it doesn’t exist, the request is sent to the second, named location @index_php, which proxies it to the index_php_upstream upstream group.

    The third location block handles requests for /wp-admin and serves the index.php file directly. This location does not need to handle URLs that aren’t found.

    The last location block handles requests for the .php extension, which match the regular expression. It proxies the requests to the index_php_upstream upstream group, where the generic NGINX Unit application handles all PHP requests directly. The try_files directive handles 404 errors by displaying a page generated directly by NGINX. Another option is to let WordPress handle 404 errors, by replacing =404 with @index_php.

    For security purposes, or for segregating traffic further, you can include additional location blocks and different parameters to the try_files directive.

  4. Verify that the main /etc/nginx/nginx.conf configuration file has an include directive that reads in files from the /etc/nginx/conf.d directory:

    include /etc/nginx/conf.d/*.conf;
  5. Run this command to verify that the configuration is syntactically valid:

    $ sudo nginx -t
  6. Reload the configuration:

    $ sudo nginx -s reload
  7. In a web browser, navigate to the IP address or hostname of your WordPress site and complete the installation:

You’re all set! You now have WordPress up and running in a “LEMU” stack with NGINX and NGINX Unit.

Enhanced Capabilities in NGINX Plus

To get the most out of your WordPress application, we recommend NGINX Plus for its enhanced features, such as application health checks, sophisticated caching of both static and dynamic content, and live activity monitoring. These features are especially beneficial if you have multiple WordPress servers and require load balancing. And they work well with NGINX Unit; the image below shows the live activity monitoring dashboard on an NGINX Plus instance that is load balancing three NGINX Unit servers.

For details about all the great features in NGINX Plus, see the product page. To try it in your WordPress environment, start your free 30-day trial today or contact us.

Webinar: Optimize Application Execution with NGINX Unit

Cover image

The application server that speaks your language


Amanda Bockoven

Associate Sales Engineer


F5, Inc. 是备受欢迎的开源软件 NGINX 背后的商业公司。我们为现代应用的开发和交付提供一整套技术。我们的联合解决方案弥合了 NetOps 和 DevOps 之间的横沟,提供从代码到用户的多云应用服务。访问 了解更多相关信息。