In chapter 1 of this guide, I took you through the initial steps of setting up and securing a virtual server on DigitalOcean. In this chapter I will guide you through the process of setting up Nginx, PHP-FPM and MariaDB, which will form the foundations of a working web server.

Before moving on, you will need to open a new SSH connection to the server, if you haven’t already:

SSH

Installing Nginx

Although the official Ubuntu package repository includes Nginx packages, they’re often very outdated. Instead, I like to use the package repository maintained by Ondřej Surý that includes the latest Nginx stable packages.

First add the repository and update the package lists:

sudo add-apt-repository ppa:ondrej/nginx -y
sudo apt update

There may now be some packages that can be upgraded, let’s do that now:

sudo apt dist-upgrade -y

Then install Nginx:

sudo apt install nginx -y

Once complete, you can confirm that Nginx has been installed with the following command:

nginx -v

Additionally, when visiting your server’s Fully Qualified Domain Name (FQDN) in the browser, you should see an Nginx welcome page.

Welcome to nginx

Now that Nginx has successfully been installed it’s time to perform some basic configuration. Out-of-the-box Nginx is pretty well optimized, however there are a few basic adjustments to make. But, before opening the configuration file, you need to determine your server’s CPU core count and the open file limit.

Enter the following command to get the number of CPU cores your server has available and take note of the number as we’ll use it in a minute:

grep processor /proc/cpuinfo | wc -l

Run the following to get your server’s open file limit and take note, we’ll need it as well:

ulimit -n

Next, open the Nginx configuration file, which can be found at /etc/nginx/nginx.conf:

sudo nano /etc/nginx/nginx.conf

nginx.conf

I’m not going to list every configuration directive but I am going to briefly mention those that you should change. If you would find it easier to see the whole thing at once, feel free to download the complete Nginx config kit now.

Start by setting the user to the username that you’re currently logged in with. This will make managing file permissions much easier in the future, but this is only acceptable security-wise when running a single user access server.

The worker_processes directive determines how many workers to spawn per server. The general rule of thumb is to set this to the amount of CPU cores your server has available. In my case, this is 1.

The events block contains 2 directives, the first worker_connections should be set to your server’s open file limit. This tells Nginx how many simultaneous connections can be opened by each worker_process. Therefore, if you have 2 CPU cores and an open file limit of 1024, your server can handle 2048 connections per second. However, the number of connections doesn’t directly equate to the number of users that can be handled by the server, as the majority of web browsers open at least 2 connections per request. The multi_accept directive should be uncommented and set to on, which informs each worker_process to accept all new connections at a time, opposed to accepting one new connection at a time.

Moving down the file you will see the http block. The first directive to change is the keepalive_timeout, which by default is set to 65. The keepalive_timeout determines how many seconds a connection to the client should be kept open before it’s closed by Nginx. This directive should be lowered as you don’t want idle connections sitting there for up to 65 seconds if they can be utilized by new clients. I have set mine to 15.

For security reasons you should uncomment the server_tokens directive and ensure it is set to off. This will disable emitting the Nginx version number in error messages and response headers.

Underneath server_tokens add the client_max_body_size directive and set this to the maximum upload size you require in the WordPress Media Library. I chose a value of 64m.

Further down the http block you will see a section dedicated to Gzip compression. By default, Gzip is enabled but you should tweak these values further for better handling of static files. First, you should uncomment the gzip_proxied directive and set it to any, which will ensure all proxied request responses are gzipped. Secondly, you should uncomment the gzip_comp_level and set it to a value of 5. This controls the compression level of a response and can have a value in the range of 1 – 9. Be careful not to set this value too high as it can have a negative impact on CPU usage. Finally, you should uncomment the gzip_types directive, leaving the default values in place. This will ensure that JavaScript, CSS and other file types are gzipped in addition to the HTML file type.

In order for Nginx to correctly serve PHP you also need to ensure the fastcgi_param SCRIPT_FILENAME directive is set, otherwise you will receive a blank white screen when accessing any PHP scripts. Open the fastcgi_params file:

sudo nano /etc/nginx/fastcgi_params

