Web presence step by step Chapter 17: Using subdomains to host multiple websites under a single domain name

Previous step Chapter 16: Using a script to automate the creation of a virtual host on an Apache web server
Next step: Chapter 18: Configuring a residential router to forward ports to the internal IP address of an on-premises server

Web presence step by step is a series of posts that show you to how to build a web presence.

In this post, we learn how to declare multiple subdomain names under a single domain name for use as virtual host names, for multiple separate websites.

Understanding virtual hosts and their relation to domain and subdomain names

A shared LAMP web server can host multiple websites, or “virtual hosts,” named after domain and subdomain names.

You may wish to host a web-based application like MyBB or Nextcloud as its own website, rather than as a subdirectory of an existing website, without purchasing an additional domain name.

Multiple subdomains can be hosted on the same web server, or on different web servers.

The bare domain and the www subdomain

A virtual host can be identified by a “bare” domain name like “webpresencestepbystep.com,” or by a subdomain name, like “www.webpresencestepbystep.com” — by convention, the www subdomain prefix points to the same content as the “bare” domain name.

Additional subdomains like “community” and “media”

Additional subdomain names can be declared like “community.webpresencestepbystep.com” and “media.webpresencestepbystep.com” – these subdomains can point to separate websites on the same web server, or on different web servers.

Understanding domain and subdomain names and their relation to Domain Name System (DNS) zone files

As we can see in the DNS zone for the domain, the host names “@” (“bare domain”) and “community” are A declarations associated with the IP address of web server A.

The host name “www” is a CNAME declaration associated with the host name “@” so implicitly is associated with the IP address of web server A.

The host name “media” is an A declaration associated with the IP address of web server B.

2 websites on web server A, 1 website on web server B, all as subdomains of a single domain name

By using multiple subdomains of the same domain name, 3 separate websites can be declared, with 2 websites hosted on web server A, and 1 website hosted on web server B, without the need to purchase additional domain names.

Bare domain and subdomain www on web server A

webpresencestepbystep.com and www.webpresencestepbystep.com on web server A

A note about www and CNAME

the subdomain “www” host name is a canononical name (“CNAME”) of the @ host name, which identifies the “bare” domain. This means that www.webpresencestepbystep.com will resolve to the same IP address as webpresencestepbystep.com

Virtual host profiles

/etc/apache2/sites-available/webpresencestepbystep.com.conf:

# generated 2021/05/18 19:42:53 EDT by addvhost.php
<VirtualHost *:80>
<IfModule mpm_itk_module>
AssignUserID webpresencestepbystep_com webpresencestepbystep_com
</IfModule>
ServerName webpresencestepbystep.com
ServerAlias www.webpresencestepbystep.com
DocumentRoot /usr/web/webpresencestepbystep_com/webpresencestepbystep.com
ServerAdmin info@yourdomain.com
CustomLog /var/log/apache2/webpresencestepbystep.com-access_log combined
ErrorLog /var/log/apache2/webpresencestepbystep.com-error_log
RewriteEngine on
RewriteCond %{SERVER_NAME} =webpresencestepbystep.com [OR]
RewriteCond %{SERVER_NAME} =www.webpresencestepbystep.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

/etc/apache2/sites-available/webpresencestepbystep.com-le-ssl.conf:

<IfModule mod_ssl.c>
<VirtualHost *:443>
<IfModule mpm_itk_module>
AssignUserID webpresencestepbystep_com webpresencestepbystep_com </IfModule>
ServerName webpresencestepbystep.com
ServerAlias www.webpresencestepbystep.com
DocumentRoot /usr/web/webpresencestepbystep_com/webpresencestepbystep.com ServerAdmin info@yourdomain.com CustomLog /var/log/apache2/webpresencestepbystep.com-access_log combined ErrorLog /var/log/apache2/webpresencestepbystep.com-error_log Include /etc/letsencrypt/options-ssl-apache.conf SSLCertificateFile /etc/letsencrypt/live/linuxstepbystep.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/linuxstepbystep.com/privkey.pem
</VirtualHost>
</IfModule> 

Website

Subdomain community on web server A

community.webpresencestepbystep.com on the same IP address, on web server A

Virtual host profiles

/etc/apache2/sites-available/community.webpresencestepbystep.com.conf:

# generated 2021/05/29 12:45:14 EDT by addvhost.php
<VirtualHost *:80>
<IfModule mpm_itk_module>
AssignUserID community_webpresencestepbystep_ community_webpresencestepbystep_
</IfModule>
ServerName community.webpresencestepbystep.com
DocumentRoot /usr/web/community_webpresencestepbystep_/community.webpresencestepbystep.com
ServerAdmin info@yourdomain.com
CustomLog /var/log/apache2/community.webpresencestepbystep.com-access_log combined
ErrorLog /var/log/apache2/community.webpresencestepbystep.com-error_log
RewriteEngine on
RewriteCond %{SERVER_NAME} =community.webpresencestepbystep.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

/etc/apache2/sites-available/community.webpresencestepbystep.com-le-ssl.conf

<IfModule mod_ssl.c>
<VirtualHost *:443>
<IfModule mpm_itk_module>
AssignUserID community_webpresencestepbystep_ community_webpresencestepbystep_
</IfModule>
ServerName community.webpresencestepbystep.com
DocumentRoot /usr/web/community_webpresencestepbystep_/community.webpresencestepbystep.com
ServerAdmin info@yourdomain.com
CustomLog /var/log/apache2/community.webpresencestepbystep.com-access_log combined
ErrorLog /var/log/apache2/community.webpresencestepbystep.com-error_log
SSLCertificateFile /etc/letsencrypt/live/linuxstepbystep.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/linuxstepbystep.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

Website

Subdomain media on web server B

media.webpresencestepbystep.com on a different IP address, on web server B

Virtual host profiles

/etc/apache2/sites-available/media.webpresencestepbystep.com.conf:

# generated 2021/05/29 17:12:33 UTC by addvhost.php
<VirtualHost *:80>
<IfModule mpm_itk_module>
AssignUserID media_webpresencestepbystep_com media_webpresencestepbystep_com
</IfModule>
ServerName media.webpresencestepbystep.com
DocumentRoot /usr/web/media_webpresencestepbystep_com/media.webpresencestepbystep.com
ServerAdmin info@yourdomain.com
CustomLog /var/log/apache2/media.webpresencestepbystep.com-access_log combined
ErrorLog /var/log/apache2/media.webpresencestepbystep.com-error_log
RewriteEngine on
RewriteCond %{SERVER_NAME} =media.webpresencestepbystep.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

/etc/apache2/sites-available/media.webpresencestepbystep.com-le-ssl.conf

<IfModule mod_ssl.c>
<VirtualHost *:443>
<IfModule mpm_itk_module>
AssignUserID media_webpresencestepbystep_com media_webpresencestepbystep_com
</IfModule>
ServerName media.webpresencestepbystep.com
DocumentRoot /usr/web/media_webpresencestepbystep_com/media.webpresencestepbystep.com
ServerAdmin info@yourdomain.com
CustomLog /var/log/apache2/media.webpresencestepbystep.com-access_log combined
ErrorLog /var/log/apache2/media.webpresencestepbystep.com-error_log
SSLCertificateFile /etc/letsencrypt/live/media.webpresencestepbystep.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/media.webpresencestepbystep.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

Website

Previous step Chapter 16: Using a script to automate the creation of a virtual host on an Apache web server
Next step: Chapter 18: Configuring a residential router to forward ports to the internal IP address of an on-premises server

Web presence step by step Chapter 16: Using a script to automate the creation of a virtual host on an Apache web server

Previous step: Chapter 15: Using dwservice.net to provide remote technical support as an alternative to TeamViewer
Next step: Chapter 17: Using subdomains to host multiple websites under a single domain name

Web presence step by step is a series of posts that show you to how to build a web presence.

In this chapter we install and use a script to automate the creation of a virtual host on an Apache web server.

A PHP script that automates the creation of a virtual host under Apache

This script collects and validates inputs, then executes the commands to create a virtual host under Apache.

A note about the source code view below

For formatting reasons, the text is limited to a fixed width. To fully view the text, you can scroll to the right to see the ends of lines, or use the print view for this blog post.

To view the source code in an another text editor, download and uncompress the zip file described below, or select and copy the text from the source code example below, and paste the text into a file on your computer called “addvhost.php”

Consider copying the file to your Apache web server’s /usr/bin directory with a chmod of 755 so that it can be executed from the system path. Steps to do so are included in the procedure below.

Saving the PHP script to a file called addvhost.php

Download this zip file:

https://blog.gordonbuchan.com/files/addvhost.zip

Uncompress the zip file to extract the file “addvhost.php” then copy the file to your Apache web server.

Source code of the script

Scroll right to see the ends of lines.

#!/usr/bin/php
<?PHP
// addvhost.php
// v0102
// updated to variable-ize vhostip as a base setting
// creates a virtual host under Apache
// Gordon Buchan 20210512 https://gordonbuchan.com
// MIT License https://mit-license.org
// tested on Ubuntu 20.04, may work on Debian
// directory structure allows for chroot jails for SFTP:
// in a jail you do not own your home directory, only your webdir
// tip: apt install finger whois
// ////////////////////////////////////////////////////////////////
// start summary
// initialize base settings in variables ie bvhwb
// ask for vhostsubdomain, vhostusername, vhostpassword
// infer vhosthomedir, vhostwebdir by convention
// create user, home directory, password
// create directory
// create index.php document
// chown vhosthomedir as root:root
// chown vhostwebdir as vhostusername:vhostusername
// chmod vhostwebdir
// create virtual host file
// enable virtual host
// echo suggestion that client restart apache, run certbot --apache, restart apache
// end summary

// ////////////////////////////////////////////////////////////////
// start base settings

$bvhostconfdir = "/etc/apache2/sites-available";
$bvhwb = "/usr/web";
$restartcommandstr = "systemctl apache2 restart";
$vhostenablecommandstr = "a2enmod";
$echoplaintextpasswords = TRUE;
$logplaintextpassword = TRUE;
$vhostserveradmin = "info@yourdomain.com";
// tip: could be "xxx.xxx.xxx.xxx"
$vhostip = "*";

