Stay Tuned!

Subscribe to our newsletter to get our newest articles instantly!

Feature Tech

A Complete Setup Guide for Contabo VPS Ubuntu 20.04 with preinstalled Webmin + LAMP


Last Updated:
[2023-05-31] Added a section for Upgrading from PHP 7.4 to 8.1 and Upgrading from PHP 8.1 to 8.2.
[2022-01-22] Fix grammatical errors.
[2023-01-19] Fix the SQL commands in the Databases / Create Database section.

I haven’t touched the Linux server for ages and getting used to it with Cpanel or Parallel. However, as we need to scale up our internal websites and lower the hosting cost, I need to go back to managing these natively in Linux. When configuring with Ubuntu Linux, it doesn’t sound very comforting. However, it turned out it’s not too bad as I thought – it’s like riding a bike all over again 🙂

We decided to go with Contabo for all public external interface servers, and internally we used Microsoft Azure and DigitalOcean/Docker. I set up a few virtual servers in Australia, Germany, and the United States. They are in various modes: test, staging, or production servers. Each server has an FTP backup storage. Ideally, it will be a mirror between these two sites.

As I progressed, I found a few challenges and started documenting this. This guideline becomes our standard when we deploy the Ubuntu Linux virtual machine.

Let’s dig into it!

Setup and Configuration



Open your browser and enter the URL that Contabo supplied for Webmin access.

For instance:

Under the Dashboard, click the link on Package updates, if any.

Click the Update Selected Packages button.

Click the Install Now button.

Click the Return to package list button.

To reboot the machine, on the left navigation, click Bootup and Shutdown.

Under the Bootup and Shutdown page, scroll down. Click the Reboot System button.

Click the Reboot System once more.

Command Line

Use an application called Putty.

Login as root and cut the password, and use right-click on the mouse to paste.


# sudo apt update

Note: If there is stating X packages need to update.

# sudo apt-get update
# sudo apt-get upgrade
# sudo apt-get dist-upgrade


# sudo shutdown -r 0

Verify the LAMP installation


By default, the database installation is MariaDB which is forked from Oracle MySQL.

mysql --version

Note: it will show: mysql Ver 8.0.25-0ubuntu0.20.04.1 for Linux on x86_64 ((Ubuntu))

Check MYSQL service is running or not.

sudo systemctl status mysql.service

Note: Press q to exit

Note: Check the running status by looking at

Active: active (running)

If it is not active (running), run to start:

sudo systemctl start mysql.service

Change the MariaDB root password:

sudo mysqladmin --user=root --password=currentpassword password "newpassword"

Test the new password:

mysql -u root -p

Under MariaDB console, to exit:


Apache Web Server

Check the Apache 2 status:

sudo systemctl status apache2

Note: Press q to exit

Note: Check the running status by looking at

Active: active (running)

If it is not running:

sudo systemctl start apache2

PHP and Extensions

sudo php --version


Root Password

To set a root password, open a terminal window and execute the following sudo command. First, you need to enter your user password ( given that you are part of the sudo administration group ), after which enter and retype a new root password :

# sudo passwd root

You should now be ready to log in with a new root password. Try it now:

# su

Use the whoami command to confirm that you are logged in as a root user:

# whoami

Add Sudo User

To create another sudo user:

# adduser testuser

Enter and retype the password. Enter the name and relevant information.

Add the new user to the sudo group:

# adduser testuser sudo

Add Super User Account for MariaDB

Access the MariaDB command line:

# sudo mysql -u root -p

Create a new user:

> CREATE USER 'testuser'@'%' IDENTIFIED BY 'testpassword';

Grant all permissions to the new MariaDB user account.


Access the MariaDB command line using the new account.

# sudo mysql -u testuser -p

Verify the new MariaDB account privileges.

# SHOW GRANTS FOR 'testuser';


Change SSH Server Port

Open up the Webmin via URL with standard port.

Expand servers and select SSH server from the leftmost side of the Webmin control panel and click on the edit config files option:

