WordPress on Ubuntu 22.04 LTS with Nginx and MySQL

WordPress on Ubuntu 22.04 LTS with Nginx and MySQL. Generated by Microsoft Designer.
Republished November 2023

This guide will take you through the steps required to install WordPress on Ubuntu 22.04 LTS with Nginx and MySQL. You will need a newly installed Ubuntu server running Ubuntu 22.04 LTS with ‘sudo‘ root privileges. If not then I would personally suggest having a look at something like Amazon LightSail which makes it easy to install a secure Linux VPS (no affiliation). WordPress is not particularly demanding and can easily run with 1GB of RAM and under 10GB hard disk for a small blog.

The post itself is the result of having to reinstall my WordPress site and deciding to take a bit more control. I had to look across multiple guides to find the particular blend of installation I was looking for. With this guide, I am hoping to not only detail the steps but also to provide background and links where you can go for more information.

Considerations

Some thoughts before we start. Firstly, as I will mention below, you need to consider if your site is going to be prefixed with a www or not. Getting this wrong or changing your mind can result in some unpleasant redirect problems. Also, consider your firewall settings for the duration of the install process. Are you going to allow people to have access while you are installing? I use my VPS firewall to only allow access from the IP address I am working on until I have finished. This only requires a small opening while I provision the certificate, but at that point, there is nothing visible. I then open the site to the world once everything is complete, especially the final securing of the WordPress section.

With the exception of WordPress, we will otherwise be using the default software versions that are available through the Ubuntu package manager.

NOTE: Throughout this guide, we will use the domain ‘example.com’ as the name of our site, please change it appropriately.

1. Ensure Ubuntu is up to date

Before installing any software on a Linux box, it is standard practice to ensure that the machine has all the latest patches in place.

sudo apt update
sudo apt upgrade

2. Installing the Nginx webserver

I choose Nginx as it offers good performance and can provide some more advanced functionality, such as proxy serving and acting as a web cache. This reduces the number of programs that I need to deal with on more complex installs which is good.

sudo apt install nginx
sudo systemctl status nginx

This results in a running Nginx service, the second line is just to check the status, you would expect to see something like:

3. Installing the MySQL database

I use MySQL as I particularly like using MySQL Workbench when I need more complex remote administration of a database. MySQL Workbench installs locally on my development machine which saves cluttering up my servers with administration tools.

sudo apt install mysql-server
sudo systemctl status mysql

MySQL is now running on the machine. The second line is just to check the status, you would expect to see something like:

Finally, we should also apply basic security settings to the MySQL install.

sudo mysql_secure_installation

This is the Secure Installation Script and it is good practice to run this on a production server. It will optionally perform several useful steps to help the security of your installation of MySQL This includes a password validation component, removing the anonymous user, blocking remote root access and deleting the test database. I generally answer ‘Y’ to all the options. Further information can be found here.

4. Install PHP

WordPress is built upon PHP and so required this language along with several modules.

sudo apt install php-fpm php-bcmath php-cli php-curl php-dev php-gd php-imagick php-imap php-intl php-mbstring php-mysql php-soap php-xml php-xmlrpc php-zip unzip -y

php -v

The last line just confirms the version installed. This will be needed later in order to locate the main php config file. The returned version information should look like:

In this case we are using version ‘8.1’ which is the number we should remember for locating the main php.ini file.

The exact list of modules tends to vary depending on where you look, but for a discussion on which modules are needed refer to here and here (the second link is old, but provides a good insight). I have attempted to include a comprehensive list to reduce any problems down the line.

5. Prepare for WordPress

We can now prepare for the installation of WordPress itself onto the machine. Some settings need to be changed or provided for each of the services we have just installed.

5.1. WordPress database & user

WordPress requires a database and database user to store its information. MySQL should also have a password for the root user. These have no relationship with your WordPress users and are not something you would need to access on a day-to-day level. As always you will want to choose secure passwords. This generally means 16 or more characters composed of numbers, symbols and mixed-case letters.

sudo mysql -u root -p
CREATE DATABASE database_name_db;
CREATE USER 'database_user'@'localhost' IDENTIFIED BY 'user_secret_password';
GRANT ALL PRIVILEGES ON database_name_db.* TO 'database_user'@'localhost';
FLUSH PRIVILEGES;
exit;

This is where you will need to provide your own specific values for database_name_db, database_user and user_secret_password.

5.2. Adjust PHP for use with WordPress

Locate and open the php.ini file. As of writing this will probably be for version 8.1, however it is possible that you are using a different one. In that instance replace the /8.1/ in the path with the version you are using.

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

Then change the following settings:

file_uploads = On
allow_url_fopen = On
upload_max_filesize = 64M 
post_max_size = 64M 
memory_limit = 128M 
max_execution_time = 30 
max_input_time = 60