// ////////////////////////////////////////////////////////////////
// end base settings

// ////////////////////////////////////////////////////////////////
// start function sink
 
// start polyfill
// str_contains() polyfill for pre PHP8: code for this function taken from php.net
if (!function_exists('str_contains')) {
	function str_contains(string $haystack, string $needle): bool 
	{
	return '' === $needle || false !== strpos($haystack, $needle);
	} // end function str_contains()
}
// ////////////////////////////////////////////////////////////////
// end polyfill

// validate functions
// We will be using the readline() function to ask questions on the command line.
// These functions allow us to do rich validation within a while statement to trap
// the readline in a loop until our conditions are satisfied.
// We will also echo text to the console with reasons for rejection to assist the client.
// For example: bad string format, vhost appears to exist already, etc.

// ////////////////////////////////////////////////////////////////
function vhostsubdomainverify($vhostsubdomainstr) {
global $bvhwb;
global $bvhostconfdir;

//assume true until proven false
$returnval = TRUE;

// is the string clean?
// note that "-" hyphen character is permitted, not part of symbol sieve
if ( preg_match('/[\'^£$%&*()}{@#~?><>,|=_+¬]/', $vhostsubdomainstr) ) {
	$returnval = FALSE;
	echo "string has special character that is not permitted\n";
}

// string does not contain a period symbol
if (!str_contains($vhostsubdomainstr,".") ) {
	$returnval = FALSE;
	echo "string does not contain a \".\" period symbol.\n";
}

// string contains two period symbols in a row
if (str_contains($vhostsubdomainstr,"..") ) {
	$returnval = FALSE;
	echo "string contain two \"..\" period symbols in a row.\n";
}

// string contains leading period symbol
$strlen = strlen($vhostsubdomainstr);
$begsample = substr($vhostsubdomainstr,0,1);
if ($begsample == ".") {
	$returnval = FALSE;
	echo "string begins with a \".\" period symbol.\n";
}

// string contains trailing period symbol
$endlen = strlen($vhostsubdomainstr);
$endsample = substr($vhostsubdomainstr,($endlen - 1),1);
if ($endsample == ".") {
	$returnval = FALSE;
	echo "string ends with a \".\" period symbol.\n";
}

// does the vhostsubdomain already exist?
$vhostsubdomainstrund = str_replace(".","_",$vhostsubdomainstr);
clearstatcache();
if (is_dir("$bvhwb/$vhostsubdomainstrund") ) {
	$returnval = FALSE;
	echo "webdir for proposed vhost already exists.\n";
} else {
} // end if (is_dir()

$grepforvhost1str = "grep -i 'ServerName $vhostsubdomainstr' $bvhostconfdir/*";
$grepforvhost2str = "grep -i 'ServerAlias $vhostsubdomainstr' $bvhostconfdir/*";
$grepforvhost1res = shell_exec($grepforvhost1str);
$grepforvhost2res = shell_exec($grepforvhost2str);

// if the string has contents something was there for the grep to find
if ($grepforvhost1res || $grepforvhost2res) {
	echo "subdomain appears to be part of an existing virtual host\n";
	$returnval = FALSE;
}

return $returnval;
} // end function vhostsubdomainverify()

// ////////////////////////////////////////////////////////////////
function prependverify($prependverify) {

// let us make our tests and comparisons case-insensitive
$lowerpv = strtolower($prependverify);

if ( ( $lowerpv == "n") || ($lowerpv == "no") || ($lowerpv == "y") || ($lowerpv == "yes") ) {
	$returnval = TRUE;
} else {
	echo "please indicate n or no, y or yes\n";
	$returnval = FALSE;
}

return $returnval;
} // end function prependverify()

// ////////////////////////////////////////////////////////////////
function usernameverify($vhostusername) {

// force to lower-case
$vhostusername = strtolower($vhostusername);

// assume TRUE until proven false
$returnval = TRUE;

// is the string clean?
// note that "-" hyphen character is permitted, as is the "_" underscore character, not part of symbol sieve
if ( preg_match('/[\'^£$%&*()}{@#~?><>,|=+¬]/', $vhostusername) ) {
	$returnval = FALSE;
	echo "string has special character that is not permitted\n";
}

$vhunstrlen = strlen($vhostusername);

if ($vhunstrlen < 2) {
	echo "username should be minimum 2 characters\n";
	$returnval = FALSE;
}
if ($vhunstrlen > 32) {
	echo "username should be maximum 32 characters\n";
	$returnval = FALSE;
}

// what does finger return?
$fingerstr = shell_exec("finger $vhostusername 2>&1");

if (!str_contains("$fingerstr","no such user") ) {
	echo "finger found this username to already be in use\n";
	$returnval = FALSE;
}

return $returnval;
} // end function usernameverify()

// ////////////////////////////////////////////////////////////////
function passwordplainverify($passwordplain) {

// assume TRUE until proven false
$returnval = TRUE;

// we should do some tests here
// but mostly just for length, not all that fancy stuff.
// but: we will want to offer to auto-generate a plaintext password

$ppstrlen = strlen($passwordplain);

if ($ppstrlen < 8) {
	echo "password should be at least 8 characters\n";
	$returnval = FALSE;
}

return $returnval;
} // end function passwordplainverify()

// ////////////////////////////////////////////////////////////////
function genpassverify($genpassverify) {

// let us make our tests and comparisons case-insensitive
$lowergpv = strtolower($genpassverify);

if ( ( $lowergpv == "n") || ($lowergpv == "no") || ($lowergpv == "y") || ($lowergpv == "yes") ) {
	$returnval = TRUE;
} else {
	echo "please indicate n or no, y or yes\n";
	$returnval = FALSE;
}

return $returnval;
} // end function genpassverify()

// ////////////////////////////////////////////////////////////////
function genuserverify($genuserverify) {

// let us make our tests and comparisons case-insensitive
$lowerguv = strtolower($genuserverify);

if ( ( $lowerguv == "n") || ($lowerguv == "no") || ($lowerguv == "y") || ($lowerguv == "yes") ) {
	$returnval = TRUE;
} else {
	echo "please indicate n or no, y or yes\n";
	$returnval = FALSE;
}

return $returnval;
} // end function genuserverify()

// end function sink

// ////////////////////////////////////////////////////////////////
// start get information at command line: vhostsubdomain, vhostusername, vhostpassword
// also, generate and derive values
echo "\naddvhost.php\n";
echo "Add a virtual host to Apache\n\n";

// ask and validate inputs
// the readline is trapped in a loop until vhostsubdomainverify() is satisfied
// function will also echo text to the console with reasons for rejection to assist the client
// bad string format or vhost appears to exist already, etc.

// ////////////////////////////////////////////////////////////////
// vhostsubdomain

$vhostsubdomain = "";
while (!$vhostsubdomain || !vhostsubdomainverify($vhostsubdomain) ) {
    $vhostsubdomain = readline("Enter domain xxxxxxxx.xxx or subdomain xxxxxxxx.xxxxxxxx.xxx: ");
}

// putting this here because it is right after we have the $vhostsubdomain string, and just before we need it for $genuseranswer
// will also need this later for derived values like the $vhostwebdir

$vhostsubdomainund = str_replace(".","_",$vhostsubdomain);

// should we prepend with www. as well?

$prependanswer = "";
while (!$prependanswer || !prependverify($prependanswer) ) {
    $prependanswer = readline("Do you wish to prepend the subdomain www.$vhostsubdomain as well (n/y)? ");
}

$prependanswer = strtolower($prependanswer);

// ////////////////////////////////////////////////////////////////
// vhostusername

// default username
// should we offer to automatically generate a username based on the subdomain host name?

$genuseranswer = "";
while (!$genuseranswer || !genuserverify($genuseranswer) ) {
    $genuseranswer = readline("Generate a username? ");
}

$genuseranswer = strtolower($genuseranswer);

if ( ($genuseranswer=="yes") || ($genuseranswer=="y") ) {
	// generate a username
	// we are counting on the novel construction of this name with _ modeled on subdomain

	$vhostusername = $vhostsubdomainund;
	$vhostusernamestrlen = strlen($vhostusername);
	// the unique stuff is closer to the front
	// so we will truncate to first 32 characters
	if ($vhostusernamestrlen > 32) {
		$vhostusername = substr($vhostusername,0,32);
	}

	// what does finger return?
	$fingerstr2 = shell_exec("finger $vhostusername 2>&1");
	if (!str_contains("$fingerstr2","no such user") ) {
		echo "finger found this username to already be in use\n";
		exit();
	}

} else {
	// the client said no to automatic generation of username so we will ask for one
	$vhostusername = "";
	while (!$vhostusername || !usernameverify($vhostusername) ) {
	    $vhostusername = readline("Enter username: ");
	}
} // end if ($genuseranswer=="yes")

// ////////////////////////////////////////////////////////////////
// vhostpasswordplain

// should we offer to automatically generate a plaintext password?

$genpassanswer = "";
while (!$genpassanswer || !genpassverify($genpassanswer) ) {
    $genpassanswer = readline("Generate a plaintext password? ");
}

$genpassanswer = strtolower($genpassanswer);

if ( ($genpassanswer=="yes") || ($genpassanswer=="y") ) {
	// generate a random plaintext password
	$vhostpasswordplain = "";
	$passwordlength = "8";
	$posscharsplain = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
	$posscharssymbols = '!@#%*?';
	$posscharspstrlen = strlen($posscharsplain);
	$posscharssstrlen = strlen($posscharssymbols);
	// first the plain characters
	for ($i=0;$i<($passwordlength-1);$i++) {
		$randomint = random_int(0,$posscharspstrlen-1);
		$randomchar = substr($posscharsplain,$randomint,1);
		$vhostpasswordplain .= $randomchar;
	} //end for $i
	// now the symbol character
		$randomint = random_int(0,$posscharssstrlen-1);
		$randomchar = substr($posscharssymbols,$randomint,1);
		$vhostpasswordplain .= $randomchar;
	// now shuffle the string so the symbol position moves and as bonus the string is different
	$vhostpasswordplain = str_shuffle($vhostpasswordplain);
} else {
	// the client said no to automatic generation of plaintext, so we will ask for one
	$vhostpasswordplain = "";
	while (!$vhostpasswordplain || !passwordplainverify($vhostpasswordplain) ) {
		$vhostpasswordplain = readline("Enter plaintext password: ");
	}
} // end if ($genpassanswer=="yes")

