How to setup WordPress on Digital Ocean (2020)

With incidents such as Australian bush fire, Covid-19 Pandemic and civil unrest in many countries over one reason or the other, 2020 is basically proving to be one long nightmare. To make it worse for me, my server got hacked, majorly due to lack of proper security or a bad WordPress vulnerability (To put it in the words of Einstein, mildly, human stupidity has no limit).

So I decided to migrate the server to a new one and this time decided to invest some time in making it more secure. This guide is basically a document to setup such a server to host WordPress sites with as much security hardening as I possibly can do. 2020, bring it on!

Setting up Server and Hostname

  1. Create account on Digital Ocean. (My referral).
  2. Create a Droplet in Digital Ocean. Choose latest Debian version.
  3. Enter the Server IP address into Domain’s Advance DNS which can be find on site where domain is registered.
HostValueTTLComment
@Server IPAutomatic
wwwServer IPAutomatic
srvServer IPAutomatic(may not be required)
Host Name Settings
TypeValue
MXE RecordServer IP
Mail Settings : MXE Record

After this everything is done on Domain side. Now we have to configure the server.

  1. Login with root from terminal (on Linux) or PuTTY (from Windows) with below command:
ssh root@ServerIPAddress
-enter password
-change password for root //terminal will ask to set new password
  1. Now install the firewall on server.
sudo apt-get update 		//update the newest version of packages
sudo apt upgrade		//upgrade the packages (if any)
sudo apt-get install ufw 	//install ufw firewall
sudo ufw status verbose		//check status of firewall if active or inactive (should be inactive at this point)
sudo ufw default deny incoming		//deny incoming connections
sudo ufw default allow outgoing		//allow outgoing connections
  1. Enable IPv6 with UFW
sudo nano /etc/default/ufw //open ufw file in nano editor and check if "IPV6=yes", if not, then set "IPV6=yes", save (Ctrl + X) and exit
  1. Allow connections on UFW:
sudo ufw allow 22 //to allow SSH (like connecting from Terminal)
sudo ufw allow 25 //to allow SMTP connections (Used in sending emails)
sudo ufw allow 80 //to allow HTTP connections (Normal web traffic)
sudo ufw allow 443 //to allow HTTPS connections (Secure channel) Optional:

//Optional
sudo ufw allow 2222/tcp //to allow custom ssh if using it
sudo ufw allow 5000:5003/tcp //to allow specific ports tcp
sudo ufw allow 5000:5003/udp //to allow specific ports udp

Note: in order to deny any connection, replace “allow” with “deny” in above commands.

  1. Restart ufw firewall using below commands:
sudo ufw disable //disable ufw
sudo ufw enable //enable ufw (make sure port 22 was enabled before prompting Yes to confirmation)
  1. Create user on server with below command:
sudo adduser username //create new user
sudo usermod -aG sudo username //add user to sudo group
su - username //login as the user just created (Or optionally use below step)
  1. Login, In Terminal using new user with below command:
ssh username@DomainName
enter user's password
  1. Disable root on debian in order to provide more security to server:
sudo nano /etc/ssh/sshd_config 		//edit "sshd_config" file in nano editor

Make following changes in file:

PermitRootLogin no		//remove # from "#PermitRootLogin yes" and make the line as "PermitRootLogin no"
AllowUsers username		//in the end of the file add "AllowUsers username", save and exit (ctrl+X and then Y)
sudo systemctl restart sshd	//Restart SSH service

Try logging in with root@ServerIPAddress, it should deny login due to permission denied. Now only those users will be able to login using SSH which were set in “AllowUsers username” command.

  1. Install nginx on server using below command:
sudo apt install nginx				//install Nginx

Now test if nginx is install by checking ServerIPAddress in URL on browser. It should show “Welcome to nginx” page if correctly installed.

  1. Install Mariadb (MySql) on server using below command:
sudo apt install mariadb-server mariadb-client		//install MariaDB server and MariaDb Client
sudo systemctl start mariadb			//start MariaDb server
sudo systemctl status mariadb //verify if MariaDb service is running  --Optional
sudo mysql_secure_installation			//Secure Database installation
    -current password for root : (just press enter here)
    -Change the root password? : y	// Enter a secure password for root
    -Remove anonymous users?   : y
    -Disallow root login remotely? : y
    -Remove test database and access to it? : y
    -Reload privilege tables now? : y