This sets some reasonable values for PHP. Be aware that the upload_max_filesize and post_max_size may impact your ability to make backups of your WordPress site, as they will limit how large a file can be downloaded and uploaded. Increase accordingly, if you encounter problems.

5.3. Prepare the Ngnix server block & WordPress directory

We need somewhere to hold the WordPress files and to let Nginx know about it.

WWW or Non-WWW
You need to decide if you are going to include the WWW in you site address of not. It does not matter too much which way you go, but choose one and stick to it. Our Nginx settings will reflect this and we will force the one version for SEO purposes.

For this guide we will choose to have our site without the WWW. 

Create the directory for the site to live.

sudo mkdir /var/www/example.com/
sudo chown -R www-data:www-data /var/www/example.com

And then create a server block to let Nginx know about our new site.

sudo nano /etc/nginx/sites-available/example.com
server{
    listen 80;
    server_name example.com www.example.com;

    # Redirect www to non-www; adjust to your preference
    if ($host != example.com) {
        rewrite ^ $scheme://example.com$request_uri permanent;
    }

    root /var/www/example.com;
    index index.php;

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

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

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
    }
}

This initial config will handle the basic connection for HTTP to our WordPress site. It also handles sending the www prefix to the non-www address (or vice-versa depending on your preference). This file is more of a template into which we will use CertBot to fill the HTTPS configuration. To make the site ‘active’ we create a symbolic link to the directory of Ngnix’s enabled sites.

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

It is a good idea to have Ngnix test the configuration before we reload the server to take in the new configuration.

sudo nginx -t
sudo systemctl reload nginx

5.4. Install Let’s Encrypt and obtain a TLS Certificate

We will be using a free Let’s Encrypt TLS certificate. This is free, with the only downside being that it lasts for 3 months, but given that it is possible to automate the renewal, this is not really a problem. These days I see little reason to use anything else, especially say for a blog. Generally one requests the domain and the www. sub-domain when requesting the certificate to allow for redirects.

sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot --nginx
sudo certbot renew --dry-run

sudo nginx -t
sudo systemctl reload nginx

After answering a few questions, you should end up with a working certificate. More information about installing CertBot on Ubuntu can be found here. It will modify your Nginx configuration files to include the newly added certificates and redirect traffic through HTTPS.

Remember to test the config and restart Nginx to update the webserver.

When the time comes the certificate should be renewed automatically, however it can also be done manually if needed.

sudo certbot renew

6. Install WordPress

WordPress comes as a single archive, holding all the required files. It is then just a case of copying the contents of the folder to the desired location, making some configuration changes and then going onto the browser to complete the installation.