// ////////////////////////////////////////////////////////////////
// vhostpasswordhashed (transformation)

// yes, i tried password_hash() -- it did not work for SHA512, this does.
// tip: apt install whois to get mkpasswd command
$vhostpasswordhashed = shell_exec("mkpasswd -m sha-512 $vhostpasswordplain");
// remove linefeed from the string
$vhostpasswordhashed = str_replace("\n","",$vhostpasswordhashed);

// ////////////////////////////////////////////////////////////////
// end get information at command line: vhostsubdomain, vhostusername, vhostpassword

// ////////////////////////////////////////////////////////////////
// start print collected values

$vhosthomedir = "$bvhwb/$vhostusername";
$vhostwebdir = "$bvhwb/$vhostusername/$vhostsubdomain";

echo "\nvalues collected, generated, and derived\n\n";

echo "vhostsubdomain: $vhostsubdomain\n";
echo "prependanswer: $prependanswer\n";
echo "vhostusername: $vhostusername\n";
echo "genpassanswer: $genpassanswer\n";
if ($echoplaintextpasswords) {
	echo "vhostpasswordplain: $vhostpasswordplain\n";
}
echo "vhostpasswordhashed: $vhostpasswordhashed\n";
echo "vhosthomedir: $vhosthomedir\n";
echo "vhostwebdir: $vhostwebdir\n";

// ////////////////////////////////////////////////////////////////
// end print collected values

// ////////////////////////////////////////////////////////////////
// start engine section

// ////////////////////////////////////////////////////////////////
// create the $vhostusername with $vhosthomedir and $vhostpasswordhashed

// build the string, look at the string, then maybe do a shell_exec of the string
$shelluseraddstr = "useradd -m -d '$vhosthomedir' '$vhostusername' -s '/usr/bin/bash' -p '$vhostpasswordhashed'";

// disable for production
// echo "shelluseraddstr: $shelluseraddstr\n";

// so it will always be declared
$shelluseraddret = "";
// disable for testing other conditions without committing to this
$shelluseraddret = shell_exec($shelluseraddstr);

//echo "shelluseraddret: $shelluseraddret\n";
// non-null (non-0) exit value from shell indicates an error
if ($shelluseraddret) {
	echo "ERROR: there was a problem executing the shell command to create the vhostusername $vhostusername. Stopping.\n";
	exit();
} else {
	//echo "SUCCESS: the vhostusername $vhostusername was created\n";
}

echo "\n";

// ////////////////////////////////////////////////////////////////
// mkdir $vhostwebdir

$mkdirvhostwebdirret = mkdir($vhostwebdir,0775,TRUE);

if (!$mkdirvhostwebdirret) {
	echo "ERROR: there was a problem creating the vhostwebdir $vhostwebdir. Stopping\n";
	exit();
} else {
	//echo "SUCCESS: the vhostwebdir $vhostwebdir was created.\n";
}

// ////////////////////////////////////////////////////////////////
// fwrite $vhostwebdir/index.php

$indexfilecontents = "<?PHP\n\necho \"<p>$vhostsubdomain</p>\\n\";\n";

$fh1 = fopen("$vhostwebdir/index.php","w");
$filesuccess1 = fwrite($fh1,$indexfilecontents);
fclose($fh1);
 
if ($filesuccess1) {

	//chown root $vhosthomedir

	$vhosthomedirownretu1 = chown("$vhosthomedir","root");
	if ($vhosthomedirownretu1) {
		//echo "SUCCESS chown root $vhosthomedir\n";
	} else {
		echo "ERROR chown root $vhosthomedirdir not successful\n";
		exit();
	}

	//chgrp root $vhosthomedir

	$vhosthomedirownretg1 = chgrp("$vhosthomedir","root");
	if ($vhosthomedirownretg1) {
		//echo "SUCCESS chgrp root $vhosthomedir\n";
	} else {
		echo "ERROR chgrp root $vhosthomedirdir not successful\n";
		exit();
	}

	//echo "SUCCESS indexfile written to file: $vhostwebdir/index.php\n";

	// chmod the $vhostwebdir
	chmod("$vhostwebdir", 0755);
	$vhostwebdirperms = substr(sprintf('%o', fileperms("$vhostwebdir")), -4);
	//echo "vhostwebdirperms: $vhostwebdirperms\n";
	if ($vhostwebdirperms == "0755") {
		//echo "SUCCESS chmod 755 $vhostwebdir\n";
	} else {
		echo "ERROR chmod 755 $vhostwebdir not successful. Stopping.\n";
	exit();
	}

	// chown $vhostusername $vhostwebdir
	$vhostwebdirownretu1 = chown("$vhostwebdir",$vhostusername);
	if ($vhostwebdirownretu1) {
		//echo "SUCCESS chown $vhostusername $vhostwebdir\n";
	} else {
		echo "ERROR chown $vhostusername $vhostwebdir not successful\n";
		exit();
	}

	// chgrp $vhostusername $vhostwebdir
	$vhostwebdirownretg1 = chgrp("$vhostwebdir",$vhostusername);
	if ($vhostwebdirownretu1) {
		//echo "SUCCESS chgrp $vhostusername $vhostwebdir\n";
	} else {
		echo "ERROR chgrp $vhostusername $vhostwebdir not successful\n";
		exit();
	}

	// chmod the $vhostwebdir/index.php
	chmod("$vhostwebdir/index.php", 0755);
	$vhostindexperms = substr(sprintf('%o', fileperms("$vhostwebdir/index.php")), -4);
	//echo "vhostindexperms: $vhostindexperms\n";
	if ($vhostindexperms == "0755") {
		//echo "SUCCESS chmod 755 $vhostwebdir/index.php\n";
	} else {
		echo "ERROR chmod 755 $vhostwebdir/index.php not successful. Stopping.\n";
	exit();
	}

	// chown $vhostusername $vhostwebdir/index.php
	$vhostindexownretu1 = chown("$vhostwebdir/index.php",$vhostusername);
	if ($vhostindexownretu1) {
		//echo "SUCCESS chown $vhostusername $vhostwebdir/index.php\n";
	} else {
		echo "ERROR chown $vhostusername $vhostwebdir/index.php not successful\n";
		exit();
	}

	// chgrp $vhostusername $vhostwebdir/index.php
	$vhostindexownretg1 = chgrp("$vhostwebdir/index.php",$vhostusername);
	if ($vhostindexownretu1) {
		//echo "SUCCESS chgrp $vhostusername $vhostwebdir/index.php\n";
	} else {
		echo "ERROR chgrp $vhostusername $vhostwebdir/index.php not successful\n";
		exit();
	}
} else {
	echo "ERROR indexfile not written to file: $vhostwebdir/index.php\n";
	exit();
}

// ////////////////////////////////////////////////////////////////
// fwrite $bvhostconfdir/$vhostsubdomain.conf

$timestring = date("Y/m/d H:i:s T");

$vhostconffilecontents  = "# generated $timestring by addvhost.php\n";
$vhostconffilecontents .= "<VirtualHost $vhostip:80>\n";
$vhostconffilecontents .= "<IfModule mpm_itk_module>\n";
$vhostconffilecontents .= "\tAssignUserID $vhostusername $vhostusername\n";
$vhostconffilecontents .= "</IfModule>\n";
$vhostconffilecontents .= "ServerName $vhostsubdomain\n";
if ( ($prependanswer == "y") || ($prependanswer == "yes") ) {
	$vhostconffilecontents .= "ServerAlias www.$vhostsubdomain\n";
}
$vhostconffilecontents .= "DocumentRoot $vhostwebdir\n";
$vhostconffilecontents .= "ServerAdmin $vhostserveradmin\n";
$vhostconffilecontents .= "CustomLog /var/log/apache2/$vhostsubdomain-access_log combined\n";
$vhostconffilecontents .= "ErrorLog /var/log/apache2/$vhostsubdomain-error_log\n";
$vhostconffilecontents .= "</VirtualHost>\n";

// disable in production
// echo "vhostconffilecontents = \n$vhostconffilecontents\n";

// write the text file
$fh2 = fopen("$bvhostconfdir/$vhostsubdomain.conf","w");
$filesuccess2 = fwrite($fh2,$vhostconffilecontents);
fclose($fh2);
 
if ($filesuccess2) {
	//echo "SUCCESS virtual host config written to file: $bvhostconfdir/$vhostsubdomain.conf\n";
} else {
 	echo "ERROR virtual host config not written to file: $bvhostconfdir/$vhostsubdomain.conf\n";
 	exit();
}

// ////////////////////////////////////////////////////////////////
// shell_exec a2ensite $bvhostconfdir/$vhostsubdomain.conf

// so it will always be declared
$shella2enret = "";
// disable for testing other conditions without committing to this
$shella2enret = shell_exec("a2ensite $vhostsubdomain.conf");

//echo "shella2enret: $shella2enret\n";
// non-null (non-0) exit value from shell indicates an error
if ( str_contains($shella2enret,"ERROR") ) {
	//echo "ERROR: there was a problem executing the shell command to enable the vhostsubdomain $vhostsubdomain. Stopping.\n";
	exit();
} else {
	echo "SUCCESS: enabled vhostsubdomain $vhostsubdomain\n";
}

// ////////////////////////////////////////////////////////////////
// echo to console suggestion that systemctl restart apache2 be executed

echo "\n";
echo "Next steps:\n";
echo "This script did not restart apache2. That is up to you.\n";
echo "systemctl restart apache2\n";
echo "validate site on port 80\n";
echo "run certbot --apache to expand ssl cert\n";
echo "systemctl restart apache2\n";
echo "validate site on port 443\n";
echo "\n";

// ////////////////////////////////////////////////////////////////
// end engine section

Declaring the host name in DNS

Declare the host name in the DNS zone file for the domain:

Testing that the host name loads a “park page”

