Installing, Configuring, and Securing PHP 8.1 on Ubuntu 20.04

Updated on December 15, 2021
Installing, Configuring, and Securing PHP 8.1 on Ubuntu 20.04 header image

Introduction

PHP is one of the most popular web languages. Common uses include server-side scripting and automation. This guide documents the installation and configuration of PHP 8.1 FastCGI Process Manager (FPM) on a Ubuntu 20.04 server running an Nginx or Apache webserver.

Prerequisites

1. Install and Update Support Repositories

To support the installation of PHP 8.1 and ensure the most up-to-date version, add the main repository supported by one of the Ubuntu developers.

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

Nginx Repository

If you plan on using Nginx as your web server, add the Nginx specific repository:

# sudo add-apt-repository -y ppa:ondrej/nginx-mainline

Apache Repository

If you plan on using Apache as your web server, add the Apache specific repository:

# sudo add-apt-repository -y ppa:ondrej/apache2

Update the New Repositories

After adding the repositories, update the local apt sources and update any required files:

# sudo apt update -y
# sudo apt upgrade -y

2. Main Installation

Nginx Install

Install Nginx by running:

# sudo apt install -y nginx

Apache Install

Install Apache by running:

# sudo apt install -y apache2 libapache2-mod-fcgid

PHP Installation

Install PHP and various common extensions by running:

# sudo apt install -y -q php8.1-{cli,fpm,mysql,gd,soap,mbstring,bcmath,common,xml,curl,imagick}

Extra Binaries

To support PHP, install unzip and composer. Composer is an open-source PHP dependency manager.

# sudo apt install -y -q unzip
# sudo curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

Allow HTTP and HTTPS

Ubuntu 20.04 comes with UFW installed, which is a local firewall that prevents web server traffic. Allow HTTP and HTTPS traffic by running:

# sudo ufw allow http
# sudo ufw allow https

Test the Default Page

Nginx and Apache both have a default page. Open a browser and visit http://demo.example.com/ and ensure the server is running and serving HTML pages.

3. User Configuration

Most web servers have multiple sites running on them. To secure the sever effectively, every site should have its own user and group, which also helps troubleshoot and track problems. This document uses demo.example.com as the address, so create demoweb as the webserver user. The following commands add a group, add a user, and then assign the user to the group and make the group's home directory the default website directory for both Nginx and Apache.

# groupadd demoweb
# useradd -g demoweb -d /var/www/html -s /sbin/nologin demoweb

4. PHP-FPM Configuration

Create a backup copy of the default PHP-FPM configuration and then rename the original file to associate it with the web user:

# cp /etc/php/8.1/fpm/pool.d/www.conf /etc/php/8.1/fpm/pool.d/conf.default
# mv /etc/php/8.1/fpm/pool.d/www.conf /etc/php/8.1/fpm/pool.d/demoweb.conf

Change the associated user and socket associated with the pool by editing the new configuration file:

# nano /etc/php/8.1/fpm/pool.d/demoweb.conf

Change four lines:

  • Change the top line inside the brackets that sets the pool name from [www] to [demoweb]
  • Change the line user = www-data to user = demoweb
  • Change the line group = www-data to group = demoweb
  • Change the line listen = /run/php/php8.1-fpm.sock to listen = 127.0.0.1:9000

Save the file and restart the PHP-FPM service:

# service php8.1-fpm restart

4. Nginx Configuration

This section covers the configuration of Nginx. If you are using Apache, skip ahead to section 5.

Nginx Security

To help secure Nginx, add a snippets.d directory with more configurations that the webserver accesses:

# sudo mkdir /etc/nginx/snippets.d

After creating the directory, create supplemental files to secure content. Each file represents a file type or extension it blocks.

Create a file to deny .git files:

# nano /etc/nginx/snippets.d/deny-git.conf

Place the following snippet in this file:

location ~ /\.git {
    deny all;
}

Create a file preventing composer cache, JSON, and lock files:

# nano /etc/nginx/snippets.d/deny-composer.conf

Place the following snippets in this file:

location ~ /vendor/\.cache {
    deny all;
}
location ~ /(composer.json|composer.lock) {
    deny all;
}

Create a file to deny .htaccess files:

# nano /etc/nginx/snippets.d/deny-htaccess.conf

Place the following snippet in this file:

location ~ /\.ht {
    deny all;
}

Create a file to deny .env files:

# nano /etc/nginx/snippets.d/deny-env.conf

location ~ /\.env {
    deny all;
}

Create a file to deny license and readme files:

# nano /etc/nginx/snippets.d/deny-license-readme.conf