Ensure the following directive exists, if not add it to the file:

fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;

That’s the basic Nginx configuration dealt with, hit CTRL X followed by Y to save the changes. For the changes to take effect you must restart Nginx, however before doing so you should ensure that the configuration file contains no errors by issuing the following command:

sudo nginx -t

If everything looks OK, go ahead and restart Nginx:

sudo service nginx restart

Restart nginx

Catch All Server Block

Currently, when you visit the server’s FQDN in a web browser you should see the Nginx welcome page. However, this usually isn’t the desired behavior. It would be better if the server returned an empty response for unknown domain names.

Begin by removing the following two files:

sudo rm /etc/nginx/sites-available/default
sudo rm /etc/nginx/sites-enabled/default

Now you need to add a catch all block to the Nginx configuration. Using nano, open the nginx.conf file:

sudo nano /etc/nginx/nginx.conf

Towards the bottom of the file you’ll find a line that reads:

include /etc/nginx/sites-enabled/*;

Underneath that, add the following block:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;
}

Hit CTRL X followed by Y to save the changes and then test the Nginx configuration:

sudo nginx -t

If everything looks good, restart Nginx:

sudo service nginx restart

Now when you visit the FQDN you should receive an error.

Browser error

Here’s my final nginx.conf file, after applying all of the above changes. I have removed the mail block, as this isn’t something that’s commonly used.

user ashley;
worker_processes 1;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 1024;
    multi_accept on;
}

http {

    ##
    # Basic Settings
    ##

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 15;
    types_hash_max_size 2048;
    server_tokens off;
    client_max_body_size 64m;

    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    ##
    # SSL Settings
    ##

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

    ##
    # Logging Settings
    ##

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    ##
    # Gzip Settings
    ##

    gzip on;

    # gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 5;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    ##
    # Virtual Host Configs
    ##

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;

    server {
        listen 80 default_server;
        listen [::]:80 default_server;
        server_name _;
        return 444;
    }
}

Download the complete set of Nginx config files

Install PHP 7.4

Just like Nginx, the official Ubuntu package repository does contain PHP packages, however, they are not the most up-to-date, so I use one maintained by Ondřej Surý. Add the repository and update the package lists like you did for Nginx:

sudo add-apt-repository ppa:ondrej/php -y
sudo apt update

Then install PHP 7.4:

sudo apt install php7.4-fpm php7.4-common php7.4-mysql \
php7.4-xml php7.4-xmlrpc php7.4-curl php7.4-gd \
php7.4-imagick php7.4-cli php7.4-dev php7.4-imap \
php7.4-mbstring php7.4-opcache php7.4-redis \
php7.4-soap php7.4-zip -y

After the installation has completed, confirm that PHP has installed correctly:

ashley@server:~$ php-fpm7.4 -v
PHP 7.4.5 (fpm-fcgi) (built: Apr 28 2020 14:49:23)
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.5, Copyright (c), by Zend Technologies

Configure PHP 7.4 and PHP-FPM

Now that PHP has installed you need to configure the user and group that the service will run under. As mentioned in the series introduction, this setup does not provide security isolation between sites by configuring PHP pools, so we will run a single PHP pool under your user account. If security isolation between sites is required we do not recommend that you use this approach and instead use SpinupWP to provision your servers.

Open the default pool configuration file:

sudo nano /etc/php/7.4/fpm/pool.d/www.conf

Change the following lines, replacing www-data with your username:

user = www-data
group = www-data

listen.owner = www-data
listen.group = www-data

Next, you should adjust your php.ini file to increase the WordPress maximum upload size. Both this and the client_max_body_size directive within Nginx must be changed in order for the new maximum upload limit to take effect. Open your php.ini file:

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

Change the following lines to match the value you assigned to the client_max_body_size directive when configuring Nginx:

upload_max_filesize = 2M
post_max_size = 8M

Hit CTRL X and Y to save the configuration. Before restarting PHP, check that the configuration file syntax is correct:

ashley@server:~$ sudo php-fpm7.4 -t
[09-May-2020 09:07:00] NOTICE: configuration file /etc/php/7.4/fpm/php-fpm.conf test is successful

If the configuration test was successful, restart PHP using the following command:

sudo service php7.4-fpm restart

Now that Nginx and PHP have been installed, you can confirm that they are both running under the correct user by issuing the htop command:

htop

If you hit SHIFT M the output will be arranged by memory usage which should bring the nginx and php-fpm7.4 processes into view. You should notice a few occurrences of both nginx and php-fpm.

Both processes will have one instance running under the root user (this is the main process that spawns each worker) and the remainder should be running under the username you specified.

If not, go back and check the configuration, and ensure that you have restarted both the Nginx and PHP-FPM services.

Installing WP-CLI

If you have never used WP-CLI before, it’s a command line tool for managing WordPress installations, and greatly simplifies the process of downloading and installing WordPress (plus many other tasks).

Navigate to your home directory:

cd ~/

Using cURL, download WP-CLI:

curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar

You can then check that it works be issuing:

php wp-cli.phar --info

The command should output information about your current PHP version and a few other details.

In order to access the command line tool by simply typing wp you need to move it into your PATH and ensure that it has execute permissions:

chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp

You can now access the WP-CLI tool by typing wp.

WP-CLI Output

MariaDB

The final package to install is MariaDB, which is a drop-in replacement for MySQL. I choose MariaDB because it offers more features and speed improvements over MySQL. It’s also fully open source and has been adopted by a number of large companies. Again, the official Ubuntu package repository does contain a MariaDB package, but it’s not the most recent stable release.

Add the repository and update the package lists like you did for Nginx and PHP-FPM:

sudo apt-get install software-properties-common
sudo apt-key adv --fetch-keys 'https://mariadb.org/mariadb_release_signing_key.asc'
sudo add-apt-repository 'deb [arch=amd64,arm64,ppc64el] http://mirrors.up.pt/pub/mariadb/repo/10.4/ubuntu focal main'

To install MariaDB, issue the following command:

sudo apt install mariadb-server -y

Once MariaDB has installed, you can secure MariaDB. Luckily, there’s a built-in script which will prompt you to change a few insecure defaults:

sudo mysql_secure_installation

Follow the instructions and answer the questions. Here are my answers:
ashley@server:~$ sudo mysql_secure_installation

NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
      SERVERS IN PRODUCTION USE!  PLEASE READ EACH STEP CAREFULLY!

In order to log into MariaDB to secure it, we'll need the current
password for the root user. If you've just installed MariaDB, and
haven't set the root password yet, you should just press enter here.

Enter current password for root (enter for none): 
OK, successfully used password, moving on...

Setting the root password or using the unix_socket ensures that nobody
can log into the MariaDB root user without the proper authorisation.

You already have your root account protected, so you can safely answer 'n'.

Switch to unix_socket authentication [Y/n] Y
Enabled successfully!
Reloading privilege tables..
 ... Success!


You already have your root account protected, so you can safely answer 'n'.

Change the root password? [Y/n] Y
New password: 
Re-enter new password: 
Password updated successfully!
Reloading privilege tables..
 ... Success!

By default, a MariaDB installation has an anonymous user, allowing anyone
to log into MariaDB 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? [Y/n] Y
 ... Success!

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? [Y/n] Y
 ... Success!

By default, MariaDB 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? [Y/n] Y
 - Dropping test database...
 ... Success!
 - Removing privileges on test database...
 ... Success!

Reloading the privilege tables will ensure that all changes made so far
will take effect immediately.

Reload privilege tables now? [Y/n] Y
 ... Success!

Cleaning up...

All done!  If you've completed all of the above steps, your MariaDB
installation should now be secure.

Thanks for using MariaDB!

That’s all for this chapter. In the next chapter I will guide you through the process of setting up your first WordPress site and how to manage multiple WordPress installs.


Subscribe to get the latest news, updates and optimizations in performance and security.

Thanks for subscribing 👍

To receive awesome stuff, you'll need to head to your inbox and click on the verification link we sent you.
Make sure to check your "spam" folder or your "promotions" tab (if you have Gmail).
If you're still having trouble, then messages us at sudo@spinupwp.com.