Three-Tier App VMware Hands-on Labs

3-Tier Demo App – 2023 Edition – app-01a

This is the third post in a series about creating a simple 3-tier web application for demo purposes. This updates a series from 2017 to use the latest version of Photon OS at this time. The posts in this new series are as follows:

At this point, you have downloaded Photon OS and prepared the base template according to the specifications and have built and tested the database server, db-01a. Working from the bottom-up, left to right, we move next to the application server, which sits between the database and the web tier. As with the database, this is nothing fancy, but it gets the job done.

The Application (app-01a)

The application server sits in the middle of our stack and handles the formatting of data returned from the database. We will again use Apache and Python because we know how to use those tools from the database server build. This time, the CGI script presents a simple HTML form and table containing data pulled from the database via HTTP on port 3306.

To add a bit of realism and fun to the process, we will configure SSL on the application server. The basic configuration is straightforward once you have the certificate and key, and the process is good to know how to do. We will be using our own self-signed certificate here, but this is a demo!

The red box in the following diagram highlights the component that we are building in this post.

Application Server

Note that the first steps of this section look quite a bit like the steps we performed for the database server. I’ll outline them here. Details can be found in the previous section.

Deploy and configure a copy of the base template

Deploy a copy of the base Photon OS template you created by following the steps in the Template section. Name it something that makes sense to you for your app server. I called mine app-01a because I am just that creative.

Deploy a copy of the base Photon OS template you created by following the steps in the Template section. Name it something that makes sense to you for your app server. I called mine app-01a because I am just that creative.

Power up the VM, access the console and log in as the root user.

Change the hostname in /etc/hostname and in /etc/hosts

Change the IP address in /etc/systemd/network/

Use a SSH client to access the machine as root (this makes pasting commands easier)

Install the Apache web server

Install the Apache server here, just like on the database server

# tdnf install httpd

Due to the SSL requirement on this machine, there are more files to edit than in the database server build, so get your editing fingers ready!

Prepare to create the certificate and key

Creating the certificate is easiest if we pass it a file containing all the answers needed by openssl. Navigate to /tmp and create the file app-01a.conf

# cd /tmp 
# vi app-01a.conf

Put the following contents into the file. (Substitute values appropriate for your environment.) The CN and the values under the [alt_names] section are the most important for our purposes here.

distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no

C = US
ST = California
L = Palo Alto
O = VMware
OU = Hands-on Labs
CN = app-01a.corp.local

keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

DNS.1 = app-01a.corp.local
DNS.2 = app-01a
IP.1 =

Save and close the file.

NOTE: Thank you to Jason Marchesano for not only reporting an SSL certificate challenge with Edge and Chrome on Windows 11 but also providing a fix to my original configuration.

Generate the key and certificate from the configuration file

Note that this is a long command. You may need to scroll all the way to the right to get all of it.

# openssl req -x509 -nodes -days 1825 -newkey rsa:2048 -keyout app.key -out app.pem -config app-01a.conf

(optional) Validate that the PEM file “looks right”

# openssl x509 -in app.pem -noout -text

Read through the text output and make sure the certificate looks as you expect. If you filled out the configuration file properly, it should be fine.

Leave those files where they are for now. We’ll need them in a few minutes.

Configure the web server to run SSL on port 8443

SSL over the default port is so yesterday. Besides, we want something that differentiates this traffic from the port 443 SSL traffic that comes from the client to the web server we’ll be creating later. Changing the port makes the firewall rules more interesting and easier to distinguish. Besides, this tier is not normally user-facing, so there is no complexity with requiring normal users to append “:8443” to the end of the host portion of the URL.

Open the configuration file

Use the “+52” to jump to the line we want.

# vi +52 /etc/httpd/httpd.conf

Disable listening on port 80

We don’t need this server listening on the default HTTP port, so put a # in front of the “Listen 80” on line 52)

#Listen 80

Load the SSL module