Use a web browser to visit the host name:

Executing commands as root

This chapter assumes that you are logged in as the root user. If you are not already root, escalate using this command:

sudo su

Installing the finger, whois, and unzip utilities

This script depends on the finger, whois, and unzip utilities.

Enter this command:

apt install finger whois unzip

Downloading and uncompressing the addvhost.zip file

Enter this command:

wget https://blog.gordonbuchan.com/files/addvhost.zip

Enter this command:

unzip addvhost.zip

Enter these commands:

mv addvhost.php /usr/bin
chmod 755 /usr/bin/addvhost.php

Enter this command:

addvhost.php

Completing the addvhost.php questionnaire, entering a username and a plaintext password

In this example, we decline the option to generate a username, and enter a value for the username. We also decline the option to generate a plaintext password, and enter a value for the plaintext password.

root@server01:~# addvhost.php

addvhost.php
Add a virtual host to Apache

Enter domain xxxxxxxx.xxx or subdomain xxxxxxxx.xxxxxxxx.xxx: webpresencestepbystep.ca
Do you wish to prepend the subdomain www.webpresencestepbystep.ca as well (n/y)? y
Generate a username? n
Enter username: webuserca
Generate a plaintext password? n
Enter plaintext password: password

values collected, generated, and derived

vhostsubdomain: webpresencestepbystep.ca
prependanswer: y
vhostusername: webuserca
genpassanswer: n
vhostpasswordplain: password
vhostpasswordhashed: $6$IdLp5YrW.Z3Tvnm$hlRvIBour47UcZrVm0QA2YgLp2z3C3e5W7PwiS3o.KbZz.mtFeCvWdew/eemdec3Wz9t.WEIuIm3Q2EKTuXYd1
vhosthomedir: /usr/web/webuserca
vhostwebdir: /usr/web/webuserca/webpresencestepbystep.ca

SUCCESS: enabled vhostsubdomain webpresencestepbystep.ca

Next steps:
This script did not restart apache2. That is up to you.
systemctl restart apache2
validate site on port 80
run certbot --apache to expand ssl cert
systemctl restart apache2
validate site on port 443

Enter this command:

systemctl restart apache2

Enter this command:

certbot --apache

Enter this command:

systemctl restart apache2

Visiting the virtual host

Use a web browser to visit the host name:

Completing the addvhost.php questionnaire, accepting a generated username and plaintext password

In this example, we accept the option to generate a username. We also accept the option to generate a plaintext password.

Take careful note of the plaintext password value, as shown in the “vhostpasswordplain” field.

root@server01:~# addvhost.php

addvhost.php
Add a virtual host to Apache

Enter domain xxxxxxxx.xxx or subdomain xxxxxxxx.xxxxxxxx.xxx: webpresencestepbystep.com
Do you wish to prepend the subdomain www.webpresencestepbystep.com as well (n/y)? y
Generate a username? y
Generate a plaintext password? y

values collected, generated, and derived

vhostsubdomain: webpresencestepbystep.com
prependanswer: y
vhostusername: webpresencestepbystep_com
genpassanswer: y
vhostpasswordplain: NQeQ2%VT
vhostpasswordhashed: $6$Woe9pPUwnXqUP$9RW60p6SSNfqLJSi4BeAyhe89mBpyTELk2/at7eJcKqou5Q9Y6Nti4P7EoyTV0CBfin6SxlvNHvkZjrpEGxxX0
vhosthomedir: /usr/web/webpresencestepbystep_com
vhostwebdir: /usr/web/webpresencestepbystep_com/webpresencestepbystep.com

SUCCESS: enabled vhostsubdomain webpresencestepbystep.com

Next steps:
This script did not restart apache2. That is up to you.
systemctl restart apache2
validate site on port 80
run certbot --apache to expand ssl cert
systemctl restart apache2
validate site on port 443

Enter this command:

systemctl restart apache2

Enter this command:

certbot --apache

Enter this command:

systemctl restart apache2

Visiting the virtual host

Use a web browser to visit the host name:

Previous step: Chapter 15: Using dwservice.net to provide remote technical support as an alternative to TeamViewer
Next step: Chapter 17: Using subdomains to host multiple websites under a single domain name

Web presence step by step Chapter 15: Using dwservice.net to provide remote technical support as an alternative to TeamViewer

Previous step: Chapter 14: Installing and configuring Live Helper Chat to add text chat support to a website
Next step: Chapter 16: Using a script to automate the creation of a virtual host on an Apache web server

Web presence step by step is a series of posts that show you to how to build a web presence.

In this chapter we install and configure dwservice.net to provide remote technical support as an alternative to TeamViewer.

dwservice.net allows a client to share their computer’s desktop so you can provide technical support

When you build and maintain technical systems, you need to support the clients of those systems, including customers, colleagues, and contractors. dwservice.net allows you to invite a client to share their computer’s desktop so you can provide technical support.

Creating an account on the dwservice.net service

Visit the dwservice.net site:

https://dwservice.net

Create a username and password for the dwservice.net site. You will need it later in this procedure.

dwservice.net provides client software for the Windows, MacOS, and Linux operating systems

This chapter contains sections describing how to install the dwservice.net client for Windows, MacOS, and Linux.

Windows

Visiting the website to download the installer on a Windows workstation

Visit the dwservice.net site:

https://dwservice.net

Click on “Download”:

Click on “Download”:

Right-click on the file name, click “Show in folder”:

Using the dwservice.net client on a Windows workstation in run-once mode

Double-click on the file to run the dwagent.exe setup program:

Select “Run,” click on “Next”:

View on the Windows workstation:

Accessing the Windows workstation from the dwservice.net site

Visit the dwservice.net site:

https://dwservice.net

Enter the username and password displayed on the Windows workstation running the dwservice.net client, click on “Sign in”:

Resources available on the Windows workstation

This page shows the resources available on the Windows workstation:

Screen (remote desktop)

Remote control of the desktop of the Windows workstation:

Files and Folders

Access to the filesystem on the Windows workstation:

Shell

Access to the operating system shell prompt on the Windows workstation:

Installing the dwservice.net client on a Windows workstation to enable unattended access

Right-click on the “dwagent.exe” file. Select “Run as administrator”:

Select “Install,” click on “Next”:

Select “Yes,” click on “Next”:

Select “Creating a new agent, click on “Next”:

Enter the username and password of a valid dwservice.net account in the “DWS user” and DWS password” fields. Enter a name to describe the workstation in the “Agent name” field. Click on “Next”:

Click on “Close”:

Visit the dwservice.net site:

https://dwservice.net

Enter the username and password of a valid dwservice.net account in the “DWS user” and DWS password” fields. Click on “Sign in”:

Click on “Agents”:

Click on the icon for the Windows workstation:

Resources available on the Windows workstation

This page shows the resources available on the Windows workstation:

Screen (remote desktop)

Remote control of the desktop of the Windows workstation:

Files and Folders

Access to the filesystem on the Windows workstation:

Shell

Access to the operating system shell prompt on the Windows workstation:

MacOS

Visiting the website to download the installer on a MacOS workstation

Visit the dwservice.net site:

https://dwservice.net

Click on “Download”:

Click on “Download”:

Click on “Allow”:

Locate the downloaded file, click on it:

This error message appears. Click on “OK”:

On the MacOS workstation, launch “System Preferences.” Click on “Security & Privacy”:

Click on “Open Anyway”:

Click on “Open”:

The dwservice.net package opens:

Click on “DWAgent”:

Click on “Run”:

View on the MacOS workstation:

Accessing the MacOS workstation from the dwservice.net site

Visit the dwservice.net site:

https://dwservice.net

Enter the username and password displayed on the MacOS workstation running the dwservice.net client, click on “Sign in”:

The following message appears on the MacOS workstation. Click on “Open System Preferences”:

Click on “Quit Now”:

On the MacOS workstation, restart the dwservice.net client.

Accessing the MacOS workstation from the dwservice.net site (again)

Visit the dwservice.net site:

https://dwservice.net

Enter the username and password displayed on the MacOS workstation running the dwservice.net client, click on “Sign in”:

Resources available on the MacOS workstation

This page shows the resources available on the MacOS workstation:

This image has an empty alt attribute; its file name is Screenshot-from-2021-05-01-06-49-07-1024x725.png

Screen (remote desktop)

Remote control of the desktop of the MacOS workstation:

Files and Folders

Access to the filesystem on the MacOS workstation:

Shell

Access to the operating system shell prompt on the MacOS workstation:

Installing the dwservice.net client on a MacOS workstation to enable unattended access

Click on “Open”:

Select “Install,” click on “Next”:

enter the “User Name” and “Password” for the MacOS workstation, click on “OK”:

Click on “Next”:

Select “Yes,” click on “Next”:

Select “Creating a new agent,” click on “Next”:

Enter the username and password of a valid dwservice.net account in the “DWS user” and DWS password” fields. Enter a name to describe the workstation in the “Agent name” field. Click on “Next”:

Click on “Close”:

View of the DWAgent Monitor on MacOS:

Click on “OK”:

Visit the dwservice.net site:

https://dwservice.net

Enter the username and password of a valid dwservice.net account in the “DWS user” and DWS password” fields. Click on “Sign in”:

Click on “Agents”:

Click on the icon for the MacOS workstation:

Resources available on the MacOS workstation

This page shows the resources available on the MacOS workstation. Click on “Screen”:

Allowing the dwaggui program to record the computer’s screen (needed for Screen/remote desktop access)

Click on “Open System Preferences”:

Allow the dwgguilnc program to record the screen:

Screen (remote desktop)

Remote control of the desktop of the MacOS workstation:

Files and Folders

Access to the filesystem on the MacOS workstation:

Shell

Access to the operating system shell prompt on the MacOS workstation:

Linux

Ensuring that Wayland is disabled

dwservice.net is not compatible with the Wayland display server.

from a root operating system prompt (use “sudo su” if you are not yet logged in as root), enter these commands:

cd /etc/gdm3
nano custom.conf

(Note: the directory location varies by distribution. For the Fedora distribution, specify the directory “etc/gdm”).

Remove the # comment before “WaylandEnable=false”:

Save and exit the file:

If you had to remove the “#” sign before “WaylandEnable=false” then reboot the Linux workstation so the change can take effect.

Visiting the website to download the installer on a Linux workstation

Visit the dwservice.net site:

https://dwservice.net

Click on “Download”:

Using the dwservice.net client on a Linux workstation in run-once mode

Open a terminal window on the Linux desktop. Change to the folder where the file was downloaded. Enter this command:

bash dwagent.sh

Select “Run,” click on “Next”:

View on the Linux Workstation:

Accessing the Linux workstation from the dwservice.net site

Visit the dwservice.net site:

https://dwservice.net

Enter the username and password displayed on the Linux workstation running the dwservice.net client, click on “Sign in”:

Resources available on the Linux workstation

This page shows the resources available on the Linux workstation:

Screen (remote desktop)

Remote control of the desktop of the Linux workstation:

Files and Folders

Access to the filesystem on the Linux workstation:

Shell

Access to the operating system shell prompt on the Linux workstation:

Installing the dwservice.net client on a Linux workstation to enable unattended access

Open a terminal window on the Linux desktop. Change to the folder where the file was downloaded.

Use “sudo su” to become root if you have not already done so. enter these commands:

Enter this command:

bash dwagent.sh 

Select “Install,” click on “Next”:

Click on “Next”:

Select “Yes,” click on “Next”:

Enter the username and password of a valid dwservice.net account in the “DWS user” and DWS password” fields. Enter a name to describe the workstation in the “Agent name” field. Click on “Next”:

Click on “Close”:

Visit the dwservice.net site:

https://dwservice.net

Enter the username and password of a valid dwservice.net account in the “DWS user” and DWS password” fields. Click on “Sign in”:

Click on “Agents”:

Click on the icon for the Linux workstation:

Resources available on the Linux workstation

This page shows the resources available on the Linux workstation:

Screen (remote desktop)

Remote control of the desktop of the Linux workstation:

Files and Folders

Access to the filesystem on the Linux workstation:

Shell

Access to the operating system shell prompt on the Linux workstation:

Previous step: Chapter 14: Installing and configuring Live Helper Chat to add text chat support to a website
Next step: Chapter 16: Using a script to automate the creation of a virtual host on an Apache web server

Web presence step by step Chapter 14: Installing and configuring Live Helper Chat to add text chat support to a website

Previous step: Chapter 13: Installing and configuring MyBB to create a community forum site
Next step: Chapter 15: Using dwservice.net to provide remote technical support as an alternative to TeamViewer

Web presence step by step is a series of posts that show you to how to build a web presence.

In this chapter we install and configure Live Helper Chat to add text chat support to a website.

Installing the php-curl and php-bcmath extensions, enabling the Apache headers module, and restarting the Apache web server

Live Helper Chat requires the php-curl library, the php-bcmath library, and the headers Apache module.

Use an SSH terminal program to connect to the Ubuntu Linux cloud server you created in Chapter 3: Buying an Ubuntu Linux cloud server from Digital Ocean.

Enter these commands:

apt install php-curl php-bcmath
a2enmod headers
apachectl restart

Creating the MySQL database that will store data for the Live Helper Chat software

Enter this command:

mysql -u root -P

In the MySQL console, enter these commands (where ‘xxxxxx’ is your password):

create user lhc01@localhost;
set password for lhc01@localhost = 'xxxxxx';
create database lhc01;
use lhc01;
grant all privileges on * to lhc01@localhost;
quit

Downloading the zip file containing the Live Helper Chat software

Visit this site:

https://livehelperchat.com

Click on “Download”:

Click on on the link below “Github – recommended link”:

The file downloads:

Extracting the contents of the zip file

If you need detailed instructions on how to uncompress zip files or rename directories on Windows, MacOS, or Linux

This chapter assumes that you have followed the steps in Chapter 8: Installing and configuring WordPress to create a website, and Chapter 9: Installing and configuring phpMyAdmin to manage MySQL databases. If you have not read those chapters, and you are not familiar with how to uncompress a zip file or rename a directory using your operating system, please consult those chapters.

Uncompressing the livehelperchat-master.zip file

We need to uncompress the livehelperchat-master.zip file:

Viewing the extracted folders and files

We can see the lhc_web directory:

Using FileZilla to upload the lhc_web directory to the directory containing the documents for your website

We will use the FileZilla file transfer program to upload the directory “lhc_web” to the directory containing the documents for your website.

If you need detailed instructions on how to install and use FileZilla on Windows, MacOS, or Linux

This chapter assumes that you have read Chapter 7: Configuring the SSH server on an Ubuntu Linux cloud server to limit SFTP directory visibility within chroot jail directories, or that you are familiar with the FileZilla file transfer program.

Using Filezilla, connect to the Ubuntu Linux cloud server you created in Chapter 3: Buying an Ubuntu Linux cloud server from Digital Ocean. On the right (remote) side, select the directory that contains the documents for your website. On the left (local) side, select the directory that contains the “lhc_web” directory. Right-click or command-click on the directory, select Upload:

The transfer completes:

Installing the Live Helper Chat software using the Live Helper Chat installation wizard

For our example domain, we will visit:

https://linuxstepbystep.com/lhc_web/index.php/site_admin/install/install

(substitute your domain name)

Click “Next”:

Complete the fields as shown. Click “Next”:

Complete the field as shown below. Click “Finish installation”:

Click “Login here”:

Generating the Live Helper Chat JavaScript text to add to an HTML header