Un-comment the port option from the configuration file set a new port number value here, and click the save option.

Then click the apply changes button to apply the current configuration by sending a SIGHUP signal to the running SSHd process.


Change Webmin Port

If you need it from the Webmin portal, it requires installing libsocket6-perl:

apt-get install libsocket6-perl

Open up the Webmin via URL with standard port 10000. On the left-hand side, expand the Webmin and select the Webmin Configuration option.

On the Webmin configuration, change the port accordingly:

Click the Save button.

Stop/Start/Restart Webmin

To stop the server:

sudo /etc/webmin/stop

To start the server:

sudo /etc/webmin/start

To restart the server:

sudo /etc/webmin/restart


Install ClamAV

Once logged into your Ubuntu 20.04 server, run the following command to update your base system with the latest available packages.

# sudo apt-get update -y

By default, the ClamAV package is available in the Ubuntu 20.04 default repository. You can install it with the following command:

# sudo apt-get install clamav clamav-daemon -y

Before updating the database, you will need to stop the clamav-freshclam service. You can stop it with the following command:

# sudo systemctl stop clamav-freshclam

Update the database using the following command:

# sudo freshclam

Start the clamav-freshclam service and enable it to start at system reboot with the following command:

# sudo systemctl start clamav-freshclam
# sudo systemctl enable clamav-freshclam

By default, freshclam stores all databases inside /var/lib/clamav/ directory. You can list them with the following command:

# sudo ls /var/lib/clamav/

Get the following output:

bytecode.cvd  daily.cvd  main.cvd

Clamscan scans files and directories for viruses and deletes them immediately. The basic syntax of Clamscan is shown below:

# clamscan [options] [files-or-directories]

A brief explanation of the most commonly used options are shown below:

  • –infected : This option display a list of all infected files.
  • –remove : This option removes all infected files from your system.
  • –recursive : This option will scan all directories and sub-directories.

For example, you can scan the /etc directory with the following command:

# sudo clamscan --infected --remove --recursive /etc

You can print all available options with clamscan using the following command:

# sudo clamscan -h

Setup the daily scan and create an SH file called:

/usr/bin/clamscan -i -r $SCAN_DIR >> $LOG_FILE

Give execute permission. 

Intrusion Prevention via Fail2ban

The Fail2ban package is included in the default Ubuntu 20.04 repositories. To install it, enter the following command as root or user with sudo privileges:

# sudo apt update
# sudo apt install fail2ban

Once the installation is completed, the Fail2ban service will start automatically. You can verify it by checking the status of the service:

# sudo systemctl status fail2ban

The output will look like this:

The default Fail2ban installation comes with two configuration files, /etc/fail2ban/jail.conf and /etc/fail2ban/jail.d/defaults-debian.conf. It is not recommended to modify these files as they may be overwritten when the package is updated.