Place the following snippets in this file:

location ~ /(LICENSE.md|README.md) {
    deny all;
}

Create a file that adds secure headers to every request.

# nano /etc/nginx/snippets.d/add-headers.conf

Place the following three lines in this file:

add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";

Nginx Site Configuration File

Remove the default site configuration:

# rm /etc/nginx/sites-enabled/default

Create a new site:

# nano /etc/nginx/sites-available/demoweb

Add the following to the file (make sure to change demo.example.com to match your DNS entry):

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

        root /var/www/html;

        index index.php;

        server_name demo.example.com;

        location / {
                try_files $uri $uri/ =404;
        }

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                fastcgi_pass 127.0.0.1:9000;
        }

        error_page  404 /;

        include snippets.d/deny-git.conf;
        include snippets.d/deny-htaccess.conf;
        include snippets.d/deny-env.conf;
        include snippets.d/deny-license-readme.conf;
        include snippets.d/deny-composer.conf;
        include snippets.d/add-headers.conf;

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

}

NOTE: You could use one line include snippets.d/*.conf. However, that allows for a malicious configuration file injected and loaded erroneously. Instead, save the file and then link it to the active file:

# ln -s /etc/nginx/sites-available/demoweb /etc/nginx/sites-enabled/demoweb

After saving the supplemental files and making the site configuration changes, check the Nginx configuration by running:

# sudo nginx -t

If there are no errors, Nginx returns:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

At this point, restart Nginx by running service nginx restart.

Remove the default HTML file:

# rm /var/www/html/index.nginx-debian.html 

Create a test file

# nano /var/www/html/index.php

Put the following in the file:

<?php
phpinfo();

Nginx PHP Test

Visit http://demo.example.com/. The PHP Version Information page should display. Search for the Environment section and ensure demoweb is the associated user.

5. Apache Configuration

Enable the FPM and proxy binaries for PHP:

# sudo a2enmod actions fcgid alias proxy_fcgi
# sudo a2enconf php8.1-fpm

Apache Site Configuration File

Remove the default site configuration:

# rm /etc/apache2/sites-enabled/000-default.conf

Create a new site:

# nano /etc/apache2/sites-available/demo.conf

Add the following to the file (make sure to change demo.example.com to match your DNS entry):

<VirtualHost *:80>
    ServerName demo.example.com

    ServerSignature Off
    FileETag None

    ## Vhost docroot
    DocumentRoot "/var/www/html"

    <Directory "/var/www/html">
        Options -Indexes +FollowSymLinks +MultiViews
        AllowOverride None
        Require all granted
    </Directory>

    <DirectoryMatch "^/.*/\.git/">
        Require all denied
    </DirectoryMatch>

    <FilesMatch "^\.git">
        Require all denied
    </FilesMatch>

    <FilesMatch "^\.env">
        Require all denied
    </FilesMatch>

    <FilesMatch "^composer\.lock">
        Require all denied
    </FilesMatch>

    <FilesMatch "^composer\.json">
        Require all denied
    </FilesMatch>

    <FilesMatch "^README.md">
        Require all denied
    </FilesMatch>

    <FilesMatch \.php$>
        SetHandler "proxy:fcgi://127.0.0.1:9000"
    </FilesMatch>

    ErrorLog "/var/log/apache2/demoweb.error.log"
    ServerSignature Off
    CustomLog "/var/log/apache2/demoweb.access.log" combined

</VirtualHost>

Link it to the active file:

# ln -s /etc/apache2/sites-available/demo.conf /etc/apache2/sites-enabled/demo.conf

After saving the supplemental files and making the site configuration changes, check the Apache configuration by running:

# sudo apachectl configtest

If there are no errors, Apache returns:

Syntax OK

At this point, restart Apache by running service apache2 restart.

Remove the default HTML file:

# rm /var/www/html/index.html 

Create a test file:

# nano /var/www/html/index.php

Put the following in the file:

<?php
phpinfo();

Apache PHP Test

Visit http://demo.example.com/. The PHP Version Information page should display. Search for the Environment section and ensure demoweb is the associated user.

6. Secure the Web Server with Certbot

Install Certbot using snap:

# sudo snap install core; sudo snap refresh core
# sudo snap install --classic certbot
# sudo ln -s /snap/bin/certbot /usr/bin/certbot

Run certbot, following the prompts, to secure the webserver. Certbot requests an SSL certificate and modifies the configuration, sending all traffic to the secure site.

Conclusion

PHP is a powerful web scripting and command-line programming language. Adding composer further extends the functionality of PHP, granting access to multiple libraries, taking your applications to the next level.

Further Reading