Provisioning of a Wordpress website implementation on cloud based on a server with LAMP (Linux 🐧, Apache2 🖌️, MySQL 🐬 and PHP 🐘). Check it at http://188.166.90.196 / http://www.lizfer.com.
I decided to create a personal space where I could demonstrate my Linux system administration skills. The prerrogative was:
- This space needs to be operational anytime, anywhere (availability); and
- it must meet minimal performance standards (reliability).
The first step was provisioning an instance (that is, a droplet) at Digital Ocean. My pick was Ubuntu 20.04 LTS, hosted at Amsterdam (default-ams3).
After having the instance ready (with my SSH public and private keys properly installed), the next step was configuring the server to have the LAMP setup installed.
First things first: ssh root@188.166.90.196
into it, then apt-get update && apt-get upgrade
. Now, good practices: create a regular user and stop using the root account.
This was pretty straightforward:
- Ran
adduser lizfer
and created the PID 1000 user; - Added it to the sudoers group using
usermod -aG sudo lizfer
; - Allowed it to use the SSH public key by copying it from root (and granted ownership of this copy) using rsync:
rsync --archive --chown=lizfer:lizfer ~/.ssh /home/lizfer
.
At this point, my newly-created user had administrative privileges whenever it used sudo
and I could use this account to access from any remote client that had the SSH private key installed.
Although this was a simple demonstration, it's always good practice to have a firewall set, to avoid undesired connections. Since that this is an Ubuntu machine, I used UFW. To enable it, I just had to enter ufw enable
. As other software are installed, description on firewall settings will be added.
From this moment onwards, I decided to authenticate using the PID 1000 account (lizfer) for testing reasons - hence why lots of sudo
will appear.
Configuring the "A" in LAMP was a handful of work - my first attempt was configuring a Nginx web server, but conflicts with php-fpm and cacheing prevented me from doing so.
The first step was getting Apache2 from Ubuntu's repos using sudo apt install apache2
. Just had to accept the installation terms.
By downloading Apache, UFW automatically creates profiles for Apache based on ports available for access. By entering sudo ufw app list
, the list of available applications was prompted on the stdout (tl;dr: Apache profile opens only port 80, Apache Full opens both port 80 and port 443 and Apache Secure opens only port 443):
Available applications:
Apache
Apache Full
Apache Secure
OpenSSH
To configure the TLS/SSL certificate for Wordpress, I allowed communication in ports 80 and 443 by enabling the Apache Full profile: sudo ufw allow in "Apache Full"
.
It might look scary at first, but setting up a database was not that difficult due to Ubuntu's configuration.
Just had to use sudo apt install mysql-server
to start downloading. Once completed, I ran sudo mysql_secure_installation
to configure it using MySQL's security script:
- For the
VALIDATE PASSWORD PLUGIN
, I set a MySQL root password withSTRONG
length (security should always be strong, IMHO); - Disabled root account access from a remote client (outside of the local host);
- Removed anonymous user pre-set accounts;
- Removed the test database, and privileges that allow access to databases with names starting with "test_".
Setting up a MySQL root password was extremely cautious, since that the default authentication method for root is using a unix_socket.
Wordpress saves information on a database to manage the website and information. I created a new database and user for security reasons (the same ones why I created the PID 1000 user).
- First, I entered as root using
mysql -u root -p
; - Created the
wordpress
database usingCREATE DATABASE wordpress DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
; - Created the
lizfer
user withCREATE USER 'lizfer'@'%' IDENTIFIED WITH mysql_native_password BY 'psswd';
('psswd'
is not the real password, obviously); - Granted full access on
wordpress
tolizfer
:GRANT ALL ON wordpress.* TO 'lizfer'@'%';
and updated MySQL's privileges usingFLUSH PRIVILEGES
.
PHP installation by itself is not enough: I also installed several other packages, such as php-mysql
(module that allows PHP to communicate with MySQL) and libapache2-mod-php
(to enable Apache to handle PHP files), for example:
sudo apt install php php-mysql libapache2-mod-php php-curl php-gd php-mbstring php-xml php-xmlrpc php-soap php-intl php-zip
.
At this point, the LAMP stack was already installed and I had a production instance working.
This chapter is reserved for configuration that must be applied before installing Wordpress.
I decided to use this machine for hosting more than one domain. Therefore, the configuration of an Apache Virtual Host was necessary. Data is displayed as lizfer
but this is a placeholder - upon provisioning a new domain for this machine, the proper data must be used.
The default /var/www/html
directory remained as backup for the original files; I created a new folder under www
(sudo mkdir /var/www/lizfer
) and assigned its ownership to the PID 1000 user (sudo chown -R $USER:$USER /var/www/lizfer
).
Then, I created a new configuration file under Apache structure using Nano: sudo nano /etc/apache2/sites-available/lizfer.conf
and included the following content in it:
<VirtualHost *:80>
ServerName lizfer
ServerAlias www.lizfer.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/lizfer
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
RewriteEngine on
RewriteCond %{SERVER_NAME} =www.lizfer.com [OR]
RewriteCond %{SERVER_NAME} =lizfer
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
<Directory /var/www/lizfer/>
AllowOverride All
</Directory>
</VirtualHost>
I used sudo a2ensite lizfer
to instruct Apache which is the current active server, and disabled the default website using sudo a2dissite 000-default
. Ran the final configuration test with sudo apache2ctl configtest
(with a Syntax OK
response) and rebooted the instance.
I acquired the lizfer.com domain on Google Domains ("Why?" you ask. "Billing" is the answer) and set it to route to my droplet's IP address:
This process was added because WordPress deals with personal data, which requires some TLS/SSL security. For the Certificate Authority, I chose Let's Encrypt because it automatizes most processes with Certbot and it easily integrates with Apache. A few more sub-steps:
- Installed the
certbot
andpython3-certbot-apache
(integration between Apache and Certbot) packages:sudo apt install certbot python3-certbot-apache
; - Used the Apache plugin for Certbot to set up authentication and installation:
sudo certbot --apache
Then, up to configuring Wordpress! 🚀
As a good practice, the latest Wordpress version should be downloaded straight from their website using curl
.
- First: moved to a writable, temporary directory:
cd /tmp
- Downloaded using
curl -O https://wordpress.org/latest.tar.gz
; - Extracted the file using
tar xzvf latest.tar.gz
; - Created a dummy .htaccess file for Wordpress:
touch /tmp/wordpress/.htaccess
and copied the sample config file for the Wordpress to read:cp /tmp/wordpress/wp-config-sample.php /tmp/wordpress/wp-config.php
; - Avoided future Wordpress permission issues upon updating by creating an upgrade directory:
mkdir /tmp/wordpress/wp-content/upgrade
; - Copied the whole content of the directory to Apache's directory:
sudo cp -a /tmp/wordpress/. /var/www/lizfer/
; - Granted ownership to www-data (user and group) on Apache's directory:
sudo chown -R www-data:www-data /var/www/lizfer
; - Set proper permissions on Wordpress folders using
find
:sudo find /var/www/lizfer/ -type d -exec chmod 750 {} \;
andsudo find /var/www/lizfer/ -type f -exec chmod 640 {} \;
; - Removed the index.html file from the Wordpress directory (Wordpress prioritizes HTML files, but we'll use PHP):
sudo rm /var/www/lizfer/index.html
.
Upon configuring, Wordpress requires to adjust some secret keys:
- Obtained random values from Wordpress' secret key generator using
curl -s https://api.wordpress.org/secret-key/1.1/salt/
; - Entered the proper values on Wordpress' config file using nano:
sudo nano /var/www/lizfer/wp-config.php
, together with defining other settings (database name, username and password); - Defined the filesystem writing method (
FS_METHOD
) todirect
, on the bottom of this very same file, to avoid prompting for FTP credentials when posting or querying for a blog entry (for example):
// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'wordpress' );
/** MySQL database username */
define( 'DB_USER', 'lizfer' );
/** MySQL database password */
define( 'DB_PASSWORD', 'psswd' );
...
/** Pretend this is the end of the file, OK? */
define('FS_METHOD', 'direct');
- Enabled
.htaccess
overrides by editingsudo nano /etc/apache2/sites-available/lizfer-le-ssl.conf
(to include the SSL exceptions):
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName lizfer
ServerAlias www.lizfer.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/lizfer
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLCertificateFile /etc/letsencrypt/live/www.lizfer.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/www.lizfer.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
<Directory /var/www/lizfer/>
AllowOverride All
</Directory>
</VirtualHost>
</IfModule>
Since that the index.html file was deleted, Wordpress switched to load index.php - I had to enter https://www.lizfer.com to configure it on the web interface.
After selecting the language, the five-minutes-express installation page was prompted. I chose a username, created another password and completed the installation.
(Tweaks will be added with time.)
Voilá!
- Digital Ocean, for great documentation where I learned from and hosting my website
- @groovemonkey (Dave Cohen) for his awesome Udemy course and other content on Youtube