sudo mysql -u root -p				//login to MariDB shell
GRANT ALL ON *.* TO 'username'@'localhost' IDENTIFIED BY 'NewPwdForUser';		//This 'Username' will be created in MariaDb as new user with the new pwd entered in 'NewPwdForUser'
FLUSH PRIVILEGES;				//for the changes to take effect
\q						//to exit fom MaiaDb shell
  1. Install PHP for processing using below command:
sudo apt install php-fpm php-mysql	//install php-fpm and php-mysql packages
  1. Download, Install and configure PhpMyAdmin (be sure to find link to latest PhpMyAdmin from https://www.phpmyadmin.net/files/):
wget https://files.phpmyadmin.net/phpMyAdmin/5.0.2/phpMyAdmin-5.0.2-all-languages.zip
sudo apt install unzip
unzip phpMyAdmin-5.0.2-all-languages.zip
rm phpMyAdmin-5.0.2-all-languages.zip
sudo mv /home/vyom/phpMyAdmin-5.0.2-all-languages/ /opt/
sudo mv /opt/phpMyAdmin-5.0.2-all-languages /opt/phpMyAdmin			
sudo ln -s /opt/phpMyAdmin/ /srv/phpmyadmin		

The last command creates a link for /opt/phpMyAdmin as /srv/phpmyadmin

  1. Install certbot so that SSL certificates can be installed for every domain:
sudo apt install certbot
cd /etc/letsencrypt
sudo openssl dhparam -out dhparams.pem 2048

The last command generates dhparams file. It will take about a min or two depending on server speed.

  1. Create certificate for the server phpmyadmin page we are trying to host
sudo service nginx stop
sudo certbot certonly --standalone --preferred-challenges http -d srv.DomainName
    -Agree
    -Yes
    -No
sudo service nginx restart
  1. Configure nginx configuration to serve server page
cd /etc/nginx/sites-available/
sudo nano srv.DomainName.conf	//create a new file "srv.DomainName.conf" as per requirement to enable nginx and ssl etc.

Enter a proper nginx config like below:

server {
    listen 1111 ssl;
    ssl_certificate /etc/letsencrypt/live/srv.sitename.tld/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/srv.sitename.tld/privkey.pem;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/letsencrypt/dhparams.pem;
    ssl_ciphers '<the long SSL cipher string>';

    root /srv/phpmyadmin;
    error_page 404 /404.html;


    index index.html index.php;

    server_name srv.sitename.tld;

    location / {
            try_files $uri $uri/ index.php?$args =404;
    }


    location ~ \.php$ {
            include snippets/fastcgi-php.conf;
            fastcgi_pass unix:/run/php/php7.3-fpm.sock;
    }
}
  1. Allow the port number specified in the configuration file in firewall
sudo ufw allow 1111
  1. Make symlink of the nginx config file in sites-enabled folder
sudo ln -s /etc/nginx/sites-available/srv.sitename.tld.conf /etc/nginx/sites-enabled/srv.sitename.tld.conf
  1. Restart nginx service
sudo service nginx restart

If there are no error till now, then you have successfully setup nginx and certbot
The phpmyadmin page should be available at the URL: srv.sitename.tld:1111


Configure the WordPress site

Now we can host one or more WordPress based sites on the server. Below are the steps to host one WordPress site (DomainName.tld):

  1. Create directory structure for WordPress
cd /home/username
mkdir www
cd www
mkdir DomainName.tld
cd DomainName.tld
  1. Now download WordPress tar file
wget https://wordpress.org/latest.tar.gz
  1. Extract the tar file
tar -xzvf latest.tar.gz
  1. Rename the wordpress folder (just extracted) to public_html
sudo mv wordpress public_html
  1. Open the URL srv.sitename.tld:1111 and create the database with following name
username_sitename
Encoding: utf8mb4_general_ci
  1. Create the site’s nginx config:
sudo nano /etc/nginx/sites-available/DomainName.tld.conf
  1. Enter a proper config like below (php version might need to be modified depending on the version installed on your server):
server {
	listen 80 ;
	server_name DomainName.tld www.DomainName.tld;
	return 301 https://$server_name/$request_uri;
}

server {
	listen 443 ssl;
	ssl_certificate /etc/letsencrypt/live/DomainName.tld/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/DomainName.tld/privkey.pem;
	ssl_prefer_server_ciphers on;
	ssl_dhparam /etc/letsencrypt/dhparams.pem;
	ssl_ciphers '<the long SSL cipher string>';

	root /home/user/www/DomainName.tld/public_html;
	error_page 404 /404.html;


	index index.html index.php;

	server_name DomainName.tld www.DomainName.tld;

	location / {
		try_files $uri $uri/ /index.php?$args;
	}

	
	location ~ \.php$ {
		include snippets/fastcgi-php.conf;
		fastcgi_pass unix:/run/php/php7.3-fpm.sock;
	}
}
  1. Create a symlink of the config in sites-enabled folder:
sudo ln -s /etc/nginx/sites-available/DomainName.tld.conf /etc/nginx/sites-enabled/DomainName.tld.conf
  1. Create SSL certificate for the site:
sudo service nginx stop
sudo certbot certonly --standalone --preferred-challenges http -d DomainName.tld
sudo service nginx start
  1. If there are no error in the above step, then proceed to provide to change permissions to WordPress folder:
cd /home/user/www/
sudo chown www-data:www-data -R *             // Let Apache be owner
sudo find . -type d -exec chmod 755 {} \;
sudo find . -type f -exec chmod 644 {} \;
  1. If there is no error in above step, then proceed to open the URL: DomainName.tld in the browser and setup WordPress.

At this moment, you can restore the WordPress site from a backup taken by backup plugins like UpDraft, which restores your blog posts and SQL database.

Follow this guide to setup more than one WordPress sites too.


Configure Email Configuration

  1. Install postfix (let default values be there in prompts):
sudo apt-get install postfix
  1. Check if postfix service is enabled:
sudo service postfix status
  1. Open this file using nano:
sudo nano /etc/postfix/main.cf
  1. Write following lines in the end of file:
virtual_alias_domains = mydomain.com myanotherdomain.com
virtual_alias_maps = hash:/etc/postfix/virtual
  1. Create a database like file out of it:
postmap /etc/postfix/virtual
  1. Create virtual file:
sudo nano /etc/postfix/virtual
  1. Add entries of email addresses which you want to forward:
postmaster info@example.com
abuse info@example.com
  1. Restart postfix:
sudo service postfix reload

WordPress Security

After the setup you should tighten the access rights, according to Hardening WordPress all files except for wp-content should be writable by your user account only. wp-content must be writable by www-data too.

chown <user>:<user> -R *		// Let your useraccount be owner
chown www-data:www-data wp-content 	// Let apache be owner of wp-content

Maybe you want to change the contents in wp-content later on. In this case you could

> temporarily change to the user to www-data with su, 
> give wp-content group write access 775 and join the group www-data 
> OR mention the following line in end of wp-config.php
define('FS_METHOD', 'direct');

Following are some of the steps taken from WordPress Security guide at WPBeginner:

  1. Disable File Editing

Add following lines in wp-config.php in end:

// Disallow File Edit (disabled ability to edit theme from WP GUI
define( 'DISALLOW_FILE_EDIT', true );
  1. Disable PHP File Execution in Certain WordPress Directories

Create the file wp-content/uploads/.htaccess and write following in it

deny from all
  1. Limit Login Attempts

Install plugin https://wordpress.org/plugins/login-lockdown and limit login:

  1. Add Two Factor Authentication

Install plugin https://wordpress.org/plugins/two-factor-authentication and enable dual authentication

  1. Disable Directory Indexing and Browsing

Follow this guide and disable directory indexing and browsing: https://www.wpbeginner.com/beginners-guide/why-you-cant-find-htaccess-file-on-your-wordpress-site. And for nginx, from this guide, make changes in nginx.conf similar to following where autoindex is off:

server {
        listen   80;
        server_name  domain.com www.domain.com;
        access_log  /var/...........................;
        root   /path/to/root;
        location / {
                index  index.php index.html index.htm;
        }
        location /somedir {
               autoindex off;
        }
 }
  1. Disable XML-RPC in WordPress

Follow the guide here (https://www.maketecheasier.com/disable-xml-rpc-wordpress) and disable XML-RPC. For Nginx paste following code in server block:

# nginx block xmlrpc.php requests
location /xmlrpc.php {
    deny all;
}
  1. Scanning WordPress for Malware and Vulnerabilities

Use this list to find one an online scanner and scan the site for potential vulnerabilities: https://www.wpbeginner.com/showcase/best-wordpress-vulnerability-scanners-online

  1. Making changes in the File Permissions the hard way:

You can follow this guide from the horse’s mouth, to set the correct permissions for WordPress files: https://wordpress.org/support/article/hardening-wordpress/


Configure fail2ban

Server security is of the utmost importance when you are hosting on your own VPS. Since a VPS is just another computer albeit running on some other part of the World, you would need to manage it like so. You would need to regularly keep it updated, review logs and also setup fail2ban, which I had previously not done. (Biggest mistake!)

Below are steps to configure fail2ban on the debian server we just configured:

  1. Install fail2ban:
sudo apt-get install fail2ban
  1. To make modifications in fail2ban config, we need to copy a file to /etc/fail2ban/jail.local. This will prevent our changes from being overwritten if a package update provides a new default file:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
  1. Open the jail.local file:
sudo nano /etc/fail2ban/jail.local
  1. It’s a good idea to add your own IP address or network to the list of exceptions to avoid locking yourself out:
ignoreip = 127.0.0.1/8 your_home_IP
  1. Increase bantime:
bantime = 3600
  1. The findtime specifies an amount of time in seconds and the maxretry directive indicates the number of attempts to be tolerated within that time. If a client makes more than maxretry attempts within the amount of time set by findtime, they will be banned:
findtime = 3600   # These lines combine to ban clients that fail
maxretry = 6      # to authenticate 6 times within a half hour.

You can go in more detail to configure more nginx settings, to make it more secure at this guide.


Troubleshooting tips

  1. If you need to import an SQL dump on WordPress and error comes up as, “413 Request Entity Too Large” then follow these steps:
sudo nano /etc/nginx/nginx.conf

Add the following line to http or server or location context to increase the size limit in nginx.conf, enter:

# set client body size to 5M #
client_max_body_size 5M;

Then restart nginx:

sudo service nginx restart

Open php config file:

sudo nano /etc/php…

Change following line:

upload_max_filesize = 5M

Restart php:

sudo systemctl restart phhttps://wordpress.org/plugins/two-factor-authenticationp7.3-fpm.service
  1. To delete empty folders following command can be used:
sudo find uploads/ -empty -type d -delete
  1. To copy files from local computer to droplet:
sudo rsync -zarvh /home/user/desktop/ user@DomainName.tld:/home/user/www/
  1. To copy files from remote to local droplet:
sudo rsync -zarvh user@DomainName.tld:/home/user/www/ /home/user/Downloads/MySitesBackup/

Conclusion

I think I have barely scratched the surface when it comes to the “secure” part of setting up the server, but I think it’s possibly the best starting ground. I hope to learn and add more tips in this blog as I go along figuring out myself.

With the way 2020 is going, no amount of security is going to be enough, but it’s the “hope” that this world is built upon and will truly be the defining part of rest of the year!


Sources

Initial Server Setup: https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-18-04

Setup a Firewall using ufw: https://www.digitalocean.com/community/tutorials/how-to-set-up-a-firewall-with-ufw-on-ubuntu-18-04
https://www.tecmint.com/setup-ufw-firewall-on-ubuntu-and-debian

Disable root on debian: https://linuxhint.com/disable_root_ssh_debian

Installing MariaDB on server: https://linuxhint.com/install_phpmyadmin_debian_10

Configuring phpMyAdmin: https://linuxhint.com/install_phpmyadmin_debian_10

Setting up postfix: https://www.binarytides.com/postfix-mail-forwarding-debian

StackOverflow replies on WordPress File Permission: https://stackoverflow.com/questions/18352682/correct-file-permissions-for-wordpress

Hardening WordPress Security: https://wordpress.org/support/article/hardening-wordpress

Increase nginx and PHP limits: https://www.cyberciti.biz/faq/linux-unix-bsd-nginx-413-request-entity-too-large

Setting up fail2ban: https://www.digitalocean.com/community/tutorials/how-to-protect-an-nginx-server-with-fail2ban-on-ubuntu-14-04

Leave a Reply

Your email address will not be published. Required fields are marked *