Shortdark Software Development

PHP 8 and Nginx on Ubuntu 20.04 LTS Tutorial

Development17th Oct 2021.Time to read: 11 mins

NginxPHPMySQLWordpressDebianLinuxCommand LineTutorial

I needed to set up instances running PHP 8 a few times to test that my WordPress plugins and composer packages work on PHP 8. I also have some older sites that are going to be put on AWS Elastic Beanstalk, but I want to get them working remotely on PHP 8 before I do. While I have other websites that use devops pipelines for deployment, I needed to do the manual process a few times, so I've recorded it here. Apart from anything, I believe it is good to understand the manual process of setting up PHP and Nginx, even if you will eventually be using a DevOps process or a container such as Docker.

At the time of writing, PHP 8 is not yet available on the default Ubuntu 20.04 repositories (nor Debian, for that matter). Also, at the time of writing, there were some minor issues with MySQL, I'm sure they were very minor issues, but I went with MariaDB to have a process that was as simple as possible, and because I tend to use MariaDB anyway.

The first part of this guide is based on How to Install PHP 8 for Apache and NGINX on Ubuntu.

First Things First

Login into instance via SSH and...

sudo apt-get update
sudo apt -y upgrade

Before we forget, let's add this line to "/etc/hosts" file with the correct IP address and domain of your website...

While you're doing this you may as well also change the hostname, "/etc/hostname", to whatever you want. Use hyphens or underscores instead of periods...


Restart the instance to load your new hostname. I like to either exit from the SSH session before rebooting the instance in the GUI, or reboot from the command line.

sudo reboot now


After the reboot log back into the instance via SSH. List the available PHP versions you can install from the repos you currently have installed...

sudo apt list php

Listing... Done php/focal 2:7.4+75 all

For PHP 8 these two repos are currently recommended...

sudo add-apt-repository ppa:ondrej/php
sudo add-apt-repository ppa:ondrej/nginx

Check the available versions of PHP again. Now, PHP 8.0 should be available.

sudo apt list php

Listing... Done php/focal all N: There is 1 additional version. Please use the '-a' switch to see it

Assuming PHP 8 is the default, you continue with...

sudo apt install php-fpm
sudo apt install php-common php-mysql php-cgi php-mbstring php-curl php-gd php-xml php-xmlrpc php-pear php-zip
php -v

Which should give you...

PHP 8.0.11 (cli) (built: Sep 23 2021 21:26:24) ( NTS )

Copyright (c) The PHP Group

Zend Engine v4.0.11, Copyright (c) Zend Technologies

with Zend OPcache v8.0.11, Copyright (c), by Zend Technologies


sudo systemctl status php8.0-fpm


