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 1.2.3.4:
ssh-copy-id my_user@1.2.3.4
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:
my_server.example.com
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
└─apache2-systemd.conf
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
</Directory>
Create ssl-params.conf:
sudo nano /etc/apache2/conf-available/ssl-params.conf
With this content:
SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
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:
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/example.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/example.com/privkey.pem
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>
<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/
</If>
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
</Directory>
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
# Other Apache Configuration
</VirtualHost>
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';
FLUSH PRIVILEGES;
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';
FLUSH PRIVILEGES;
EXIT
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:
mailq
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