Is this thing on? We need to turn on the SSL functionality, which is disabled in the default configuration. Uncomment (remove the # from the beginning of) line 150:

LoadModule ssl_module /usr/lib/httpd/modules/

Load the CGI module

Just like on the database server, we must enable the CGI module. Add the following text on its own line at the end of the module loading, above the <IfModule unixd_module> text. I use line 181:

LoadModule cgi_module /usr/lib/httpd/modules/

Include the SSL configuration file

Near the bottom of the file (around line 522), un-comment the line

Include conf/extra/httpd-ssl.conf

Save the file and exit.

Configure SSL settings

Open the SSL “extra settings” file for editing.

# vi +36 /etc/httpd/conf/extra/httpd-ssl.conf

On line 36, locate the Listen line and change the port number from 443 to 8443

Listen 8443

To make things work, I also had to comment out line 92:

#SSLSessionCache "shmcb:/etc/httpd/logs/ssl_scache(512000)"

Change the virtual host’s port number from 443 to 8443 on line 121:

<VirtualHost _default_:8443>

On lines 144 and 154, ensure that the following are set (these should be the defaults) and note the paths:

SSLCertificateFile "/etc/httpd/conf/server.crt"
SSLCertificateKeyFile "/etc/httpd/conf/server.key"

Put the certificates in the right place

Remember the files you created at the beginning? Now is the time to move them to where they are expected. Just overwrite anything is already there.

# mv /tmp/app.key /etc/httpd/conf/server.key
# mv /tmp/app.pem /etc/httpd/conf/server.crt

Start the web server and set it to start when the VM boots

# systemctl start httpd
# systemctl enable httpd

Everything should start up cleanly. If not, you may have a typo somewhere. The following command provides some good information to help locate the issue:

# systemctl status -l httpd.service

Once you have the web server started properly, take a little break. Now is just the configuration part.

Create the Python script that is our “application”

As I have written previously, Python is not my native language, but it is already present, and I can usually convince it to do what I need.

This CGI script takes the text data off the HTTP connection from the database server and formats it. It also has a simple filter field and button to change the data view a bit. Each button click submits a new request to the database and shows that the connection is still possible.

I have added some complexity here because it can provide useful information in some demonstration cases. If the IP address of the source webserver is in the “webservers” table, its name will be displayed on the page. Otherwise, the IP address is used when the Accessed via: line is printed. In certain situations, this can be useful for demonstrating that load balancing is working.

Create the script

# vi /etc/httpd/cgi-bin/

Add the following contents to, remembering that spacing is important. This script is also available on GitHub:

#!/usr/bin/env python3

# - script used to process database results for presentation

import os
import cgi
import requests

webservers = {
  '': 'web-01a',
  '': 'web-02a',
  '': 'web-03a'

print("<head><title>Customer Database</title></head>\n")
print("<h1>Customer Database Access</h1>\n")

remote = os.getenv("REMOTE_ADDR")
form = cgi.FieldStorage()
querystring = form.getvalue("querystring")

if remote in webservers:
    accessName = webservers[remote]
    accessName = remote

print("Accessed via:", accessName, "\n<p>")

if querystring != None:
    url = 'http://db-01a:3306/cgi-bin/' + querystring
    url = 'http://db-01a:3306/cgi-bin/'
    querystring = ""

r = requests.get(url)

print('<form action="/cgi-bin/">')
print(' Name Filter (blank for all records):')
print(' <input type="text" name="querystring" value="'+querystring+'">')
print(' <input type="submit" value="Apply">')

print("\n<table border=1 bordercolor=black cellpadding=5 cellspacing=0>")


# deal with the data coming across the wire
a = r.text.split("|\n#")
for row in a:
    if len(row) != 1:
        splitrow = row.split("|")
        for item in splitrow:
            if item != None:

Save and close the file.

Set execute permissions

# chmod 755 /etc/httpd/cgi-bin/

Verify executability

Now, if your database server is online and reachable, accessing the script via https on port 8443 will produce the data formatted as HTML:

# curl -k https://app-01a:8443/cgi-bin/

If you access it via a graphical browser like Chrome or Firefox, you can and witness the fruits of your labors thus far. Note that you will have to accept the self-signed certificate (scary web browser warnings), at least temporarily.

TIP: The -k switch to curl tells it to ignore the untrusted SSL certificate and continue in “insecure” mode.

Here is what it looks like from Chrome. I used the IP address in the URL here because I have not created a DNS entry for app-01a. My plan is to provide access to this application only via the web front-end and I am only accessing the app server directly as part of the build process.

The good news is that we are almost finished with the application. As you can see, you can use this as it stands as a “two-tier application” by creating a DNS record for the app-01a server, but I promised you three tiers. Stick with me and we’ll cover that in the next post.

Next post: COMING SOON