(Note: for our example domain, we can visit this site at https://linuxstepbyste.com/lhc_web/index.php/site_admin)

Enter your username and password. Click “Login”:

Click on “Settings”:

Under “Mobile,” click on “Settings”:

Select the checkbox “Enable notifications,” click on “Save”:

Click on “Settings”:

Click on “Embed code”:

Click on “Widget embed code (new):

Complete the fields and select the options as required for your site. Copy the code from the text area to the page where you would like the web chat window to appear:

Embedding Live Helper Chat in a WordPress site

To embed the Live Helper Chat application in a WordPress site, we must first install the Insert Headers and Footers plugin for WordPress.

Installing the Insert Headers and Footers WordPress plugin

In your WordPress control panel, click on “Plugins,” then click on “Add New”:

In the search window, enter the text “insert headers and footers,” locate the plugin “Insert Headers and Footers,” click on “Install Now”:

Click on “Activate”:

Click on “Settings,” then click on “Insert Headers and Footers”:

Adding the JavaScript code using the Insert Headers and Footers WordPress plugin

Complete the fields as shown.

Scroll to the bottom of the page. Paste the JavaScript code you generated earlier in the section “Generating the Live Help Chat JavaScript text to add to an HTML header” to the field “Scripts in Footer,” click “Save”:

Cick on “Visit Site”:

The chat window is visible on the WordPress site:

Texting between the site’s visitors and the site’s operators:

Embedding Live Helper Chat in a simple web page

We can add the Live Helper Chat text chat window to a simple web page.

Using an SSH terminal program, connect to your Ubuntu Linux cloud server. Change to the directory containing the documents for your website. Enter the command:

nano test.html

Paste the JavaScript code you generated in the section “Generating the Live Help Chat JavaScript text to add to an HTML header,” press Control-X to save and exit the file:

Enter these commands (where username is the username that owns the documents for the website):

chown username:username test.html
chmod 755 test.html

View the test.html page in a web browser:

Accessing the Live Helper Chat operator panel

For our example domain, we can visit this site at https://linuxstepbyste.com/lhc_web/index.php/site_admin (Substitute your domain name.)

Enter your username and password. Click “Login”:

Click on “Chat”:

Configuring and Using the Android client

Download the “Live Helper Chat” app from the Google Play Store. When you launch the application, you will see this screen. Click on “+”:

Complete the fields below as shown (substitute your domain name). Click “Login”:

Configuring and using the iOS client

Download the “Live Helper Chat” app from the Apple App Store. When you launch the application, you will see this screen. Click on “Allow”:

Click on “+”:

Complete the fields as shown (substitute your domain name). Click “Login”:

Previous step: Chapter 13: Installing and configuring MyBB to create a community forum site
Next step: Chapter 15: Using dwservice.net to provide remote technical support as an alternative to TeamViewer

Web presence step by step Chapter 13: Installing and configuring MyBB to create a community forum site

Previous step: Chapter 12: Installing and configuring the WooCommerce plugin for WordPress to enable a shopping cart for transactions
Next step: Chapter 14: Installing and configuring Live Helper Chat to add text chat support to a website

Web presence step by step is a series of posts that show you to how to build a web presence.

In this chapter, we install and configure MyBB to create a community forum site.

If you are not familiar with SSH terminal programs

This chapter assumes that you have read Chapter 4: Using an SSH terminal program to connect to an Ubuntu Linux cloud server. If you are not familiar with SSH terminal programs, please consult that chapter.

Creating a MySQL database to host the data for the MyBB community forum site

Use an SSH terminal program to connect to the Ubuntu Linux cloud server you created in Chapter 3: Buying an Ubuntu Linux cloud server from Digital Ocean.

From the system prompt, enter the command:

mysql -u root -p

From the MySQL console prompt, enter these commands (use a unique password in place of ‘xxxxxx’):

create user bb01@localhost;
set password for bb01@localhost = 'xxxxxx';
create database bb01;
use bb01;
grant all privileges on * to bb01@localhost;
quit

Installing the php-gd extension

In order to generate graphics representing captchas, we need to install the php-gd extension.

Enter the command:

apt install php-gd

Enter the command:

systemctl restart apache2

Configuring a Gmail account to allow MyBB to send email messages

We need to select or create a Gmail account that the MyBB community forum software will use to send email messages. This can be the same Gmail account you selected or created in Chapter 11: Installing and configuring the WP Mail SMTP plugin for WordPress to enable WordPress to send email messages.

Click on the settings icon (gear symbol in upper-right):

Click on “See all settings”:

Click on “Accounts and Import”:

Click on “Other Google Account settings”:

Click on “Security”:

In the section “Less secure app access,” click on “Turn on access (not recommended)”:

Enable the control so that the page displays “Allow less secure apps: ON”:

Click on “Forwarding and POP/IMAP,” select the option “Enable IMAP,” click on “Save Changes”:

Downloading the MyBB zip file

Visit this site:

https://mybb.com/download/

Click on “Download MyBB x.x.xx” (where x.x.xx is the version):

The file downloads:

If you are using Chrome, right-click or command-click on the name of the file, select “Show in folder”:

Uncompressing the MyBB zip file and renaming the Upload directory

If you need detailed instructions on how to uncompress zip files or rename directories on Windows, MacOS, or Linux

This chapter assumes that you have followed the steps in Chapter 8: Installing and configuring WordPress to create a website, and Chapter 9: Installing and configuring phpMyAdmin to manage MySQL databases. If you have not read those chapters, and you are not familiar with how to uncompress a zip file or rename a directory using your operating system, please consult those chapters.

Uncompressing the mybb_xxxx.zip file

We need to uncompress the mybb_xxxx.zip file:

Uncompressing the zip file extracts 2 directories: “Documentation,” and “Upload”:

Renaming the directory named “Upload” to “community”

Rename the “Upload” directory to “community”:

Using FileZilla to upload the community directory to the directory containing the documents for your website

We will use the FileZilla file transfer program to upload the directory renamed as “community” to the directory containing the documents for your website.

If you need detailed instructions on how to install and use FileZilla on Windows, MacOS, or Linux

This chapter assumes that you have read Chapter 7: Configuring the SSH server on an Ubuntu Linux cloud server to limit SFTP directory visibility within chroot jail directories, or that you are familiar with the FileZilla file transfer program.

Using Filezilla, connect to the Ubuntu Linux cloud server you created in Chapter 3: Buying an Ubuntu Linux cloud server from Digital Ocean. On the right (remote) side, select the directory that contains the documents for your website. On the left (local) side, select the directory that contains the “community” directory. Right-click or command-click on the directory, select Upload:

The transfer completes:

Installing the MyBB software using the MyBB installation wizard

For our example domain, we will visit:

https://linuxstepbystep.com/community

(substitute your domain name)

Click “Next”:

Click “Next”:

Click “Next”:

Complete the fields as show below. Click “Next”:

Scroll to the bottom of the web page. Click “Next”:

Click “Next”:

Click “Next”:

Complete the fields as show below. Click “Next”:

Complete the fields as show below. Click “Next”:

This screen will display when we have finished:

Visiting the community forum site

For our example domain, we will visit:

https://linuxstepbystep.com/community

(substitute your domain name)

Logging into the admin account

Enter the username and password for the admin user. Click “Login”:

Click on “Admin CP”:

Enter the username and password for the admin user. Enter the secret PIN. Click “Login”:

Configuring the MyBB community forum site

Click on “Configuration”:

Scroll towards the bottom of the web page:

Click on “Mail Settings”:

Configure settings for “Mail handler”

Mail handler: select "SMTP mail"
SMTP hostname: smtp.gmail.com
SMTP port: 587
SMTP username: the address of the gmail account you selected or created earlier in this procedure
SMTP Encryption Mode: select "TLS encryption"

Click “Save Settings”:

Click on “Login and Registration Options”:

For “Registration Method,” select “Send Email Verification”:

Scroll to the bottom of the web page. Click on “Save Settings”:

Registering as a new user on the community forum site

Use a web browser to visit the community forum site. Click on “Register.” On the page titled “Registration Agreement,” consider then if you decide click “I agree”:

Complete the fields as show in the example below. Click “Submit Registration!”:

MyBB displays this message:

Check the email account you provided during registration. View the message sent by MyBB community forum site:

To activate your account, click on the link in the email:

Welcome as a member:

We will create a post. Click on “My Forum”:

Click on “Post Thread”:

In the field “Thread Subject:” enter a subject.

In the field “Your Message:” enter the text of a message.

Click on “Post Thread”:

The message is displayed. Click on “My Category”:

We now have 1 thread and 1 post:

Previous step: Chapter 12: Installing and configuring the WooCommerce plugin for WordPress to enable a shopping cart for transactions
Next step: Chapter 14: Installing and configuring Live Helper Chat to add text chat support to a website

Forwarding ports to a KVM guest using iptables and Network Address Translation (NAT)

This post describes how to create a BASH script that opens the KVM virtual network adapter to outside traffic, and forwards ports from the KVM host to the KVM guest using iptables and Network Address Translation (NAT).

The KVM virtual network adapter rejects packets from the outside world by default

By default, the virtual network adapter for KVM (virbr0) is configured to block network traffic originating from outside the host computer. This can be resolved with iptables directives, which will be described below.

You do not need to use /etc/ufw/before.rules or /etc/libvirt/hooks/qemu to forward ports to a KVM guest

Many Internet articles and posts on this subject give the incorrect impression that the only way to forward ports to a KVM guest is via UFW and its /etc/ufw/before.rules file, and/or that you need to create a post-configuration script as a qemu “hook” in the /etc/libvirt/hooks/qemu file. You can open the virtual network adapter to outside traffic and forward ports to the KVM guest under NAT solely using iptables directives.

An example of a BASH script that opens the virtual adapter to outside traffic, and forwards ports from the host to the guest

To illustrate the solution, here is a sample BASH script that contains iptables directives that open the virtual adapter to outside traffic, and forwards ports 80/tcp, 443/tcp, and 8022/tcp from the host to the guest.

A note about the source code view below

For formatting reasons, the text is limited to a fixed width. To fully view the text, you can scroll to the right to see the ends of lines, or use the print view for this blog post.

#!/usr/bin/bash
# generated 2021/04/07 20:37:46 EDT by forwardportstoguestgenerator.php v0102
# Gordon Buchan https://gordonbuchan.com

# values
kvmsubnet="192.168.122.0/24"
wanadaptername="enx4ce1734b693e"
wanadapterip="192.168.46.123"
kvmadaptername="virbr0"
kvmadapterip="192.168.122.174"

# allow virtual adapter to accept packets from outside the host
iptables -I FORWARD -i $wanadaptername -o $kvmadaptername -d $kvmsubnet -j ACCEPT
iptables -I FORWARD -i $kvmadapterip -o $wanadaptername -s $kvmsubnet -j ACCEPT
# forward ports from host to guest
iptables -t nat -A PREROUTING -i $wanadaptername -d $wanadapterip -p tcp --dport 80 -j  DNAT --to-destination $kvmadapterip:80
iptables -t nat -A PREROUTING -i $wanadaptername -d $wanadapterip -p tcp --dport 443 -j DNAT --to-destination $kvmadapterip:443
iptables -t nat -A PREROUTING -i $wanadaptername -d $wanadapterip -p tcp --dport 8022 -j DNAT --to-destination $kvmadapterip:22

A PHP script that generates a BASH script that opens the virtual network adapter to outside traffic, and forwards ports from the host to the guest

This script runs the ifconfig and virsh commands to compile lists of possible WAN interfaces and KVM guests. This script prompts for choices at console, and generates a text file containing a BASH script with iptables directives that open the virtual adapter to outside traffic, and forward ports from the host to the guest using network address translation (NAT).

A note about the source code view below

For formatting reasons, the text is limited to a fixed width. To fully view the text, you can scroll to the right to see the ends of lines, or use the print view for this blog post.

To view the source code in an another text editor, download and uncompress the zip file described below, or select and copy the text from the source code example below, and paste the text into a file on your computer called “forwardportstoguest.php”

Consider copying the file to your Apache web server’s /usr/bin directory with a chmod of 755 so that it can be executed from the system path. Steps to do so are included in the procedure below.

Saving the PHP script to a file called forwardportstoguestgenerator.php

Download this zip file:

https://blog.gordonbuchan.com/files/forwardtoguest0102.zip

Uncompress the zip file to extract the file “forwardportstoguestgenerator.php” then copy the file to your KVM host computer.

or

Select and copy the text from the source code example above, and paste the text into a file on your computer called “forwardportstoguestgenerator.php”

#!/usr/bin/php
<?PHP
// forwardportstoguestgenerator.php
// v0102
// scan ifconfig and virsh, create iptables directives to forward ports to kvm guests
// chmod this script 755 to run as ./forwardportstoguestgenerator.php or run with php forwardportstoguestgenerator.php
// writes to a text file the BASH script forwardportstoguestscript.sh
 
// 2021/04/07
// Gordon Buchan https://gordonbuchan.com
// MIT license https://mit-license.org
 
// overview
// run the command "ifconfig" to isolate potential wan adapter names and ip addresses
// infer the KVM subnet based on the first 3 sections of the ip address of the "virbr0" adapter
// run the command "virsh net-dhcp-leases default" to isolate potential kvm guest names and ip addresses
// ask client to choose WAN adapter
// ask client to choose KVM guest
// create a batch file containing iptables directives to open the virtual adapter to packets from outside the host
// and to forward ports from the host adapter to the KVM guest adapter 80/tcp, and 443/tcp, 8022/tcp
 
// //////////////////////////////////////////////////////////////////////////////////
// start function sink
 
// str_contains() polyfill for pre PHP8
if (!function_exists('str_contains')) {
    function str_contains(string $haystack, string $needle): bool
    {
        return '' === $needle || false !== strpos($haystack, $needle);
    }
}
 
// end function sink
// //////////////////////////////////////////////////////////////////////////////////
 
// start get the WAN adapter names and ip addresses
 
// capture output of ifconfig command to variable $ifcstr
$ifcstr = `ifconfig`;
 
// convert string $ifcstr to array of lines $ifcstrarr
// use linefeed as field delimiter in array population
$ifcstrarr = explode("\n",$ifcstr);
 
// count lines in the array
$ifcstrarrnumlines = count($ifcstrarr);
 
$adnamestrarr = array();
$adipstrarr = array();
 
$kvmsubnet = "";
 
// iterate through array of lines
for ( $i=0;$i<$ifcstrarrnumlines;$i++) {
 
    if ( str_contains($ifcstrarr[$i],"flags")) {
        $flagsstr = "flags";
        $flagsstrloc = strpos("$ifcstrarr[$i]", $flagsstr) - 2;
        $adnamestr = substr($ifcstrarr[$i],0,$flagsstrloc);
    } // close if str contains "flags"
 
    // we will eventually filter virbr0, but for now we can find out the subnet for the KVM guest network
 
    if ( str_contains($ifcstrarr[$i],"inet") && !str_contains($ifcstrarr[$i],"inet6") ) {
 
        $inetstr = "inet";
        $inetstrloc = strpos("$ifcstrarr[$i]",$inetstr) + 5;
        $adipstr = substr($ifcstrarr[$i],$inetstrloc,"20");
        $spacestrloc = strpos("$adipstr"," ");
        // trimming the variable
        $adipstr = substr($adipstr,0,$spacestrloc);
 
        if (str_contains($adnamestr,"virbr0")) {
            // start infer KVM subnet
            // //////////////////////////////////////////////////////////
            // do stuff here to get the virbr0 ip address so we can infer subnet
            $kvmsubnetraw = $adipstr;
            $lastdotloc = strrpos($kvmsubnetraw,".");
            $kvmsubnet = substr($kvmsubnetraw,0,$lastdotloc) . ".0/24";
            echo "\nKVM subnet\nkvmsubnet: $kvmsubnet\n\n";
            // end infer KVM subnet
            // //////////////////////////////////////////////////////////
        } else {
            // stuff the arrays they will match by number because done at same time
            // filter for loopback device
            if (!($adipstr == "127.0.0.1")) {
                $adnamestrarr[] = $adnamestr;
                $adipstrarr[] = $adipstr;
            }
        }
 
    } // close if str contains "inet"
 
} // end for $i
 
//so we are always defined
$adnamestrarrnumlines = "";
$adnamestrarrnumlines = count ($adnamestrarr);
if (!$adnamestrarrnumlines) {
    echo "no WAN adapters found.\nStopping.\n";
    exit();
}

// if we do not have a KVM subnet, then something is wrong. Stop.
if (!$kvmsubnet) {
    echo "KVM subnet not detected. Stopping.\n";
    exit();
}
 
// end get the WAN adapter names and ip addresses
// //////////////////////////////////////////////////////////////////////////////////
 
// start get the KVM guest names and ip addresses
 
// capture output of virsh command to variable $ifcstr
$virshleastr = `virsh net-dhcp-leases default`;
 
// convert string $virshleastr to array of lines $virshleastrarr
// use linefeed as field delimiter in array population
$virshleastrarr = explode("\n",$virshleastr);
 
// count lines in the array
$virshleastrarrnumlines = count($virshleastrarr);
 
$kvmnamestrarr = array();
$kvmipstrarr = array();
 
// iterate through array of lines
for ( $j=0;$j<$virshleastrarrnumlines;$j++) {
    if ( str_contains($virshleastrarr[$j],"ipv4")) {
        $ipv4str = "ipv4";
        $ipv4strloc = strpos("$virshleastrarr[$j]", $ipv4str) + 11;
        $kvmlinestr = substr($virshleastrarr[$j],$ipv4strloc,50);
        $slashstr = "/";
        $slashstrloc = strpos("$kvmlinestr",$slashstr);
        $kvmipstr = substr($kvmlinestr,0,$slashstrloc);
        $kvmnamestr = substr($kvmlinestr,$slashstrloc+5,12);
        $kvmnamestr = trim($kvmnamestr);
        //stuff the arrays they will match by number because done at same time
        $kvmnamestrarr[] = $kvmnamestr;
        $kvmipstrarr[] = $kvmipstr;
    } // close if str contains "ipv4"
} // end for $j
 
$kvmnumlines = count ($kvmnamestrarr);
if (!$kvmnumlines) {
    echo "no VM guest DHCP leases found. Please start a VM.\nStopping.\n";
    exit();
}

// end get the KVM guest names and ip addresses
// //////////////////////////////////////////////////////////////////////////////////
 
// start ask client to choose WAN adapter
 
// show the possible WAN adapters as a numbered list to console:
echo "WAN adapters\n";
for ($k=0;$k<$adnamestrarrnumlines;$k++) {
    $displaynum = $k + 1;
    echo "$displaynum. $adnamestrarr[$k] $adipstrarr[$k]\n";
}
 
echo "\n";
 
// use readline function to ask questions interactively
// trap function in a while condition for sanity checking on input until satisfied
$wananswer = "";
while (!$wananswer || ($wananswer>$displaynum) || !is_numeric($wananswer) ) {
    $wananswer = readline("Please choose a WAN adapter (1-$displaynum): ");
}
 
echo "choice entered: $wananswer\n";
 
// because humans start at 1 and computers start at 0
$wanchoiceminus = $wananswer - 1;
 
$wanadaptername = $adnamestrarr[$wanchoiceminus];
$wanadapterip = $adipstrarr[$wanchoiceminus];
 
echo "\n";
echo "wanadaptername: $wanadaptername\n";
echo "wanadapterip: $wanadapterip\n";
echo "\n";
 
// end ask client to choose WAN adapter
// //////////////////////////////////////////////////////////////////////////////////
 
// start ask client to choose KVM guest
 
// show the possible KVM guests as a numbered list to console:
echo "KVM guests\n";
echo "(hint: if a VM is not listed here, start the VM so it gets a DHCP lease)\n";
for ($m=0;$m<$kvmnumlines;$m++) {
    $displaynum = $m + 1;
    echo "$displaynum. $kvmnamestrarr[$m] $kvmipstrarr[$m]\n";
}
 
echo "\n";
 
// use readline function to ask questions interactively
// trap function in a while condition for sanity checking on input until satisfied
$kvmanswer = "";
while (!$kvmanswer || ($kvmanswer>$displaynum) || !is_numeric($kvmanswer) ) {
    $kvmanswer = readline("Please choose a KVM guest (1-$displaynum): ");
}
 
echo "choice entered: $kvmanswer\n";
 
// because humans start at 1 and computers start at 0
$kvmchoiceminus = $kvmanswer - 1;
 
// we should not confuse kvm guest name with kvmadaptername
// we hardcode the name of the kvm adapter as the string "virbr0"
$kvmadaptername = "virbr0";
$kvmadapterip = $kvmipstrarr[$kvmchoiceminus];
 
echo "\n";
echo "kvmadaptername: $kvmadaptername\n";
echo "kvmadapterip: $kvmadapterip\n";
echo "\n";
 
// end ask client to choose KVM guest
// //////////////////////////////////////////////////////////////////////////////////
 
// start engine section
 
// construct the string variable containing the contents of the script file
 
$timestring = date("Y/m/d H:i:s T");
 
// start from nothing
$scriptcontents = "";
 
$scriptcontents .= "#!/usr/bin/bash\n";
$scriptcontents .= "# generated $timestring by forwardportstoguestgenerator.php v0102\n";
$scriptcontents .= "# Gordon Buchan https://gordonbuchan.com\n";
$scriptcontents .= "\n";
$scriptcontents .= "# values\n";
$scriptcontents .= "kvmsubnet=\"$kvmsubnet\"\n";
$scriptcontents .= "wanadaptername=\"$wanadaptername\"\n";
$scriptcontents .= "wanadapterip=\"$wanadapterip\"\n";
$scriptcontents .= "kvmadaptername=\"$kvmadaptername\"\n";
$scriptcontents .= "kvmadapterip=\"$kvmadapterip\"\n";
$scriptcontents .= "\n";
$scriptcontents .= "# allow virtual adapter to accept packets from outside the host\n";
$scriptcontents .= "iptables -I FORWARD -i \$wanadaptername -o \$kvmadaptername -d \$kvmsubnet -j ACCEPT\n";
$scriptcontents .= "iptables -I FORWARD -i \$kvmadapterip -o \$wanadaptername -s \$kvmsubnet -j ACCEPT\n";
$scriptcontents .= "# forward ports from host to guest\n";
$scriptcontents .= "iptables -t nat -A PREROUTING -i \$wanadaptername -d \$wanadapterip -p tcp --dport 80 -j  DNAT --to-destination \$kvmadapterip:80\n";
$scriptcontents .= "iptables -t nat -A PREROUTING -i \$wanadaptername -d \$wanadapterip -p tcp --dport 443 -j DNAT --to-destination \$kvmadapterip:443\n";
$scriptcontents .= "iptables -t nat -A PREROUTING -i \$wanadaptername -d \$wanadapterip -p tcp --dport 8022 -j DNAT --to-destination \$kvmadapterip:22\n";
 
$scriptfilename = "forwardportstoguestscript.sh";
 
# write the text file
$fh = fopen("$scriptfilename","w");
$filesuccess = fwrite($fh,$scriptcontents);
fclose($fh);
 
if ($filesuccess) {
    echo "SUCCESS script written to file: $scriptfilename\n";
    chmod("$scriptfilename", 0755);
    $scriptperms = substr(sprintf('%o', fileperms("$scriptfilename")), -4);
    echo "scriptperms: $scriptperms\n";
    if ($scriptperms == "0755") {
        echo "SUCCESS chmod 755 $scriptfilename successful.\n";
    } else {
        echo "ERROR chmod 755 not $scriptfilename not successful.\n";
    }
} else {
    echo "ERROR script not written to file: $scriptfilename\n";
}
 
// end engine section
// /////////////////////

Executing as root

If you have not logged in as root, please escalate to root.

Enter this command:

sudo su

Installing php cli and net-tools

The PHP script requires the php cli and the ifconfig command from net-tools. The script also requires virsh, but you likely have that tool installed already if you are hosting KVM guests.

Ubuntu

Enter the command:

apt install php7.4-cli net-tools

Fedora

Enter the command:

dnf install php-cli net-tools

Executing the PHP script forwardportstoguestgenerator.php to generate the BASH script forwardportstoguestscript.sh

Enter the command:

php forwardportstoguestgenerator.php

When prompted, choose a WAN adapter and a KVM guest.

You will see output similar to the following:

root@server:/usr/bin# php forwardportstoguestgenerator.php
KVM subnet
kvmsubnet: 192.168.122.0/24
WAN adapters
1. enx4ce1734b693e 192.168.46.123
2. wlp0s20f3 192.168.46.103
Please choose a WAN adapter (1-2): 1
choice entered: 1
wanadaptername: enx4ce1734b693e
wanadapterip: 192.168.46.123
KVM guests
(hint: if a VM is not listed here, start the VM so it gets a DHCP lease)
1. midland 192.168.122.174
Please choose a KVM guest (1-1): 1
choice entered: 1
kvmadaptername: virbr0
kvmadapterip: 192.168.122.174
SUCCESS script written to file: forwardportstoguestscript.sh
scriptperms: 0755
SUCCESS chmod 755 forwardportstoguestscript.sh successful.

Executing the BASH script forwardportstoguestscript.sh

Enter the command:

bash forwardportstoguestscript.sh

Testing the forwarded ports

Using a different workstation on the network, connect to the IP address of the computer hosting the KVM guest.

If you have forwarded the public-facing ports on your router to the IP address of the computer hosting the KVM guest, test whether traffic on the ports is forwarded to the KVM guest.

Web presence step by step Chapter 12: Installing and configuring the WooCommerce plugin for WordPress to enable a shopping cart for transactions

Previous step: Chapter 11: Installing and configuring the WP Mail SMTP plugin for WordPress to enable WordPress to send email messages
Next step: Chapter 13: Installing and configuring MyBB to create a community forum site

Web presence step by step is a series of posts that show you to how to build a web presence.

In this chapter, we install and configure the WooCommerce plugin for WordPress to enable a shopping cart for transactions.

Accessing the WordPress control panel

Visit the URL for your WordPress control panel by entering your domain name followed by “/wp-admin” as in this example:

https://linuxstepbystep.com/wp-admin

Enter the username and password you specified when you created the WordPress blog in Chapter 8: Installing and configuring WordPress to create a website. Click “Log in”:

Installing the plugin

Click “Plugins”:

Click “Add New”:

search for the text “woocommerce”:

Click “Install Now”:

Click “Activate”:

Complete the fields “Address,” “Country/Region,” “City,” “Post code,” Click “Continue”:

Click “No thanks”:

Check boxes that apply to your situation. Click “Continue”:

For now, we will limit ourselves to the free tier of services, “Physical products” and “Downloads.” Avoid checking the remaining boxes until you become more expert, as these additional options incur a monthly or annual cost cost. Click “Continue”:

Select answers to the questions. Click “Continue”:

For now, we will de-select the additional services. Click “Continue”:

Click “Continue with my active theme”:

Click “No thanks”:

Click “Next”:

Click “Next”:

Click “Let’s go”:

Configuring WooCommerce

The WooCommerce plugin page appears. The “Finish setup” wizard is displayed. Explore the wizard by completing the steps. We will not explore this wizard in detail as it is largely self-explanatory, and there are many permutations of possible choices specific to each business case.

Avoid “Set up payments” until you have learned more

Delay configuring payments for your store until the end. You should feel comfortable experimenting with the software before there are live financial consequences due to a link with a payment service.

Avoid Jetpack and WooCommerce Tax, set up taxes manually

In the “Set up tax” section, avoid the option “Install Jetpack and WooCommerce Tax,” and instead select the option “Set up manually.”

Start by setting tax rates manually. If you decide later that you need the functionality in the “Jetpack” and “WooCommerce Tax” tools, you can install them later.

Previous step: Chapter 11: Installing and configuring the WP Mail SMTP plugin for WordPress to enable WordPress to send email messages
Next step: Chapter 13: Installing and configuring MyBB to create a community forum site

Web presence step by step Chapter 11: Installing and configuring the WP Mail SMTP plugin for WordPress to enable WordPress to send email messages

Previous step: Chapter 10: Integrating Google Site Kit with WordPress to view Google Analytics and Google Search Console visitor statistics
Next step: Chapter 12: Installing and configuring the WooCommerce plugin for WordPress to enable a shopping cart for transactions

Web presence step by step is a series of posts that show you to how to build a web presence.

In this chapter, we install and configure the WP Mail SMTP plugin for WordPress to enable WordPress to send email messages.

WordPress needs to be able to send emails

Some operations require that the WordPress software be able to send email messages. We need to choose or create a Gmail account from which messages will be sent. We need to configure that Gmail account with a web application and OAUTH2 authentication tokens. We need to install and configure the WP Mail SMTP plugin with information from the Gmail account.

Choosing or creating a Gmail account from which to send email messages

Choose an existing Gmail account, or create a new Gmail account. This account should not be the same email account you use for some other purpose, such as employee email. This Gmail account will be used by the WP Mail SMTP plugin for WordPress to send email messages. Login to this Gmail account.

Creating a web application and OAUTH2 authentication tokens

Visit this website:

https://console.developers.google.com/flows/enableapi?apiid=gmail&pli=1

Check the box for “I agree to the Google Cloud Platform Terms of Service…”
Select Country
“I would like to receive periodic emails…” Select “No”
Click “Agree and continue”:

Click “Go to credentials”:

Which API are you using?
Gmail API

Which data will you be accessing?
Web server (e.g. node.js, Tomcat)

Click “What credentials do I need?”:

Click “Set up consent screen”:

User Type
Select “External”

Click “Create”:

Complete the fields as shown below (use your domain instead of the example domain)

Click “Save and continue”:

Click “Add users”:

Enter an email address. Click “Add”:

Click “Save and continue”:

You will see a screen similar to the following. Note that we are on the right tab. Click on the left tab:

Enter values for “Name,” “Authorized JavaScript engine.”

For the field “Authorized redirect URIs,” enter this value:

https://connect.wpmailsmtp.com/google/

Click “Refresh”:

Click “OAuth client ID”:

Click “Done”:

You will see a screen similar to the following:

Installing and Configuring the WP Mail SMTP plugin for WordPress

Go to the control panel for your WordPress blog software. Go to Plugins. Search for “smtp.” Click on the “Install Now” button next to “WP Mail SMTP by WPForms”:

Click “Activate”:

Select “Gmail”:

Enter the values for “Client ID” and “Client Secret” you obtained from the Gmail control panels earlier in this procedure. Enter this value for “Authorized redirect URI”:

https://connect.wpmailsmtp.com/google/

Click “Save Settings”:

Click “Allow plugin to send emails using your Google account”:

Select the Gmail account you chose to allow the WP SMTP Mail plugin for WordPress to send email messages:

Click “Continue”:

Click “Allow”:

Click “Allow”:

Sending a test message

Enter an email address. Click “Send Email”:

An email similar to the following should arrive in the test destination mailbox:

Previous step: Chapter 10: Integrating Google Site Kit with WordPress to view Google Analytics and Google Search Console visitor statistics
Next step: Chapter 12: Installing and configuring the WooCommerce plugin for WordPress to enable a shopping cart for transactions

Web presence step by step Chapter 10: Integrating Google Site Kit with WordPress to view Google Analytics and Google Search Console visitor statistics

Previous step: Chapter 9: Installing and configuring phpMyAdmin to manage MySQL databases
Next step: Chapter 11: Installing and configuring the WP Mail SMTP plugin for WordPress to enable WordPress to send email messages

Web presence step by step is a series of posts that show you to how to build a web presence.

In this post, we install and configure the Google Site Kit plugin for WordPress in order to access Google Analytics and Google Search Console website statistics.

Understanding the link between WordPress, the Google Site Kit plugin, Google Analytics, and Google Search Console

In order to track visitor statistics for your website, Google Analytics needs a tracking code inserted into each web page you host. Google Search Console requires that we prove we have the authorization of the owner of the domain in order to view search engine statistics specific to the domain. By installing and configuring the Google Site Kit plugin for WordPress, we are able to add the necessary tracking code, and to prove to Google that we are the authorized owner of the domain name.

Creating or choosing a Gmail account for the connections to Google Analytics and Google Search Console

We will need a Gmail account to control the Google Analytics and Google Search Console transactions for your website. This account may have to be shared with marketing consultants and sales personnel, so it should not be associated with the email for a specific employee. The Gmail account can be a personal (free) Gmail account.

Logging out of Gmail, just to be sure

Before we begin, sign out of Gmail:

Logging into the Gmail account you created for Google Analytics and Google Search Console

Login to the account you created for use with Google Analytics and Google Search Console:

Enter the password for the account:

Gmail appears, logged into the account you created to manage Google Analytics and Google Search Console transactions:

Configuring Google Analytics to create a property

Visit the Google Analytics site. Click “Start measuring”:

Enter a value for “Account name,” check the boxes under “Account Data Sharing Settings.” Click “Next”:

Enter a value for “Property name.” Click “Next”:

Check a box for “Business size.” Check boxes as applicable under “How do you intend to use Google Analytics with your business (Check all that apply).” Click “Create”:

Check the boxes to accept the terms, click “I Accept” (if you want to):

Do not check any of these boxes. Click “Save”:

The Google Analytics site appears:

Installing the Google Site kit plugin for WordPress

Go to the control panel for your WordPress blog. Go to Plugins. Click “Add New.” Search for:

“analytics”

Locate “Site Kit by Google — Analytics, Search Console, AdSense, Speed.” Click on the button “Install Now” for the plugin:

Click “Activate”:

Click “Start setup”:

Click “Sign in with Google”:

Select the account you created to control Google Analytics and Google Search Console transactions for your domain:

Click “Allow”:

Click “Allow”:

Click “Allow”:

Click “Proceed”:

Click “Allow”:

Click “Add site”:

Click “Go to my Dashboard”:

The Google Site Kit plugin for WordPress Dashboard appears. In the box for “Analytics,” click on “Connect service”:

Select the Gmail account you created to control Google Analytics and Google Search Console Transactions for your website:

Click “Allow”:

Click “Allow”:

Select Account, Property. Provide a value for “View Name.” Click “Proceed”:

Select the Gmail account you created to control the Google Analytics and Google Search Console transactions for your domain:

Click “Allow”:

Click “Allow”:

The Google Site Kit plugin for WordPress Dashboard appears. Note that both “Search Console” and “Analytics” are now shown as “Connected”:

Wait a few days, then come back. There will be data then.

An example of what to expect:

Previous step: Chapter 9: Installing and configuring phpMyAdmin to manage MySQL databases
Next step: Chapter 11: Installing and configuring the WP Mail SMTP plugin for WordPress to enable WordPress to send email messages