This guide assumes that you already have some of the basics handled.
- A Digital Ocean account
- A Droplet running Ubuntu 18.04 deployed (but not set up)
- A domain (optional)
- Basic understanding of
SSH
- Basic understanding of
nano
NOTE: all items marked in bold are boilerplate and should be replaced with your own values.
First we must set up and secure the droplet.
SSH into your server
ssh root@your_server_ip_goes_here
Create a new user
adduser username
Enter a new password and fill in any additional info, or just hit ENTER.
Grant your new user admin privileges
usermod -aG sudo username
Allow password authentication access for the new sudo user
nano /etc/ssh/sshd_config
Find the line that says PasswordAuthentication no
and change it to
PasswordAuthentication yes
Save and quit the text editor by typing ctrl + X
followed by Y
Restart the ssh
service
service ssh restart
Check which firewall profiles are available
ufw app list
# Output
Available applications:
OpenSSH
Allow SSH connections
ufw allow OpenSSH
# Output
Rules updated
Rules updated (v6)
Enable the firewall
ufw enable
Type y
and press ENTER
Now you can see that SSH connections are enabled
ufw status
# Output
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Open a new terminal window/tab and enter
ssh username@your_server_ip_goes_here
Enter the password and run a sudo command to ensure that the user has root privileges.
sudo ufw app list
You may be asked for your password. Use the one set up in previous steps
LAMP stands for Linux Apache MySQL PHP, which are the essential requirements for running a WordPress website.
While still accessing your Droplet via the sudo user we set up earlier, update and install Apache.
sudo apt update
sudo apt install apache2
Press Y
when asked if you're sure you'd like to install.
We need to add an application profile for Apache
sudo ufw app list
You'll notice 3 new profiles
# Output
Available applications:
Apache
Apache Full
Apache Secure
OpenSSH
We want to use Apache Full
because it enables traffic to ports 80 (HTTP) and 443 (HTTPS)
sudo ufw allow in "Apache Full"
Now, when you access your server via a browser, you should see the Apache2 Default Page.
http://your_server_ip_goes_here
If you need to find the IP of your server at any time, simply call
curl -4 icanhazip.com
Now that the server is up, we can configure the database.
sudo apt install mysql-server
Remove defaults and lock down access to your database
sudo mysql_secure_installation
Press Y
to install the VALIDATE PASSWORD PLUGIN
Securing the MySQL server deployment. Connecting to MySQL using a blank password. VALIDATE PASSWORD PLUGIN can be used to test passwords and improve security. It checks the strength of password and allows the users to set only those passwords which are secure enough. Would you like to setup VALIDATE PASSWORD plugin? Press y|Y for Yes, any other key for No: y
Press 1
and follow the MEDIUM
password strength guidelines
There are three levels of password validation policy: LOW Length >= 8 MEDIUM Length >= 8, numeric, mixed case, and special characters STRONG Length >= 8, numeric, mixed case, special characters and dictionary file Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 1
Enter a new password
that meets the criteria and press Y
to accept it.
Estimated strength of the password: 100 Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y
For the rest of the questions, press Y
and ENTER
at each prompt.
sudo mysql
Check which authentication method each user uses
SELECT user,authentication_string,plugin,host FROM mysql.user;
# Output
+------------------+-------------------------------------------+-----------------------+-----------+
| user | authentication_string | plugin | host |
+------------------+-------------------------------------------+-----------------------+-----------+
| root | | auth_socket | localhost |
| mysql.session | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost |
| mysql.sys | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost |
| debian-sys-maint | *337CC3D55BCC1BA64047C3559593136117F669B4 | mysql_native_password | localhost |
+------------------+-------------------------------------------+-----------------------+-----------+
4 rows in set (0.01 sec)
Configure root to authenticate with a
password. Be sure to select a strong password.
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '**password**';
Then reload the tables and invoke the changes
FLUSH PRIVILEGES;
Now, check that the authentication method has been updated.
SELECT user,authentication_string,plugin,host FROM mysql.user;
+------------------+-------------------------------------------+-----------------------+-----------+
| user | authentication_string | plugin | host |
+------------------+-------------------------------------------+-----------------------+-----------+
| root | *F60E736B9BAE549E45C246225D58E17D16BF1D22 | mysql_native_password | localhost |
| mysql.session | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost |
| mysql.sys | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost |
| debian-sys-maint | *337CC3D55BCC1BA64047C3559593136117F669B4 | mysql_native_password | localhost |
+------------------+-------------------------------------------+-----------------------+-----------+
4 rows in set (0.00 sec)
Voila! Everything looks correct, root now authenticates using a password. You can now exit the MySQL Shell.
exit
Install PHP dependencies.
sudo apt install php libapache2-mod-php php-mysql
Next, we need to make sure that Apache prefers PHP files over HTML or others.
sudo nano /etc/apache2/mods-enabled/dir.conf
Once in the configuration file, move index.php
from the index it is in by default, to the 0th index (first position).
It should look like this when you're done.
<IfModule mod_dir.c>
DirectoryIndex index.php index.html index.cgi index.pl index.xhtml inde$
</IfModule>
Save and quit the text editor with ctrl + X
and Y
then ENTER
Restart Apache
sudo systemctl restart apache2
Make sure Apache is running as intended
sudo systemctl status apache2
If all is well, it should look like this
● apache2.service - The Apache HTTP Server
Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset:
Drop-In: /lib/systemd/system/apache2.service.d
└─apache2-systemd.conf
Active: active (running) since Fri 2019-04-19 01:59:12 UTC; 26s ago
Process: 15126 ExecStop=/usr/sbin/apachectl stop (code=exited, status=0/SUCCES
Process: 15133 ExecStart=/usr/sbin/apachectl start (code=exited, status=0/SUCC
Main PID: 15149 (apache2)
Tasks: 6 (limit: 1152)
CGroup: /system.slice/apache2.service
├─15149 /usr/sbin/apache2 -k start
├─15154 /usr/sbin/apache2 -k start
├─15155 /usr/sbin/apache2 -k start
├─15156 /usr/sbin/apache2 -k start
├─15157 /usr/sbin/apache2 -k start
└─15158 /usr/sbin/apache2 -k start
Exit this output by typing Q
The virtual host will be used to configure the domain. Remember to replace example.com with your own domain name.
Create a directory for the website
sudo mkdir -p /var/www/example.com
Assign ownership of the directory
sudo chown -R $USER:$USER /var/www/example.com
Ensure that permissions of your web roots are correct
sudo chmod -R 755 /var/www/example.com
Create a new configuration file
sudo nano /etc/apache2/sites-available/example.com.conf
Paste in the following, substituting any example values for your own.
ServerAdmin admin@example.com ServerName example.com ServerAlias www.example.com DocumentRoot /var/www/example.com/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined
Save and close the file when you're done.
Enable the file
sudo a2ensite <b>example.com</b>.conf
Disable the default file
sudo a2dissite 000-default.conf
Test config for errors
sudo apahce2ctl configtest
You may see a warning in the output, you can ignore that for now as long as you see Syntax OK
# Output
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1. Set the 'ServerName' directive globally to suppress this message
Syntax OK
Restart apache to apply the changes
sudo systemctl restart apache2
For the purposes of this tutorial we will be using certbot
First add certbot
sudo add-apt-repository ppa:certbot/certbot
Press ENTER
to accept
Install certbot
's Apache package
sudo apt install python-certbot-apache
Run certbot
sudo certbot --apache -d example.com -d www.example.com
Enter your email address if/when prompted.
Agree to the ToS by pressing A
.
Answer Y
or N
depending on whether or not you ='d like your email shared with the EFF.
You will then be prompted with the following
# Output
Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
Select option 2
.
If successful, you will receive the following output
# Output 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-07-18. To obtain a new or tweaked version of this certificate in the future, simply run certbot again with the "certonly" option. To non-interactively renew *all* of your certificates, run "certbot renew" - Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal. - 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
Ensure that your SSL certificate will auto-renew to avoid any future technical debt.
sudo certbot renew --dry-run
Finally, the moment you've been waiting for. We're just a few moments away from actually setting your WordPress website loose on the world. But first, a few preparations must be made.
Log into MySQL as root
mysql -u root -p
Enter the password
you created for this user in the previous steps.
Create a separate database for WordPress.
CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
Create a new user for the database. Make sure to set
passwordto a secure password.
GRANT ALL ON wordpress.* TO 'wordpressuser'@'localhost' IDENTIFIED BY 'password';
Flush the privileges
FLUSH PRIVILEGES;
Exit MySQL
EXIT;
We will install the most popular PHP extensions used with WordPress
sudo apt update
sudo apt install php-curl php-gd php-mbstring php-xml php-xmlrpc php-soap php-intl php-zip
Restart Apache
sudo systemctl restart apache2
Open the Apache config file for your site
sudo nano /etc/apache2/sites-available/example.com.conf
Add the following within the VirtualHost
block.
example.com/> AllowOverride All
Enable the rewrite module
sudo a2enmod rewrite
Test the configuration
sudo apache2ctl configtest
Restart apache to apply changes
sudo systemctl restart apache2
Enter the tmp
directory and download the latest version of WordPress
cd /tmp
curl -O https://wordpress.org/latest.tar.gz
Extract the file
tar xzvf latest.tar.gz
Create an .htaccess
file
touch /tmp/wordpress/.htacess
Copy the sample config file to the filename that WordPress reads
cp /tmp/wordpress/wp-config-sample.php /tmp/wordpress/wp-config.php
Create an upgrade directory to avoid permissions issues
mkdir /tmp/wordpress/wp-content/upgrade
Copy the contents of the directory into the document root
sudo cp -a /tmp/wordpress/. /var/www/example.com
Update ownership of the directory
sudo chown -R www-data:www-data /var/www/example.com
Set correct permissions on the WordPress directories and files
sudo find /var/www/example.com/ -type d -exec chmod 750 {} \; sudo find /var/www/example.com/ -type f -exec chmod 640 {} \;
Grab secure values from the WordPress secret keygen
curl -s https://api.wordpress.org/secret-key/1.1/salt/
The output will look similar to this. Use your own values in the coming steps and DO NOT COPY THESE.
# Output
define('AUTH_KEY', '1jl/vqfs<XhdXoAPz9 DO NOT COPY THESE VALUES c_j{iwqD^<+c9.k<J@4H');
define('SECURE_AUTH_KEY', 'E2N-h2]Dcvp+aS/p7X DO NOT COPY THESE VALUES {Ka(f;rv?Pxf})CgLi-3');
define('LOGGED_IN_KEY', 'W(50,{W^,OPB%PB<JF DO NOT COPY THESE VALUES 2;y&,2m%3]R6DUth[;88');
define('NONCE_KEY', 'll,4UC)7ua+8<!4VM+ DO NOT COPY THESE VALUES #`DXF+[$atzM7 o^-C7g');
define('AUTH_SALT', 'koMrurzOA+|L_lG}kf DO NOT COPY THESE VALUES 07VC*Lj*lD&?3w!BT#-');
define('SECURE_AUTH_SALT', 'p32*p,]z%LZ+pAu:VY DO NOT COPY THESE VALUES C-?y+K0DK_+F|0h{!_xY');
define('LOGGED_IN_SALT', 'i^/G2W7!-1H2OQ+t$3 DO NOT COPY THESE VALUES t6**bRVFSD[Hi])-qS`|');
define('NONCE_SALT', 'Q6]U:K?j4L%Z]}h^q7 DO NOT COPY THESE VALUES 1% ^qUswWgn+6&xqHN&%');
Open the WordPress config file
sudo nano /var/www/example.com/wp-config.php
Find the section that looks like this under Authentication Unique Keys and Salts
. . .
define('AUTH_KEY', 'put your unique phrase here');
define('SECURE_AUTH_KEY', 'put your unique phrase here');
define('LOGGED_IN_KEY', 'put your unique phrase here');
define('NONCE_KEY', 'put your unique phrase here');
define('AUTH_SALT', 'put your unique phrase here');
define('SECURE_AUTH_SALT', 'put your unique phrase here');
define('LOGGED_IN_SALT', 'put your unique phrase here');
define('NONCE_SALT', 'put your unique phrase here');
. . .
Delete and paste in the values you copied from the step above.
Next, find the section towards the top that looks like this and replace with your own values.
If you've followed the tutorial up to this point, you can copy the code below. The only change you'll need to make is to the DB_PASSWORD
value.
. . .
define('DB_NAME', 'wordpress');
/** MySQL database username */
define('DB_USER', 'wordpressuser');
/** MySQL database password */
define('DB_PASSWORD', 'password');
. . .
define('FS_METHOD', 'direct');
Save and close the file when you're done.
From your browser, navigate to your website
https://your_website
Follow the WordPress onboarding flow, and you're all set!`