Tutorial: Ubuntu 18.04 LAMP Setup for WordPress

This tutorial shows you, how to setup a base ubuntu 18.04 installation as LAMP server for WordPress. It may aksi be used for other PHP applications, but the tutorial was written with WordPress in mind.

Enable Sudo and Disable Root Login

Start with adding a user:

adduser my_user

Add user to sudo group:

usermod -a -G sudo my_user

If you want to authenticate using ssh keys, run this command from your client (where the ssh key is stored) – replace my_user and

ssh-copy-id my_user@

Now we have to disable SSH login for the user root. Edit /etc/ssh/sshd_config:

nano /etc/ssh/sshd_config

And set PermitRootLogin to no:

PermitRootLogin no

Now, restart SSH:

sudo service ssh restart

End the current ssh connection by typing exit and reconnect as the newly created user.

ssh my_user@my_server.example.com

Set the Hostname

To set the hostname, open /etc/hostname:

sudo nano /etc/hostname

And set it to the FQDN:


Update the System

Update the list of available packages:

sudo apt-get update

And install the available updates:

sudo apt-get upgrade -y

Perform a reboot:

sudo reboot

Install Apache With LetsEncrypt

Install the apache webserver:

sudo apt install apache2 -y

Check, if the webserver is correctly installed and running by typing:

sudo systemctl status apache2

The output should be something like that:

● apache2.service - The Apache HTTP Server
   Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
  Drop-In: /lib/systemd/system/apache2.service.d
   Active: active (running) since Thu 2019-08-29 13:42:14 CEST; 8s ago
 Main PID: 2896 (apache2)
    Tasks: 55 (limit: 2299)
   CGroup: /system.slice/apache2.service
           ├─2896 /usr/sbin/apache2 -k start
           ├─2898 /usr/sbin/apache2 -k start
           └─2899 /usr/sbin/apache2 -k start

Aug 29 13:42:14 my_server.example.com systemd[1]: Starting The Apache HTTP Server...
Aug 29 13:42:14 my_server.example.com systemd[1]: Started The Apache HTTP Server.

Enable apache modules:

sudo a2enmod ssl
sudo a2enmod headers
sudo a2enmod rewrite
sudo a2enmod http2
sudo a2enmod expires
sudo a2enmod deflate
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event

To increase the apache security, we want to hide some informations and disable some features. So, create a configuration file:

sudo nano /etc/apache2/conf-available/zzz-custom.conf

And enter:

ServerTokens Prod
ServerSignature Off
TraceEnable Off
Options all -Indexes
Header unset ETag
Header always unset X-Powered-By
FileETag None

Enable that configuration:

sudo a2enconf zzz-custom.conf

And restart apache:

sudo systemctl restart apache2

Configure LetsEncrypt

Install certbot:

sudo apt install certbot -y

Generate a certificate (may take some seconds):

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