● php8.0-fpm.service - The PHP 8.0 FastCGI Process Manager
Loaded: loaded (/lib/systemd/system/php8.0-fpm.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2021-10-17 16:51:16 UTC; 59s ago
Docs: man:php-fpm8.0(8)
Process: 58123 ExecStartPost=/usr/lib/php/php-fpm-socket-helper install /run/php/php-fpm.sock /etc/php/8.0/fpm/pool.d/www.conf 80 (>
Main PID: 58110 (php-fpm8.0)
Status: "Processes active: 0, idle: 2, Requests: 0, slow: 0, Traffic: 0req/sec"
Tasks: 3 (limit: 560)
Memory: 16.0M
CGroup: /system.slice/php8.0-fpm.service
├─58110 php-fpm: master process (/etc/php/8.0/fpm/php-fpm.conf)
├─58121 php-fpm: pool www
└─58122 php-fpm: pool www

Oct 17 16:51:16 ip-172-26-4-201 systemd[1]: php8.0-fpm.service: Succeeded.
Oct 17 16:51:16 ip-172-26-4-201 systemd[1]: Stopped The PHP 8.0 FastCGI Process Manager.
Oct 17 16:51:16 ip-172-26-4-201 systemd[1]: Starting The PHP 8.0 FastCGI Process Manager...
Oct 17 16:51:16 ip-172-26-4-201 systemd[1]: Started The PHP 8.0 FastCGI Process Manager.
lines 1-18/18 (END)

Instead of making a file with phpinfo(); in it that I may forget to delete, I'd rather run the php -i command. If you are looking for the php.ini file, something like this gives nice colour highlighting...

php -i | grep ini

This should show you where the php config files are located, should you need to modify them.


Based on Installing and Using NGINX on Ubuntu 20.04 with WordPress config from How to Install WordPress Using Nginx on Ubuntu 18.04...

sudo apt install nginx
sudo service nginx status
sudo unlink /etc/nginx/sites-enabled/default
sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/
sudo ln -s /etc/nginx/sites-available/ /etc/nginx/sites-enabled/
sudo nano /etc/nginx/sites-available/

For WordPress the Nginx config "/etc/nginx/sites-available/" might be like this (not using SSL)...

server {
    listen 80;
    listen [::]:80;

    root /var/www/;
    index index.php index.html index.htm;

    location / {
        try_files $uri $uri/ /index.php?$args;
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires max;
        log_not_found off;

For a website that is not WordPress, and that will use composer packages, you might want the "root" line to be like below. The "composer.json" and "vendor" would live outside of the "html" directory in the ""...

root /var/www/;
  1. Check the config.
  2. Tell nginx to start after each reboot.
  3. (Re)start Nginx.
  4. Check status.
sudo nginx -t
sudo systemctl enable nginx
sudo service nginx restart
sudo service nginx status
sudo systemctl enable nginx

The last line should enable nginx to restart after a reboot.

Point the A record(s) of your domain at the IP, e.g.

Put a dummy "index.php" file in "/var/www/" and check that it works...

cd /var/www
sudo nano index.php

An easy thing to use for a dummy page is the current date and time...

<?php echo date('Y-m-d H:i:s');

If you've pointed the A record at the domain, going to the domain in a browser now should show you the date on the server. If the timezone the server is using is not the one you wish to use now is as good a time as any to change it.

Show the date, time and timezone on the commandline, then change the timezone if you wish...

sudo dpkg-reconfigure tzdata

At this stage, you know that both PHP and Nginx are working. It might be a good time to reboot the instance to make sure Nginx restarts when the instance starts running again.


From Install Apache, MySQL and PHP 8 (LAMP) on Ubuntu 20.04 LTS

If you are using a separate database instance, you only need to install the mariadb client...

sudo apt install mariadb-client -y

If you want a local database on the instance, install and configure the server too...

sudo apt install mariadb-server mariadb-client -y
sudo systemctl enable mariadb.service
sudo mysql_secure_installation

Which gives...


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
you haven't set the root password yet, the password will be blank,
so you should just press enter here.

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

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

Set root password? [Y/n]
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]
... 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]
... 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]
- Dropping test database...
- Removing privileges on test database...

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

Reload privilege tables now? [Y/n]
... 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!

Creating Database and Database User

From Moving a WordPress Blog to Different Hosting

create database name_of_new_database;
CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON name_of_new_database . * TO 'newuser'@'localhost';

Error Logs

One of the first things I look for, because I know I'll need them, are the error logs. We're only using the one domain for this instance so we did not specify a log file in the nginx config. Nginx will create it automatically in the default location. To find where the file is (assuming an error has been created) do a find command...

sudo find / -name error.log

You can then use the linux "cat" or "tail" commands to view the errors.

cat /var/log/nginx/error.log


Install WordPress...

cd /var/www
sudo wget
sudo tar xfz latest.tar.gz
sudo mv wordpress
chmod g+w -R /var/www/

Create the database...

sudo mysql -uroot
create database DBNAME;
CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON DBNAME . * TO 'newuser'@'localhost';
SELECT Host, User FROM mysql.user;

Install plugins etc with WP-CLI from WP-CLI...

sudo apt install curl
curl -O
php wp-cli.phar --info
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
wp --info
cd /var/www/
wp plugin install wp-plugin-1 wp-plugin-2 wp-plugin-3 wp-plugin-4 wp-plugin-5 --activate
wp plugin install wordpress-importer --activate

Import exported XML posts/categories/etc with wordpress-importer.

Deal with the plugins that need re-configuring or have broken.


To find out which user you are currently logged in as use...


Or, the commandline prefix will probably also have this information before the hostname. For example...


From The right folder permissions for a website on a Linux server...

sudo chown -R ubuntu:www-data /var/www/
chmod -R 750 /var/www/
chmod g+s /var/www/

If the web server needs to write to a directory...

chmod g+w -R /var/www/


If we want to use composer, our root should be pointed at another directory, e.g. "html" within the "" directory. Go to the directory where you'll be storing your composer.json and make sure the composer.json exists on the server and do...

cd /var/www/
php -r "copy('', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === '906a84df04cea2aa72f40b5f787e49f22d4c2f19492ac310e8cba5b96ac8b64115ac402c8cd292b8a03482574915d1a8') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"
sudo mv composer.phar /usr/local/bin/composer
composer install


The current Laravel installation page is Laravel 8.x installation.

composer create-project laravel/laravel
sudo chgrp -R www-data storage


curl -sL | sudo -E bash -
sudo apt install nodejs
node -v
npm -v
npm install

Check Nginx Starts after Reboot

The following command should mean that Nginx restarts automatically after a reboot, but it's a good idea to check while you finish off the setting up and checking stage...

sudo systemctl enable nginx
sudo reboot now

sudo service nginx status


It's a pretty straightforward process, especially after four or five times. The main way that different websites will differ is probably the Nginx config. Having the database on a separate instance makes the process seem much simpler. Setting up old websites from previous versions of PHP on remote "dev" or "staging" servers is a good idea before upgrading the production server or drastically changing the deployment process.

Previous: Case Study: Composer PackageNext: Setting up an AWS S3 Static Website with CloudFlare