In this tutorial we will learn how to install WordPress on your Ubuntu 22.04 server with LEMP (Linux, Nginx, MariaDB, and PHP) stack. From first launch to ready-to-browse.
In a Linux server there are various applications we can use as a web server. The two most popular web servers in the market are the Apache HTTP Server and NGINX .
While there are others, these two dominate almost all of the market. In this tutorial we will focus on learning how to serve WordPress using NGINX, using the combination of applications commonly known in acronyms as the LEMP stack.
[powerkit_alert type=”info” dismissible=”false” multiline=”false”]You can also open source software to more easily install WordPress on Ubuntu using the LEMP stack. We have covered two such software, WordOps and SlickStack. [/powerkit_alert]
Table of Contents
- What is LEMP stack?
- Why are we using these applications specifically?
- Why serve WordPress with NGINX
- Installing Ubuntu 22.04
- Preparing the Ubuntu Environment for a LEMP stack
- Creating a User in Ubuntu
- Granting User Privileges in Ubuntu
- Installing NGINX
- Installing MySQL
- Installing PHP
- Install WordPress with LEMP on Ubuntu 22.04
- Conclusion
What is LEMP stack?
LEMP is a combination of applications used together to serve web pages.
This combination has everything necessary for you to be able to host a website publicly on the internet.
LEMP is a variation of another popular combination called LAMP stack. LEMP is an acronym and the letters:
- L stands for Linux, in this case we are going to be using Ubuntu 22.04
- E stands for NGINX (NGINX is pronounced Engine X in English thus making sense of the E acronym)
- M stands for MySQL, a database application
- P is for PHP, a programming language.
Our specific application versions we will be using in this example is Ubuntu 22.04 as our Linux operating system. NGINX version 1.2 as our web serving application. MySQL 8.3 as our database management system. And PHP-FPM 8.1 as our PHP process manager.
Why are we using these applications specifically?
For MySQL and PHP it’s always best practice to use the latest version of the applications, because of security reasons and to take advantage of the latest features.
The open source free version of NGINX has two versions available, the stable and mainline versions, version 1.2 and 1.21 respectively. For NGINX we choose the stable version for stability reasons, the stable version receives new features later than the mainline version but there are less chances of finding bugs. The mainline version is more similar to a beta version although more reliable than a regular beta application.
It’s not necessary to always have the latest version, I know that it can be very appealing to always choose the latest but in live production, stability is king.
So we should always resist the temptation to install the latest version or upgrade as soon as a new version comes out. In a general sense in a Linux server, upgrading major versions of software shouldn’t be performed. Minor updates, what in Linux are called point release updates, are okay to do.
But major version upgrades are extremely discouraged, as every single major change has the possibility to break the live environment, and in a web serving scenario like in this tutorial that means it could break your website.
Finally the main component of our LEMP stack, the L of our Linux operating system, is going to be Ubuntu 22.04 LTS.
The version 22.04 of Ubuntu is an LTS release which stands for Long Term Support.
Canonical, the company behind Ubuntu, releases yearly updates to its operating system. Every two years that release is an LTS release. LTS versions are designed to be used in live production environments as they are destined to be supported for 5 years, which is a good enough long time to run a production web serving server.
It’s important and recommended to run WordPress on a LEMP stack on an LTS Ubuntu operating system because once you have a working, secured server you don’t want to be changing and upgrading components as they fall out of the support period.
So choosing the latest LTS release is the recommended practice even if it came out a year prior. The version we will be installing came out on April 21, 2022.
Why serve WordPress with NGINX
NGINX was written by Igor Sysoev in 2004 out of frustration with the web servers of the time.
Those servers weren’t very well prepared to handle thousands of concurrent connections.
To have a website with millions of visitors per month was out of the reach of most people financially. Only the big enterprises had the budget to actually run huge websites.
What was available to regular people was good to serve only a small amount of concurrent visitors.
Then Igor innovated the market by releasing the first version of NGINX designed to have an event-driven asynchronous architecture well capable to serve thousands of visitors at the same time.
NGINX quickly got adopted by professional system administrators to run most enterprise websites.
As the web matured through the years to serve more dynamic, API driven, media heavy content, NGINX kept pace with the changes and it’s now considered the king of web servers. It is very well capable of handling advanced new web technologies like WebSockets, HTTP/2, compressions, and video streaming, stuff that makes up most of the modern web.
Thus when choosing to host WordPress we will want to host with the best web serving application we can install on our operating system.
Now we know the basis of what a LEMP stack is and why we choose to use it to run our WordPress website. Now let’s get to installing all the stack.
Installing Ubuntu 22.04
Installing Ubuntu 22.04 itself is not something we have to worry about on most web hosts. The process of installing Ubuntu is only needed when renting a bare metal server that has absolutely nothing installed on it prior.
Most of the VPS and web hosting cloud companies that the majority of people will contract already have an image of the Ubuntu OS and when ordering service they pre-install that image on it for you.
It’s an important thing to note because newer Linux and cloud computing users get confused trying to understand how to install Ubuntu on a server that it already is an Ubuntu installation, this distinction is not so well explained on most guides.
So in this tutorial we assume that you will get a regular pre-installed Ubuntu server from a popular cloud computing company, and thus the first leg of our LEMP stack is already done for us.
In this example we ssh login to our Ubuntu server we named “lempstacked”. lempstacked is a server that is not known to our local computer, so our local computer ask if we trust it, we input yes
and then we are asked for our password, once we input that in we are officially connected through the ssh protocol and have full control of the Ubuntu 22.04 server.
To verify we check the release information and we see that it is indeed Ubuntu 22.04 LTS that we are running.
ssh lempstacked
And after you’ve logged into your server:
lsb_release -a
We can do absolutely the same identical step as using the username and IP address of the server, in this case we are logging in as the root user of the server with the IP address 78.141.210.222.
If you don’t have an alias (nickname) set up for your server, you can just login using the following syntax:
ssh root@your_server_ip
In my case I’ll run:
ssh [email protected]
The L in our LEMP stack is here and ready so we can proceed.
Preparing the Ubuntu Environment for a LEMP stack
[powerkit_alert type=”info” dismissible=”false” multiline=”false”]On operating as the root user
As you have noticed, I have logged in as the root user to our brand new server.
That is fine for the first time and setting up but it’s commonly recommended that in Linux we work as root as little as possible.
That is because the root user has omnipotence in Linux, it has the power to create and modify, and most scarily to destroy and delete everything in the system.
There are times that by accident we can alter or delete something necessary to the functioning of the server and render it inoperable.
That’s why we want to create another user to work as, with enough privileges to get our job done but not so much to disable or handicap our server irreparably.[/powerkit_alert]
Creating a User in Ubuntu
Now we will create a user account that will manage our LEMP stack. Connected as root through SSH we will create the user with the following command.
adduser tupacshakur
Here we created a user tupacshakur and we gave it a strong random password. As a security feature of Ubuntu it is to not allow easy passwords like those based on dictionary words or simplistic ones like passw0rd. Then we skipped the other unnecessary questions and confirmed that everything was correct.
Granting User Privileges in Ubuntu
Right now we have the user tupacshakur which is just a regular user.
Regular users don’t have much of the admin rights that are necessary to run our stack. So we will grant it some privileges. These privileges work by using a special command before executing regular commands in the command line when connected as this user.
The special command is called sudo
.
When our user tupacshakur tries to run a command that only a server admin can perform such as the reboot
command to restart the server, Ubuntu 22.04 will not allow him. He will only be allowed to execute this if he first adds the magic word before it. If tupacshakur writes sudo reboot
only then will the server restart.
As you can see, this sudo power is very strong, almost like being root. So we grant this power only to computer users that absolutely need it. This new breed of user in the computer is called a superuser.
Let’s make tupacshakur a sudoer by putting him in the user group called sudo group. The rights of the sudo users are set in the configuration to this group.
usermod -aG sudo tupacshakur
Using the usermod utility we told it to append the user tupacshakur to the user group sudo.
The -a
option told it to append the user to the sudo group. Without the -a
option the default group of the user would change. That would be bad. That is because user groups are set to have common rights. The rights of sudo users are to grant only limited, highly powerful rights.
This sudo group doesn’t have all the complete, extensive, functioning rights a user needs to be a user in this computer. Meaning if we change the user group of tupacshakur it would become a broken user. Again, there are a lot of guides out there that recommend changing a group instead of appending.
You should always append a user to the sudo group with the -a
option. The -G
option simply tells that the user settings will be altered.
Installing NGINX
Now we will install the NGINX web server, the application and configuration that actually publishes the website to the public web.
We’re going to grab NGINX from the apt repository.
[powerkit_alert type=”info” dismissible=”false” multiline=”false”]apt stands for Advanced Packaging Tool and is a tool that manages packages in our computer.
All can be done from apt, from downloading, installing and purging applications. Canonical (the company behind Ubuntu) maintains the repository on the applications that we can download from the Ubuntu apt.
In other words, consider apt to be like an app store for our Ubuntu machine that can be used on the command line.[/powerkit_alert]
Before proceeding, let’s exit our root user and let’s connect as tupacshakur. We’re going to install everything from our new user.
And now we are connected as the tupacshakur user.
That is because of the magical power of fzf, a utility to improve your workflow on the command line. Learn more about fzf at How to Use fzf Command-Line Fuzzy Finder.
Before we download NGINX we have to refresh the apt list of available apps for download to make sure we get the latest version available from the apt repository by running the following command.
sudo apt update
And then, if there are any updates available, follow by.
sudo apt upgrade
For us there weren’t updates available so we are good to go.
sudo
it asked us for our password? That is because, when connecting as a sudo user, on the first sudo command it always needs to verify your identity with your user password.
On subsequent uses of sudo commands in the same session, it will not ask you for it. Once disconnected, in the next session it will ask you again.
Now let’s download and install NGINX on our server.
sudo apt install nginx
Immediately after running that command, apt tells us which additional packages need to be installed and which additional packages are optionally available to be installed.
Proceeding with the installation, it then asks us which running services on Ubuntu we want to restart to get them to recognize our new installed NGINX toy. It pre-seletcs which ones are necessary or recommended, we just leave it as is to not complicate the installation process and press enter on OK.
And voilà, NGINX is installed.
Your server will receive hundreds or even thousands of connection attempts daily from bots that are searching for unsecure servers.
Never run a server exposed to the internet without a firewall, never disable a firewall for prolonged periods of time.
The firewall is an important tool for keeping your server secure, and its basic function from the outside is to block unwanted connections, but from the inside the basic function is to stop any application running on your machine from accidentally exposing itself to the internet.
The firewall will control what is accessible from the public internet.
There are various firewalls that you can install on your Ubuntu 22.04 machine but the one that comes by default is the ufw firewall. We need to tell ufw to allow NGINX to expose itself to the internet so that NGINX can perform its function to act as a web server.
sudo ufw allow 'Nginx HTTP'
ufw then updates its rules to allow NGINX to be exposed to the internet. We repeat the same command but with HTTPS to allow secure connections.
sudo ufw allow 'Nginx HTTPS'
[powerkit_alert type=”info” dismissible=”false” multiline=”false”]Nginx HTTP and Nginx HTTPS that we just used are called application profiles, which are essentially sets of predefined rules for a specific application. These application profiles are available for many of the most common applications. Application profiles for specific software can be found in /etc/ufw/applications.d/.[/powerkit_alert]
Then we verify if the rules are applied by running the following command.
sudo ufw status verbose
This tells us that port 80 and port 443 are open and assigned to NGINX to allow connections into our machine from any IP in the world. It tells us the same for the v6 (IPv6) version.
Port 80 is for unencrypted website visits made to http and port 443 are for secure connections made to https.
The v6 part is to accept IPv6 connections, IPv6 is a protocol that allows internet service providers a much greater amount of combinations of IP addresses. Each internet user is assigned an IP address by their provider and with billions of devices connected to the internet around the world lots of companies are using the IPv6 standard. NGINX serves those visitors using IPv6 natively.
Now that we modified our firewall rules to allow NGINX to be exposed to the internet, we check if it’s live by visiting the IP address of our server on a browser.
http://78.141.210.222
Success! The web page tells us that if we see that page, then it means the NGINX web server is successfully installed and working.
Installing MySQL
Now that we have a working web server we will install MySQL, this application will serve us as the relational database that WordPress will utilize to save its structure and content in.
Here first we install MySQL with the command.
sudo apt install mysql-server
Again apt asks us for our sudo password as we invoked it to be able to install.
Then it proceeded to tell us how many additional packages it needed to install as dependencies of MySQL. It told us 28 new dependencies will be installed with a total size of 240 MB. We agreed to it and it installed them, now MySQL is installed. Lastly it asked us which services running on our Ubuntu machine currently needed to be restarted so changes could take effect, which we proceeded to skip.
Secure MySQL With the Secure Installation Script
MySQL can be a complex piece of software. A misconfiguration can allow our server to be hacked.
So right away we told it to run a script that comes bundled with MySQL to secure our server. It’s always best practice to perform this task, or the tasks this script does manually. The script is run by the following command.
sudo mysql_secure_installation
First of all this script connects us to MySQL. The script asks us a series of questions that will make our installation more secure. Unless you have a valid reason and are experienced enough, the answers to these questions should always be yes.
The first question is about VALIDATE PASSWORD COMPONENT, this is a component that prevents users from using simple passwords like dictionary words and simple combinations like passw0rd2.
When setting up a new database password this component will check for the password strength. This, 95% of times, will be great for the security of your MySQL installation but at times it can have conflict with some external web applications that access the database.
If down the road there’s a conflict that can’t be resolved because of this password checking component, then it can be disabled as needed.
The second question it asked is about the level of strength you want this check to be with three choices being LOW, MEDIUM or STRONG. I proceeded with low.
Then the script asked for a password to the root user of MySQL. Don’t confuse this with being the root user of your Ubuntu machine. This is a separate root user for MySQL only. This root user will have full admin rights to manage everything and it’s needed for the management of MySQL.
We proceeded by giving it a secure password that it accepted satisfyingly with a score of 100. A low scoring password might be insecure, so we have a chance to change it at this point if we wanted. We were satisfied with this score so we proceeded.
The third question is about disallowing root login from outside the server, remotely from another computer on the internet.
Having remote access to a database on a server is how more complex websites run their stacks, it’s common and needed when running huge websites that need multiple servers to handle hundreds of thousands of concurrent users.
But to allow the root user to be accessed remotely is considered a bad practice. It can be done but it’s not recommended for inexperienced database managers.
Just simply giving it access will make your server a big target for hack attempts and overall unwanted bot traffic. So we proceed by disallowing remote access to the root.
Next it asks if we want to reload the privileges table for changes to take effect and we do. Now that’s the end to the script and for providing some basic security to our database engine that is MySQL.
If we run the following command we can check which version it installed.
mysql -V
And we can get information about the running status with the command.
sudo service mysql status
Now we have a running MySQL instance that can be used by our WordPress application.
Installing PHP
Now we are advancing deep into the LEMP stack, and we have only one of the puzzle pieces left to configure before loading WordPress onto our server.
NGINX does not contain a native PHP processing engine like Apache does and thus we need to install an additional piece of software in order for NGINX and PHP to function together. This software is called php-fpm, which fpm
stands for fastCGI process manager, and this piece of software also needs itself a helper software of its own called php-mysql. So we will install this with the following command.
sudo apt install php-fpm php-mysql
Again apt tells us what it’s going to install and how many dependencies. 11 additional software totaling 22 MB. We agree to it and it proceeds to install, prompting us at the end if we want to restart any services in Ubuntu. We skip by pressing OK as we don’t want anything restarted.
Now we have to make a few configuration changes in order for NGINX to know how to use our PHP processor engine that we just installed.
Here we are adding a new configuration file for a new site at the location where NGINX stores them. We used sample.com as an example but you would use your own domain name in this case.
sudo nano /etc/nginx/sites-available/sample.com
We directed the nano text editor to create a new file, we pasted our configuration seen below, saved and exited.
server { listen 80; root /var/www/html; index index.php index.html index.htm index.nginx-debian.html; server_name 78.141.210.222; location / { try_files $uri $uri/ =404; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; } location ~ /\.ht { deny all; } }
Again, besides the server_name directive where we placed our IP it’s where you would write your domain name. Just the name and extension and nothing else besides the semicolon at the end. The semicolon (;
) must be kept.
For example, my server block, for bytexd.com will look something like this:
server { listen 80; root /var/www/html; index index.php index.html index.htm index.nginx-debian.html; server_name bytexd.com; ... }
After creating the server block configuration and saving it, we must now enable it. We do that by creating a symbolic link from the file we just created, which is in the sites-available directory, to the sites-enabled directory that NGINX uses.
sudo ln -s /etc/nginx/sites-available/sample.com /etc/nginx/sites-enabled/
And we unlink the default configuration so that our sample.com site takes over.
sudo unlink /etc/nginx/sites-enabled/default
Then we test if our configuration file is correct.
sudo nginx -t
It tells us that the syntax is correct and the test successful. If we misconfigured the configuration here it would output us which line of the config file is wrong. As everything is correct, we move along.
Finally we reload NGINX for all the changes to take effect.
sudo systemctl reload nginx
Now that everything is set up we will validate that NGINX can send .php
files off to our php-fpm processor.
We will do that by creating a PHP file in the public files directory.
We again sudo nano to create the file.
sudo nano /var/www/html/info.php
We pasted a little PHP script to pull the PHP setup info.
<?php phpinfo();
We open this .php
file in a browser and we get success.
Let’s remove that now, as that contains a lot of server information that shouldn’t be made public.
sudo rm info.php
And with this we have concluded the setup of our LEMP stack.
This suite of very powerful utilities known as LEMP can enable us to create any website we want and publish it to the world wide web for the world to visit.
Install WordPress with LEMP on Ubuntu 22.04
Now that we have all the components of the server ready we will install WordPress. First we will make a few preparations before downloading and setting up the application itself.
First we will log into MySQL as the root user to create a database for WordPress.
sudo mysql
This logs us in.
We know we are in MySQL because our prompt starts with mysql>
.
Then we will create a new database called wordpressworld
.
CREATE DATABASE wordpressworld DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
After we have a database we will create a database username continuing inside MySQL.
CREATE USER 'supermario'@'localhost' IDENTIFIED BY 'bowser_9Es';
This creates us a database user with the name supermario and the password bowser_9Es. Now we will grant our supermario user access to all the rights of the wordpressworld database.
GRANT ALL ON wordpressworld.* TO 'supermario'@'localhost';
Now we have a database and a user to use for WordPress. Finally we type the command inside MySQL to exit.
EXIT;
With WordPress being a monster of an application there are lots of PHP extensions that it can utilize. Let’s install the popular ones with the following command.
sudo apt install php-curl php-gd php-intl php-mbstring php-soap php-xml php-xmlrpc php-zip
After apt finishes doing its thing we have to restart our PHP-FPM procesor.
sudo systemctl restart php8.1-fpm
Now we have all the extensions we can possibly use with WordPress.
We need to go back to the NGINX server block configuration so we can add a few WordPress specific configs.
sudo nano /etc/nginx/sites-available/sample.com
And we will add a few location directives.
location = /favicon.ico { log_not_found off; access_log off; } location = /robots.txt { log_not_found off; access_log off; allow all; } location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ { expires max; log_not_found off; }
We will also adjust the try_files
location. Let’s comment out the current one and add the following line underneath it.
try_files $uri $uri/ /index.php$is_args$args;
Everything should end up looking like the below example.
Let’s again check the configuration syntax
sudo nginx -t
If everything is okay it will tell us that the syntax is ok and the test succesful. If there are errors we go back and edit the configuration. If everything is good we reload NGINX to load the new updated config.
sudo systemctl reload nginx
The prep is done and now we will proceed to install WordPress.
We will change directory to the /tmp directory. In Linux the /tmp directory is for placing stuff that you want to delete after the next boot up or in a pre-determind time, thus being called temporary.
cd /tmp
With the curl utility we will download the files of WordPress from the official WordPress website into our current /tmp direcotry.
curl -LO https://wordpress.org/latest.tar.gz
When using curl
, the -L
option tells it to follow redirects if there are any and the -O
tells it to make a copy of the file locally in our compuer.
Now we will use the tar
utility to untar the content of WordPress that we just downloaded. These are the actual files that WordPress uses.
tar xzvf latest.tar.gz
We will make a copy of the sample config file that WordPress comes with.
cp /tmp/wordpress/wp-config-sample.php /tmp/wordpress/wp-config.php
We will copy the entirity of the WordPress directory we just extracted to our public html directory.
sudo cp -a /tmp/wordpress/. /var/www/html
We can change the directory to verify that all the WordPress files are there.
cd /var/www/html && ls
It looks all okay.
We need to give ownership to a user and user group that NGINX creates, this user and group are called www-data. We will give it access to the directory where our WordPress files reside.
sudo chown -R www-data:www-data /var/www/html
Now WordPress can be managed by NGINX because it has the appropriate permissions.
We will move on to the WordPress application configuration. In the wp-config.php file we need to add certain salt keys to make things secure.
These keys are totally random and should never be copied from any tutorial or guide that you find online. It needs to be unique only to your installation. Luckily for us WordPress makes it easy to get these salt keys on the command line with the following command.
curl -s https://api.wordpress.org/secret-key/1.1/salt/
We copy these keys and then proceed to open our WP config file.
sudo nano /var/www/html/wp-config.php
Inside the config in the section of the salt keys you paste the keys that we just copy like this example.
Then we provide the database name, database user and database password. In our example case that is wordpressworld, supermario, and bowser_9Es respectively.
Below the database connection information, anywhere paste the following code.
define( 'FS_METHOD', 'direct' );
With that setting WordPress is allowed to write directly to the file system. For example when uploading a new picture or plugin.
That is all for the wp-config.php.
We proceed to open our site in a browser. If done everything according to example you should get the WordPress setup wizard. At this point you can be confident that the installtion of WordPress was a success.
After finishing the admin setup, you can then proceed to enter the admin dashboard, at http://yoursite.com/wp-admin
or visit the front page of your website.
Conclusion
Congratulations, you successfully installed LEMP stack and the WordPress application totally from start to end. Well done!
The LEMP suite of utilities are an amazing piece of free software. It can empower us to create anything we want.
It’s powerful enough to run websites like YouTube and small enough for any hobbies to pick it up and start using in an hours’ time of dedication.
Thanks for reading, now is time for creating.
If you have any feedback or questions feel free to leave us a comment and we’ll get back to you as soon as we can.
Thanks but I get permission denied (publickey) after adduser and the sudo grant. Get also problems seting up pwd for mysql, it requests ALTER USER. I am root, I have keys no pwd to log in, have been since yesterday trying all possible solutions…
Hi! Just to be clear, you’re having these issues while still acting as root, or while acting as the sudo user you created? I’m thinking maybe there could be some permissions related issues with the newly created user.
I can try to reproduce your environment – would you say that a fresh Ubuntu 22.04 install, with password login disabled and SSH keys, be a similar environment to yours?
Sorry, only saw it now…
My instance is OCI, I installed ubuntu 22.04 on ARM64.
Downloaded keys and logged with Putty. The last time, I registered a pwd on Putty Gen before saving the private key.
What I can’t remember is if it worked well and then stopped after configuring wp, or if I never got the domain set up.
I opened requests for help in these communities: https://community.cloudflare.com/t/error-521-wordpress-cloudflare-connection/403370
https://askubuntu.com/questions/1420606/521-wordpress-cloudfalre-install
https://stackoverflow.com/questions/73141241/521-wordpress-cloudfalre-install
I don’t know why the CA came up automatically. The errors are as follow:
– Err_connection_refused (when Cloudflare was paused)
– 521 server is down (when Cloudflare was enabled on complete and full TLS)
– A no html page displaing the wp text and links content. (when Cloudflare is enabled on flex)
It would help me to get help if I could access the Ngix logs.
I am not a dev, new to linux… but I am thinking Nginx needs to know there is a CA in order for it to allow cloudflare HTTPS…?
thank you so much, It works just fine.
Glad to hear! Thanks for the follow-up!
Thanks for the post.
Are you sure the domain will allow unsecured https connection? Shouldn’t it have a config for nginx to call a certificate? I only got to set up on IP, the domain has many problems. 521, ERR_connection, ERR_QUICK_PROTOCOL…
As a sugestion, I would ask you to help up config the logs so we can diagnose what is happening, but please test it before.