wget https://wordpress.org/latest.tar.gz
tar xzvf latest.tar.gz
cp wordpress/wp-config-sample.php wordpress/wp-config.php
sudo cp -a wordpress/. /var/www/example.com/
sudo chown -R www-data:www-data /var/www/example.com/*
cd /var/www/example.com/
sudo find -type d -exec chmod 755 {} \;
sudo find -type f -exec chmod 644 {} \;

This will have created a blank WordPress directory with the appropriate folder & file permissions. Next, we will modify the main config for our particular installation.

sudo nano /var/www/example.com/wp-config.php

After setting the permissions, we open the WordPress config file and enter in the database settings from section 5.1 and replace the WordPress Salt Key placeholders with real ones generated from their salts API page.

The WordPress Salt Key generator page.

Finally, open a browser to your site (https://example.com) and complete the installation as per normal. You should see the page asking to select your language. It is just standard WordPress stuff from here on in.

6.1 Troubleshooting

While everything should be working at this stage, there is potential for minor mistakes or typos to cause problems. Here are some things to consider in the case of an error:

  • Did you ensure to include your own details for the commands I have provided here? Perhaps one of my ‘example.com‘s slipped through. You can use the history command to check for this.
  • Are the ports opened with yoru firewall? You will need port 80 & 443 at the very least.
  • Did you enter the database details correctly? In the case you encountered a database error.
  • The WordPress installer will generally provide some kind of onscreen error – try searching online for that text. You may find hints of the problem ther.

Failing this check the Nginx logs to see if something is obviously wrong there. WordPress is sensitive to permission issues so that may be worth checking. Your WordPress folder and its subfolders should be owned by the user www-data and have sufficient permissions.

7. Securing WordPress

With the installation complete, the final stage is to enhance the security of your WordPress install with Nginx. As important as this is, it is also a very open-ended task with a lot of varying opinions and precise settings. For that reason, this section should only be considered a starting point.

7.1. HTTP Security Headers

The HTTP headers are part of the response that a web server sends whenever you request a page on the internet. There are many possible fields to these headers, of which a subset are known as the security headers. When properly set up, these headers can provide an extra layer of protection against a range of security vulnerabilities.

In Nginx, we can use the ‘add_header’ command to set some of these headers. This is best done from the main config file:

 sudo nano /etc/nginx/nginx.conf

Then add the following lines:

server_tokens off; # Hides the Nginx version info
add_header Content-Security-Policy "default-src 'self' https: data: 'unsafe-inline' 'unsafe-eval';" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;

Test and reload the Nginx to make these live. It should be noted that these are just one set of possible headers and should not in any way be considered definitive. Currently, they are probably good enough for most sites to start with.

sudo nginx -t
sudo systemctl reload nginx

7.2. Separate WordPress config

There are numerous WordPress specific settings that can be further made when hosted on Nginx. For the sake of clarity, I keep these in a separate ‘snippets’ file that I maintain just for WordPress. Some people like to use a global or common folder for maintaining such files, but this works for me.

Start by creating a file for these WordPress settings:

  sudo nano /etc/nginx/snippets/wordpress.conf

This file will contain all the WordPress specific snippets. For now, we will keep it relatively simple. The config was largely sourced from the official WordPress guide to Nginx. There are many resources online covering how to configure Nginx for WordPress, some of which are included for further reading at the end of this post.

#
# WordPress specific settings 
# Designed to be included in any server {} block.
#

location = /favicon.ico {
    log_not_found off;
    access_log off;
}
 
location = /robots.txt {
    allow all;
    log_not_found off;
    access_log off;
}
 
# No scripts in the uploads directory
location ~* /(?:uploads|files)/.*\.php$ {
    deny all;
}

# xmlrpc is being deprecated; block it.
location = /xmlrpc.php {
    deny all;
    access_log off;
    log_not_found off;
    return 444;
}

# hide any hidden files
location ~ /\. {
    deny all;
}

Now include this file in your site’s main handling block:

sudo nano /etc/nginx/sites-available/example.com

Make the following change after the definition of your access logs:

server{
    server_name www.example.com example.com;

    if ($host != example.com) {
        rewrite ^ $scheme://example.com$request_uri; # permanent;
    }

    root /var/www/example.com;
    index index.php;

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

    #include WordPress configs
    include snippets/wordpress.conf;

    :
    # Remainder of the file continues here
    :

Restart Nginx to make the changes live:

sudo nginx -t
sudo systemctl reload nginx

7.3. Testing your Website

Once your website is up and running, there are several free tools that can test various aspects of your website here to verify that the basics are right. This is particularly useful to perform regularly as it will also bring to light ongoing changes to security practice you may have missed.

Security Headers is an online tool that checks your HTTP headers for security issues.

SSL Server Test is a site for testing your SSL certificate configuration.

Mozilla Observatory is a site that testing a website’s overall security. This test places an emphasis on Subresource Integrity, which WordPress is not good at out of the box. For this you may consider using a plugin such as the Subresource Integrity (SRI) Manager.

Google Lighthouse is a tool for testing your website’s performance which can be run locally from Chrome.

Final Thoughts

At this point, you should now have a working WordPress installation with much greater control of the underlying server level. This will enable you to make changes that would not otherwise be possible without installing third-party plugins, for example setting the security headers as discussed above. While every effort was made, it will always be possible for bugs to have crept in, so please do let me know if there are problems or something does not make sense. Hopefully, this guide is of some help to anyone else who may be trying to install WordPress on Ubuntu 22.04 LTS with Nginx and MySQL.

If using this guide as part of a migration or restoration from a backup then for most services you should be able to simply install the relevant plugin and restore your WordPress site. However, some plugins will restore the entire WordPress directory for you, so you may need to approach section 6 differently depending on the particular plugin you are using. Given the plethora of such backup and restore plugins, I can’t really provide specific instructions here.

As I mentioned at the start, the post itself is the result of my attempt to reinstall WordPress, the first such move since I started blogging almost 2 years ago. I originally used a prebuild install of Linux but found that some parts were proving difficult to upgrade. This reinstallation has allowed me to take greater control of my WordPress installation.

This post was originally written for an earlier version of Ubuntu (20.04 LTS) and will still work for this version. The only difference will be an earlier version of PHP which will require adjusting the path for updating the PHP config. As

Originally Posted June 2021

This post originally coved an earlier Ubuntu LTS version (20.04).

Updated Sept 2021

I updated the installation of CertBot to the more current recommended method of using Snaps.

Reposted Nov 2023

In November I decided to update the post for Ubuntu 22.04, the current LTS version that people will generally be using. As I had to change the post’s URL I decided to repost the article to reflect this. The content is largely unchanged, and works much the same as when originally written, though my references may be a bit old in places.

Further reading

WordPress guide for Nginx

Nginx rules for WordPress

General WordPress Hardening

General Nginx hardening guide

Notice

The header image was AI generated by Microsoft Designer.