Fail2ban reads the configuration files in the following order. Each .local file overrides the settings from the .conf file:

  • /etc/fail2ban/jail.conf
  • /etc/fail2ban/jail.d/*.conf
  • /etc/fail2ban/jail.local
  • /etc/fail2ban/jail.d/*.local

For most users, the easiest way to configure Fail2ban is to copy them jail.conf to jail.local and modify the .local file. More advanced users can build a .local configuration file from scratch. The .local file doesn’t have to include all settings from the corresponding .conf file, only those you want to override.

Create a .local configuration file from the default jail.conf file:

# sudo cp /etc/fail2ban/jail.{conf,local}

To start configuring the Fail2ban server, open the jail.local file with your text editor:

# sudo nano /etc/fail2ban/jail.local

The file includes comments describing what each configuration option does. In this example, we’ll change the basic settings.

Whitelist IP Addresses

IP addresses, ranges, or hosts you want to exclude from banning can be added to the ignoreip directive. Here you should add your local PC IP address and all other machines you want to whitelist.

Uncomment the line starting with ignoreip and add your IP addresses separated by space:

ignoreip = ::1

Ban Settings

The values of bantime, findtime, and maxretry options define the ban time and ban conditions.

bantime is the duration for which the IP is banned. When no suffix is specified, it defaults to seconds. By default, the bantime value is set to 10 minutes. Generally, most users will want to set a longer ban time. Change the value to your liking:

bantime = 1d

To permanently ban the IP, use a negative number.

findtime is the duration between the number of failures before a ban is set. For example, if Fail2ban is set to ban an IP after five failures (maxretry, see below), those failures must occur within the findtime duration.

findtime = 10m

maxretry is the number of failures before an IP is banned. The default value is set to five, which should be fine for most users.

maxretry = 5

Email Notifications

Fail2ban can send email alerts when an IP has been banned. To receive emails, you need to have an SMTP installed on your server and change the default action, which only forbids the IP to %(action_mw)s, as shown below:

action = %(action_mw)s

%(action_mw)s bans the offending IP and sends an email with a whois report. If you want to include the relevant logs in the email, set the action to %(action_mwl)s. You can also adjust the sending and receiving email addresses:

destemail = [email protected] 
sender = [email protected]

Fail2ban Jails

Fail2ban uses the concept of jails. A jail describes a service and includes filters and actions. Log entries matching the search pattern are counted, and the corresponding actions are executed when a predefined condition is met.

Fail2ban ships with some jail for different services. You can also create your jail configurations.

By default, only the ssh jail is enabled. To enable a jail, you need to add enabled = true after the jail title. The following example shows how to allow the proftpd jail:

enabled = true port = ftp,ftp-data,ftps,ftps-data 
logpath = %(proftpd_log)s 
backend = %(proftpd_backend)s

Note: The ProFTP needs to be installed first – see this section.

The settings we discussed in the previous section can be set per jail. Here is an example:

enabled = true 
maxretry = 3 
findtime = 1d 
bantime = 4w 
ignoreip =

The filters are located in the /etc/fail2ban/filter.d directory, stored in a file with the same name as the jail. You can fine-tune the filters if you have a custom setup and experience with regular expressions.

Each time you edit a configuration file, you need to restart the Fail2ban service for changes to take effect:

# sudo systemctl restart fail2ban

Fail2ban Client

Fail2ban ships with a command-line tool named fail2ban-client which you can use to interact with the Fail2ban service.

To view all available options, invoke the command with the -h option:

# fail2ban-client -h

This tool can ban/unban IP addresses, change settings, restart the service, and more. Here are a few examples:

Check the jail status:

# sudo fail2ban-client status
# sudo fail2ban-client status sshd

Unban an IP:

sudo fail2ban-client set sshd unbanip

Ban an IP:

sudo fail2ban-client set sshd banip

Web Server and Websites

Disable the PHPinfo.php file

On Webmin, under Tools > File Manager. Find this file on /var/www/html/info.php and rename to info.php.bak

Modify the PHP.INI

Open the php.ini file located on /etc/php/7.4/fpm/

Find the following entries and change them to:

upload_max_filesize = 128M
post_max_size = 128M
max_execution_time = 600
max_input_vars = 2000

Run the following reload and then restart.

$ sudo systemctl start php7.4-fpm.service # <- start
$ sudo systemctl stop php7.4-fpm.service # <- stop it 
$ sudo systemctl restart php7.4-fpm.service # <- restart it
$ sudo systemctl reload php7.4-fpm.service # <- reload it

Configure Website with SSL

Create website folder structure as follow on /etc/www/


To avoid any permission issues, we can change the ownership of the domain document root directory to the apache user (www-data) :

# sudo chown -R www-data: /var/www/

Enable the SSL module in Apache

# sudo a2enmod ssl

Enabled the Rewrite module

sudo a2enmod rewrite

Restart the Apache.

systemctl restart apache2

Create this file called in /etc/apache2/sites-available folder.

Copy this content:

<VirtualHost *:80>

    ServerAdmin [email protected]
    DocumentRoot /var/www/

    <Directory /var/www/>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    RewriteEngine on
    RewriteCond %{SERVER_NAME} [OR]
    RewriteCond %{SERVER_NAME}
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]


<VirtualHost *:443>

    ServerAdmin [email protected]
    DocumentRoot /var/www/

    <Directory /var/www/>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    SSLEngine on
    SSLCertificateFile /etc/cloudflare/
    SSLCertificateKeyFile /etc/cloudflare/



The Cloudflare public and private keys need to be loaded on the server.

We have to include this code below; otherwise, Rewrite won’t work

<Directory /var/www/>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted

Copy info.php from /etc/www/html into /etcc/www/

Activate the test-domain site

# sudo a2ensite

Run the command to reload:

sudo systemctl reload apache2

Test the website by going to

Remove the info.php once it is OK.

Upgrading the PHP from 7.4 to 8.1

Go to the console:

To find out which version of PHP you are currently using, run:

php -v

Upgrading from PHP 7.x to PHP 8.x involves upgrading the PHP core and extensions. For example, if you use the PHP extension cURL, you must manually install the PHP 8 version at the end of this guide.

dpkg -l | grep php | tee packages.txt

Copy your results into a text file and keep it safe if you need to install the PHP 8 packages later.

In our case, we need to keep the PHP version handy as we found that one website can’t use PHP 8 due to a legacy WordPress template. So we do NOT uninstall PHP 7.4 and its extension.

See the article from – How to Upgrade from PHP 7.x to PHP 8 on Ubuntu |

sudo apt update
sudo apt upgrade

To install PHP 8.x, you can add Ondřej Surý’s PPA, which has the PHP 8 packages:

sudo apt install -y software-properties-common
sudo add-apt-repository ppa:ondrej/php
sudo apt update

Next, install PHP 8.1:

sudo apt install -y php8.1

Verify the installation:

php8.0 -v
php -v

Remember that changing the PHP CLI version does not change the version your web server uses. To use different PHP versions for different websites, you can use PHP-FPM (that’s why we used it) to handle this per site.

Install PHP 8 extensions:

sudo apt install php8.1-common php8.1-mysql php8.1-xml php8.1-xmlrpc php8.1-curl php8.1-gd php8.1-imagick php8.1-cli php8.1-dev php8.1-imap php8.1-mbstring php8.1-opcache php8.1-soap php8.1-zip php8.1-intl -y

As we use PHP-FPM, install the FPM:

sudo apt install php8.1-fpm -y

sudo a2enmod proxy_fcgi setenvif

sudo a2enconf php8.2-fpm

sudo systemctl reload apache2

Restart the Apache and start PHP-FPM 8.1 service:

sudo systemctl restart apache2 && sudo service php7.4-fpm restart

Check the service running:

ps -efH | grep php-fpm

Then add the sock file on /etc/php/8.1/fpm/pool.d/ based on www.conf for each site.

Change the site’s detail for Apache on /etc/apach2/sites-available/ for each site, especially:

Under VirtualHost:

<IfModule mod_fcgid.c>
    Options +ExecCGI
    FcgidConnectTimeout 20
    Alias /php8-fcgi /usr/lib/cgi-bin/php8-fcgi

    <FilesMatch \.php$>
        SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost"

Tested the site and checked the PHP version either via phpinfo or WordPress Tools and Server

Upgrading the PHP from 8.1 to 8.2

As the PHP 8 packages have been added, there is an update from Webmin for PHP 8.2 automatically. Checked the directory.

In this case, install the additional packages and php-FPM part and start the service.


Tuning MariaDB

NOTE: At this point, we only apply these on Development/Testing VMs. We are not used to production as yet.

Linux Kernel Settings – IO Scheduler

Most people are advised to use [noop] or [deadline] as recommended. As the new kernel and VM are related, the [none] seems the way to go.

To check:

# sudo cat /sys/block/sda/queue/scheduler

Note: You will see: [none] mq-deadline
The bracket [none] means being selected.

Resources Limits – Open Files Limit

Linux usually limits the number of file descriptors each process can open. This limit can easily exceed or impact performance for an active DB system. This system, by default, sets to 1024.

To check:

# ulimit -Sn
# ulimit -Hn

Noted down both values. Make the lower value the same as the high value.

Add the following on /etc/security/limits to increase the limit.conf

mysql soft nofile 65535
mysql hard nofile 65535

These limits will be applied after reboot. There is another way to use the changes without rebooting. Check:

Resource Limits – Core File Size

Linux limits the size of core files, as seen in the previous case. Again this has a soft and hard limit, and by default soft limit is set as 0, which effectively disables core file generation. To allow core file generation (other configurations needed for core dump generation), we can increase this value in /etc/security/limits.conf as:

mysql soft core unlimited 
mysql hard core unlimited

After the system reboot, mysql the user would be able to see the new values using ulimit command as:

# ulimit -Sc
# ulimit -Hc
Configure Swappiness

[NOTE: I don’t do this one yet]

Swappiness value in Linux determines how likely the system is to swap a page from memory to swap space configured on the system. Usually, the default value is set as 60, which can be checked from:

sysctl vm.swappiness

Its value can range from 0 to 100, where a lower value means a lower likeliness of swapping. On a database server running only MariaDB, we would want to reduce this value to 0 to avoid using swap as much as possible. Do note here that setting swappiness value of 0 should be done with caution, considering other system design factors. In case of high memory usage or I/O load, the kernel might kill the Out Of Memory (OOM) process.

Since a low swappiness setting is recommended for database workloads and, as such, for MariaDB databases, it is recommended to set swappiness to a value of 1. You can add the below line in /etc/sysctl.confto make this change persistent:

vm.swappiness = 1

The changes will take effect after the system reboot though you can always do it in advance using sysctl command:

sysctl -w vm.swappiness=1

Configure PHPMyAdmin

By default, the PHPMyAdmin is already installed as a part of the image.

On Webmin, under Tools > File Manager. Find this file on /etc/phpmyadmin/

Enter the following code at the last of that page.

$cfg['ForceSSL'] = true;

Activate the default-ssl site

# sudo a2ensite default-ssl.conf

Run the command to reload:

systemctl reload apache2

Tested the PHPMyAdmin page by opening up your browser:


To disable the phpmyadmin module:

sudo a2disconf phpmyadmin.conf

Run the command to reload:

systemctl reload apache2

To enable the phpmyadmin module:

sudo a2enconf phpmyadmin.conf

Run the command to reload:

systemctl reload apache2

Create Database

Log in to the console using Putty as a superuser


# mysql -u root -p

Enter the password.

Create the database.

> CREATE DATABASE `test-domaincomdb`;

Create the user.

> CREATE USER 'test-domaincomusr' IDENTIFIED BY 'mypassword';

Grant permissions to access and use the MySQL/MariaDB server. Only allow access from localhost (this is the most secure and common configuration you will use for a web application):

> GRANT USAGE ON *.* TO 'test-domaincomusr'@localhost IDENTIFIED BY 'mypassword';

To enable access (OPTIONAL) to the MySQL/MariaDB server from any other computer on the network:

> GRANT USAGE ON *.* TO 'test-domaincomusr'@'%' IDENTIFIED BY 'mypassword';

Grant all privileges to a user on a specific database.

> GRANT ALL privileges ON `test-domaincomdb`.* TO 'test-domaincomusr'@localhost;

As in the previous command, if you want the user to work with the database from any location, you will have to replace localhost with ‘%’.

Apply changes made.


Verify your new user has the correct permissions.

> SHOW GRANTS FOR 'test-domaincomusr'@localhost;     

Suppose you made a mistake at some point. In that case, you can undo all the steps above by executing the following commands, taking the precaution of replacing localhost with ‘%’ if you also changed it in the previous commands:

DROP USER test-domaincomusr@localhost;
DROP USER test-domaincomusr@%;
DROP DATABASE test-domaincomdb;

Login to https://{}/phpmyadmin

Login as MariaDB root or any superuser

Check the database.

Add the database name

Export the database

To export the database:

mysqldump -u username -p database_name > database-backup.sql

Extract the database

Sometimes you need to extract the .sql.gz file format. To do this:

gzip -d database-backup.sql.gz

Import the database

To import:

mysql -u username -p new_database < database-backup.sql


Install ProFTPD

We disabled this service as no longer needed. Transferring file can be done via Webmin.

Open up the Webmin.

Under the Search, type in FTP. Select the ProFTPD Server option.

On the ProFTPD Server page, click the Install Now button.

Click the Install Now button again.

Mail Server

Install Profix

Open up the Webmin.

Under the Search, type in Mail. Select the Profix Mail Server option.

On the Profix Mail Server page, click the Install Now button.

Test the sending email by going to Webmin and Webmin Configuration option.

Select the Sending Email option.

On the “Send message to” field, enter the email address.

Click the Send Email button.

Check the email to see whether it was received or not.

Using the Cron jobs, the cron job will send a notification via email internally. To check this, click the User Mailboxes options.

Review the list, and if there is an email, it will show this next to the username.

Testing Mailgun

Install Swaks – it is an SMTP of CURL:

curl -o swaks

Set the permissions for the script so you can run it:

chmod +x swaks

It’s based on Perl, so install Perl

sudo apt-get -y install perl

Test the sending:

./swaks --auth \
        --server \
        --au postmaster@YOUR_DOMAIN_NAME \
        --ap 3kh9umujora5 \
        --to [email protected] \
        --h-Subject: "Hello" \
        --body 'Testing some Mailgun awesomness!'


System Backup via Webmin

This will back up specific system folders into /backups/system. Then it will have another job to backup to FTP Storage. See the FTP backup below.

Create a backup structure:


Run the Webmin on the browser.

Go to System > Filesystem backup

Create a backup:

Set directories to backup:

Ensure all the folders especially such as /scripts

Under the Backup, ensure to put the folder and file name, i.e. /backups/system/system_backup.tar.gz

Click the Save button.

SQL Backup

Creating manual backups in a production environment can be tedious. Luckily, there is a Utility called Automysqlbackup that we can download from Ubuntu’s repository. The package uses cron jobs to schedule backups at different intervals without manual intervention.

To download the utility, run the command below:

# sudo apt-get install automysqlbackup

Then to take on-demand backups, run the command below:

# sudo automysqlbackup

Note: If you get an access denied error, you need to enter on MySQL username and password on: /etc/mysql/debian.cnf. Ensure to double-quote the password.

You can list the content of the daily backup folder by running the command below:

# sudo ls -a /var/lib/automysqlbackup/daily

You can customize Automysqlbackup utility by editing its configuration file located at “/etc/default/automysqlbackup” by running the command below:

# sudo nano /etc/default/automysqlbackup

The utility organises the MySQL backup files under the “/var/lib/automysqlbackup” directory.


This will backup the “/var/default/automysqlbackup” folder into “/backups/automysqlbackup”. Then it will have another job to backup to FTP Storage. See the FTP backup below.

Create a backup structure:


Create bash file – under /scripts :

DAYS_TO_KEEP=7    # 0 to keep forever

# Create the backup folder
if [ ! -d $BACKUP_PATH ]; then
  mkdir -p $BACKUP_PATH

if [ "$(pwd)" != "$SOURCE_PATH" ] ; then
  echo "Failed to change directory to root of source path"

for folder in * ; do
  if [[ -d $folder && ! -L "$folder" ]]; then
    echo "Found folder folder: $folder"
    date=$(date -I)
    tar -cvpzf $BACKUP_PATH/$date-$folder.tar.gz $folder

# Delete old backups
if [ "$DAYS_TO_KEEP" -gt 0 ] ; then
  echo "Deleting backups older than $DAYS_TO_KEEP days"
  find $BACKUP_PATH/* -mtime +$DAYS_TO_KEEP -exec rm {} \;

Make the script executable with the following command:

sudo chmod +x

WWW Backups

This will backup /var/www folder into /backups/www. Then it will have another job to backup to FTP Storage. See the FTP backup below.

Create a backup structure:


Create bash file – under /script

DAYS_TO_KEEP=7    # 0 to keep forever

# Create the backup folder
if [ ! -d $BACKUP_PATH ]; then
  mkdir -p $BACKUP_PATH

# change into the web root directory
cd "$WWW_PATH"
if [ "$(pwd)" != "$WWW_PATH" ] ; then
  echo "Failed to change directory to root of web path"

for website in * ; do
  if [[ -d $website && ! -L "$website" ]]; then
    echo "Found website folder: $website"
    date=$(date -I)
    tar -cvpzf $BACKUP_PATH/$date-$website.tar.gz $website

# Delete old backups
if [ "$DAYS_TO_KEEP" -gt 0 ] ; then
  echo "Deleting backups older than $DAYS_TO_KEEP days"
  find $BACKUP_PATH/* -mtime +$DAYS_TO_KEEP -exec rm {} \;

Make the script executable with the following command:

sudo chmod +x

FTP Backups

This will back up the /backups folder into the FTP site via lftp command. Install the lftp component as it has mirror functionality – easy to copy folder via FTP.

Create bash file – under /script :

TODAY=$(date --iso)
RMDATE=$(date --iso -d '7 days ago')             
lftp -u $USER,$PASSWORD $HOST << EOF                  
rm -r /backups/${TODAY}
mkdir /backups/${TODAY}      
cd /backups/${TODAY}  
lcd /backups
mirror -R
rm -r /backups/${RMDATE}

Make the script executable with the following command:

sudo chmod +x

To check the size of the folder on FTP:

lftp -u testusername,testpassword 

du -h

Note: See the last entry “.” and that will be the total folder size


The requirement is to have two identical servers – with the same OS. Let’s say we called the source server and target server. In our case, we do cross-synchronisation meaning the source server will sync to the target server, and the target server will sync to the source server.

As the blog is written, there was an issue with copying some files (www-data ownership with 600 permission on Wordfence wflog folder) using anotherroot user as sudo. We found that due to conflicting with Lockdown SSH Logins with No Password. We have to lowered down the security by allowing root, however, we still implemented the key pair between two servers. There is a still have a safety which is no password implemented and the keypair only distributed between 2 servers. 

Log in to the source server as anotherroot account, to install:

sudo apt-get install -y rsync

If you lock down SSH login without a password –, you need to undo this to:

PermitRootLogin yes
PasswordAuthentication yes

On the source server, then create a keygen for the root user:

su root
ssh-keygen -q -P ""

Note: This will create key pair files under /root/.ssh/

On the source server, then copy the id to the target server:

ssh-copy-id -p 222 root@ip-target-server

On the target server, do the same process as above (under the lockdown SSH, create a key pair, etc, above).

On the target server, check the /root/.ssh/authorised_keys … open this with notepad – usually, it will have two entries (one for the remote local machine and one from the source server)

Create a folder on the target server for the targeted sync:


Back to the Source Server, test the ssh connection to the target server, which should have no password prompt.

ssh -p 222 anotherroot@ip-target-server

Ensure to log out from this. Otherwise, it is confusing which server you logged into.

To sync:

rsync -avP --delete -e "ssh -p 222" /var/www/ root@ip-target-server:/var/www_rsync_target/

Note: This will copy all /var/www/ files to the target server on /var/www_rsync_target/ The –delete command will delete the target server if the files get deleted on the source server. With the -a command, it will copy if the files have changed.

Setup the Cron

We will open the crontab configuration file using nano for editing purposes. The file is located at /etc/crontab. Run the command below to open it:

# sudo nano /etc/crontab

Set to:

0 0 * * * root automysqlbackup

To find more about the configuration, check this:





  • With introducing Rsync between 2 servers, the lockdown SSH Logins with No Password need to be lowered by allowing root access. I need further research.
  • The Webmin’s Filesystem backup is consistently failed. Not sure what caused this. This backup uses using Perl fsdump script. At the observation stage, it seems it’s related to the setting where “Ignore read errors on files – No”.
    The note said: When this option is set to Yes, any errors reading files that should be included in the backup will be ignored. It helps skip the useless error file changes as we read it, but it may mask permission or file corruption problems.
    We set it to Yes and waited and see the result.
Webmin Filesystem Backup options


  • Error: [Fri Jul 30 06:46:14.672690 2021] [proxy_fcgi:error] [pid 45511:tid 140579765737216] [client] AH01071: Got error ‘Primary script unknown’
    Run: systemctl restart php7.4-fpm systemctl restart apache2


Install LAMP Stack on Ubuntu

Add an additional Administrator account (similar like root)

Install Apache Webserver on Ubuntu

Hosting Website on Ubuntu

Configure Phpmyadmin on Webmin

Configure CloudFlare Certificate into Apache and Configuring Port 443 and Redirection

Configuring a Subdomain in Apache2

MariaDB and Grant Permission

MariaDB Tuning

MariaDB – Error on Log in Troubleshoot

Backup with MySQLDump


Install Clam AntiVirus

Install Fail2Ban

Fail2Ban – Change the SSH port related

Fail2Ban – Unban IP

Install UFW

Change the Webmin port

Schedule backup

Backup Files to Remote FTP

Check crontab log

Create a backup in Linux with Webmin

Monitor Changes Directory Tree

How to Run Script on Startup

The End

If you have reached the end of this article, congratulation. Hopefully, I have highlighted what you need to know to set up and configure Contabo VPS.

*** Let's Discuss ***
I also would love to hear about how you deal with the situation and what IT-related challenges you might be facing. Please feel free to leave us a comment below this article or you can contact us on the Dewacorp website for your IT support, application integration, application development, or other IT-related. Alternatively, you can casually have a discussion on Dewachat. Let us know if you have any questions that we can help with!

I write this so that this is not a fixed article. Like in this journey, I learn as I go, and I re-write some parts of the article, so please keep pinging with this article or any tech posts.

Big thank you for the photo by Negative Space from Pexels.

 *** Take a Look *** 
To boost your product and service to the broader web community, you can visit our Dewalist classified website – home to 36,000+ active users and 51,000+ one year of active advertising so far. 

If you like this post, please check out our other related posts:

*** Try it Out ***
To solve, maintain and monitor Search Engine Optimisation (SEO) and other web challenges, check out our Dewagear tools website – home to 50+ tools so far.

If you love this article or any tech posts and you would like to receive an update of this article or our latest post, please sign up for the form below:

Newsletter signup

This is a newsletter for tech, creative, gadgets, games and crypto.

Please wait...

Thank you for sign up!



About Author

Valdy founded, a helpdesk and IT Services provider that has taken care of nearly 40,000 customer service requests in the past 15 years - nearly 2,600 per year. He also created the growing - a classified website that has close to 110,000 page views and 9,000+ user visits per month with 61,000+ yearly published ads and 47,000 active registered users. He blogs for the ever-growing, a tech blog that gets around 20,000 impressions per month with 700 clicks and 10 average pages first impressions on Google Search. You can reach him on the Contact Us page, social media links below or

You may also like


Guy Kawasaki shared his The Art of the Start Insight @ TiECon 2006 for entrepreneurship startup.

Insights Guy Kawasaki shared his The Art of the Start Insight @ TiECon 2006 for entrepreneurship startup. Here are 10

Dev-Notes: Exploring ASP.NET MVC

Code First method build from scratch Make a backup of /Model files and /Migrations and delete xxx_Initial.cs one, delete all files