Create a shared /var/lib/letsencrypt for “.well-known/acme-challenge”`:

sudo mkdir -p /var/lib/letsencrypt/.well-known
sudo chgrp www-data /var/lib/letsencrypt
sudo chmod g+s /var/lib/letsencrypt

Create letsencrypt.conf:

sudo nano /etc/apache2/conf-available/letsencrypt.conf

With this content:

Alias /.well-known/acme-challenge/ "/var/lib/letsencrypt/.well-known/acme-challenge/"
<Directory "/var/lib/letsencrypt/">
    AllowOverride None
    Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
    Require method GET POST OPTIONS

Create ssl-params.conf:

sudo nano /etc/apache2/conf-available/ssl-params.conf

With this content:

SSLProtocol All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
SSLHonorCipherOrder On
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set X-Frame-Options sameorigin
Header always set X-Content-Type-Options nosniff
# Requires Apache >= 2.4
SSLCompression off
SSLUseStapling on
SSLStaplingCache "shmcb:logs/stapling-cache(150000)"
# Requires Apache >= 2.4.11
SSLSessionTickets Off

SSLOpenSSLConfCmd DHParameters "/etc/ssl/certs/dhparam.pem"

Enable the newly created configuration files:

sudo a2enconf letsencrypt
sudo a2enconf ssl-params

Reload the apache configuration:

sudo systemctl reload apache2

Get the LetsEncrypt certificate

sudo certbot certonly --agree-tos --email admin@example.com --webroot -w /var/lib/letsencrypt/ -d example.com -d www.example.com

The result should be something like that:

 - Congratulations! Your certificate and chain have been saved at:
   Your key file has been saved at:
   Your cert will expire on 2019-11-28. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

The certificate should be automatically renewed, but we have to add a hook to reload apache after the certificate is renewed. To to so, open /etc/cron.d/certbot:

sudo nano /etc/cron.d/certbot

And append –renew-hook “systemctl reload apache2”, so it should look similar to this:

0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew --renew-hook "systemctl reload apache2"

You can test the renewal process by entering:

sudo certbot renew --dry-run

Create a Virtual Host (Host)

Create a folder for the website:

sudo mkdir -p /var/www/example.com/public_html

Create a test file:

sudo nano /var/www/example.com/public_html/index.html

And enter this content:

<h1>Everything works fine!</h1>

Now, set the required permissions:

sudo chown -R www-data:www-data /var/www/
sudo find /var/www -type d -exec chmod g+s {} +

Create VHost config file:

sudo nano /etc/apache2/sites-available/example.com.conf

Add this content:

<VirtualHost *:80> 
  ServerName example.com
  ServerAlias www.example.com

  Redirect permanent / https://example.com/

<VirtualHost *:443>
  ServerName example.com
  ServerAlias www.example.com

  Protocols h2 http/1.1

  <If "%{HTTP_HOST} == 'www.example.com'">
    Redirect permanent / https://example.com/

  DocumentRoot /var/www/example.com/public_html
  ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
  CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined

    <Directory /var/www/example.com/public_html>
        Options -Indexes +FollowSymLinks
        AllowOverride All

  SSLEngine On
  SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
  SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

  # Other Apache Configuration


Enable the site:

sudo a2ensite example.com

Check the syntax:

sudo apachectl configtest

If everything is ok, it should return:

Syntax OK

Restart apache:

sudo systemctl restart apache2

Open http://example.com and you should see Everything works fine!.

Grant User Access to Website Folders

Install bindfs:

sudo apt install bindfs -y

Create a folder used for mounting:

mkdir ~/websites

Run this command to generate the required entry for /etc/fstab:

printf "\n\nbindfs#/var/www/ /home/$USER/websites/ fuse force-user=$USER,force-group=$USER,create-for-user=www-data,create-for-group=www-data,create-with-perms=0770,chgrp-ignore,chown-ignore,chmod-ignore 0 0" | sudo tee -a /etc/fstab

Mount the website(s):

sudo mount /home/$USER/websites

After a reboot, the websites will be mounted automatically. This solution allows you, to access the /var/www folder and subfolders with your user but all files are saved as www-data:www-data.

Install PHP

sudo apt-get install php7.2-fpm php7.2-gd php7.2-curl php7.2-mysql php7.2-cli php7.2-common php7.2-mbstring php7.2-intl php7.2-zip php7.2-bcmath php7.2-imagick php7.2-xml -y
sudo a2enmod proxy_fcgi
sudo a2enconf php7.2-fpm
sudo service apache2 restart

To increase the file upload size when using PHP, edit:

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

And change upload_max_filesize and post_max_size to 32M, so:

; Maximum allowed size for uploaded files.
; http://php.net/upload-max-filesize
upload_max_filesize = 2M


; Maximum size of POST data that PHP will accept.
; Its value may be 0 to disable the limit. It is ignored if POST data reading
; is disabled through enable_post_data_reading.
; http://php.net/post-max-size
post_max_size = 8M

should be changed to:

; Maximum allowed size for uploaded files.
; http://php.net/upload-max-filesize
upload_max_filesize = 32M


; Maximum size of POST data that PHP will accept.
; Its value may be 0 to disable the limit. It is ignored if POST data reading
; is disabled through enable_post_data_reading.
; http://php.net/post-max-size
post_max_size = 32M

Now, restart php:

sudo service php7.2-fpm restart

Install MySQL

Next step is to install the mysql server:

sudo apt install mysql-server -y

Check, if the server is up and running:

sudo systemctl status mysql

Should return:

● mysql.service - MySQL Community Server
   Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2019-08-29 14:22:01 CEST; 7s ago
 Main PID: 2264 (mysqld)
    Tasks: 27 (limit: 2299)
   CGroup: /system.slice/mysql.service
           └─2264 /usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid

Aug 29 14:22:01 my_server.example.com systemd[1]: Starting MySQL Community Server...
Aug 29 14:22:01 my_server.example.com systemd[1]: Started MySQL Community Server.

Now, secure the mysql installation by entering:

sudo mysql_secure_installation

Press “Enter” to not use the VALIDATE PASSWORD PLUGIN. Enter a very secure mysql root password and answer all other questions with ‘y’.

Now, create an administrator user (to use instead of the root user):

sudo mysql

Replace very_strong_password by a very strong password:

GRANT ALL PRIVILEGES ON *.* TO 'administrator'@'localhost' IDENTIFIED BY 'very_strong_password';
GRANT GRANT OPTION ON *.* TO 'administrator'@'localhost';

Now, create a database and a user for your WordPress installation (modify my_database, my_username and my_password!):

CREATE DATABASE my_database;
GRANT ALL PRIVILEGES on my_database.* to 'my_username'@'localhost' identified by 'my_password';

Setup Postfix

To send mails from PHP / WordPress, we need a MTA (Mail Transfer Agent) like postfix. To install postfix, type:

sudo apt install postfix

Select “Internet site” during the configuration of postfix. The other default values can be accepted. You can send an test mail by using the following command (replace my_mail@example.com):

echo "This is the body of the email" | mail -s "This is the subject line" my_mail@example.com

If you want to redirect mails to root to your personal email address, edit /etc/aliases:

sudo nano /etc/aliases

Add this line to the end (modify my_email@example.com):

root: my_email@example.com

After modifying and saving the file, run this command:

sudo newaliases

If you want to check the mail queue, you can use:


To re-send mails from the mail queue, you can use:

sudo postfix flush

To remove all mails from the queue, run:

sudo postsuper -d ALL

Configure the Firewall

sudo ufw allow ssh
sudo ufw allow https
sudo ufw allow http
sudo ufw enable

Check the status:

sudo ufw status

Should output something like this:

Status: active

To                         Action      From
--                         ------      ----
Apache Full                ALLOW       Anywhere                  
22/tcp                     ALLOW       Anywhere                  
Apache Full (v6)           ALLOW       Anywhere (v6)             
22/tcp (v6)                ALLOW       Anywhere (v6)     

Install and Configure Fail2ban

Install fail2ban by entering:

sudo apt-get install fail2ban

Now, create a copy of fail.conf:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

If you want to edit the configuration, only edit jail.local, because jail.conf may be overwritten:

sudo nano /etc/fail2ban/jail.local

We accept the default configuration and restart fail2ban:

sudo systemctl restart fail2ban

To enable fail2ban when booting the server, type:

sudo systemctl enable fail2ban

Enable Automatic Updates

If you want to enable automatic updates, enter:

sudo apt-get install unattended-upgrades

Modify /etc/apt/apt.conf.d/50unattended-upgrades to select, what updates should be installed:

sudo nano /etc/apt/apt.conf.d/50unattended-upgrades