geoffthompson

Building a RHEL 9 Web Server on AWS

As with other posts out here, I seldom build servers, and as such, two things happen. First, I forget many of the particulars that go into setting up the server. Second, many of the particulars change because software versions change, as do best-practices.

So, this post covers the most recent steps I took to a) spin up a RHEL 8 server and b) install Apache dto c) serve up a PHP application that d) connects to an Oracle database.

Let's get to it.

Launch RDS Oracle 19c Database Instance

First things first, we need an Oracle database server. For this, we are going to using AWS RDS to create an Oracle database instance.

I will ignore steps to set up an AWS account.

Launch EC2 Web Server Instance

Now the database is running, let's create a web server to run our PHP application.

  1. Go to EC2 Dashboard in AWS.

  2. Click "Instances"

  3. Click "Launch Instances"

    1. Enter name: "RHEL8 Web Server"

    2. For AMI, search for "RHEL8", select "Red Hat Enterprise Linux 8 (HVM)" from Red Hat. Pricing at time this was selected was $0.156/hr. Click "Subscribe on instance launch"

    3. Instance type: t2.micro

    4. Key pair (I created a new ED25519 key pair for this server)

    5. Network settings: click "Edit"

      1. Check/change VPC

      2. Ensure the Subnet selected is one that is open to the Internet

      3. Auto-assign public IP: Enable

      4. Firewall (security groups): Create security group

        1. Security group name: RHEL8 Web Server
        2. Description: used default
        3. Inbound rules: ssh/Anywhere, HTTP/Anywhere, HTTPS/Anywhere
      5. Configure storage: 1x 50 GiB gp3

    6. Click "Launch instance"

After a few minutes, the server should be up and running, and we should be able to login via ssh and do a system update.

$ ssh-add -L
$ ssh-add --apple-use-keychain .ssh/KeyName.pem
$ ssh-add -L
$ ssh ec2-user@1.2.3.4 (server ip address)

To install packages on the shiny new RHEL8 web server, I will use the default package manager dnf, which is new to me (since the last time I built a server). dnf stands for "Dandified Yum", and has been adopted by Redhat to replace yum, the previous package manager (which by the way, still exists on the server).

$ sudo dnf update
$ sudo dnf upgrade

Install Oracle Instant Client

To be able to connect to the Oracle RDS database from the EC2 server, we need to download and install Oracle Instant Client.

Go to the Oracle Download Site and download the following RPMs:

$ curl -O https://download.oracle.com/otn_software/linux/instantclient/2340000/oracle-instantclient-basic-23.4.0.24.05-1.el8.x86_64.rpm
$ curl -O https://download.oracle.com/otn_software/linux/instantclient/2340000/oracle-instantclient-sqlplus-23.4.0.24.05-1.el8.x86_64.rpm
$ curl -O https://download.oracle.com/otn_software/linux/instantclient/2340000/oracle-instantclient-tools-23.4.0.24.05-1.el8.x86_64.rpm
$ curl -O https://download.oracle.com/otn_software/linux/instantclient/2340000/oracle-instantclient-devel-23.4.0.24.05-1.el8.x86_64.rpm

Since we downloaded the rpm files, we can install them using dnf:

$ sudo dnf install oracle-instantclient-basic-23.4.0.24.05-1.el8.x86_64.rpm
$ sudo dnf install oracle-instantclient-sqlplus-23.4.0.24.05-1.el8.x86_64.rpm
$ sudo dnf install oracle-instantclient-tools-23.4.0.24.05-1.el8.x86_64.rpm
$ sudo dnf install oracle-instantclient-devel-23.4.0.24.05-1.el8.x86_64.rpm

To test the installation, check to ensure sqlplus is installed and working (don't you wish all these packages would standardize on a single way to check the version?):

$ sqlplus -V

You need five things to connect to the database: the user, the password, the RDS endpoint, the port, and the database name (SID).

Here is how you enter all those things into sqlplus to connect to the database:

$ sqlplus 'my-user@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=my.db.endpoint.rds.amazonaws.com)(PORT=1521))(CONNECT_DATA=(SID=my-demo-db)))'

If everything installed correctly, you should see this response:

SQL*Plus: Release 23.0.0.0.0 - Production on Mon Jul 1 20:35:03 2024
Version 23.4.0.24.05

Copyright (c) 1982, 2024, Oracle.  All rights reserved.

Enter password: 

Enter the password to receive the SQL> prompt:

Last Successful login time: Mon Jul 01 2024 17:08:39 +00:00

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.23.0.0.0

SQL> 

Voila! You are able to connect to the RDS Oracle database from the EC2 server. Enter quit to exit.

Install Apache

Now, let's get the web server running.

$ sudo dnf install httpd
$ sudo apachectl start

This is probably the easiest step. You should be able to open a browser at the server IP address:

$ open http://123.456.78.9

You should see the default 'Red Hat Enterprise Linux Test Page' which is configured to come up by default.

The web root of a default installation exists at /var/www/html, but note that there is no index.html page at that location. This is because the configuration file is set up to launch a default page from another location. You can override this behavior by putting adding index.html in the web root:

$ cd /var/www/html
$ sudo echo "<html><body><h1>It works.</h1></body></html>" | sudo tee index.html > /dev/null

Now when you go to the server IP address, instead of the default Red Hat page, you will see your new index.html page.

To start, stop, restart, or check the status of the service:

$ sudo apachectl start
$ sudo apachectl stop
$ sudo apachectl restart
$ sudo apachectl status

PHP (with oci8)

Next up, we need to install all the php components that will get PHP working on the command line and in the browser, and connecting to an Oracle database via the Oracle Instant Client.

To get a list of PHP packages:

$ sudo dnf search php

This doesn't show versions. To check available PHP versions:

$ sudo dnf module list php

The default displays as 7.2. We want to install PHP 8.2, which is available but not enabled, so we need to enable it.

$ sudo dnf module enable php:8.2

Now we can install the PHP packages we need:

$ sudo dnf install php php-devel php-pear

In addition to php, we need php-devel and php-pear so that we can install the oci8 package via pecl.

Check versions:

$ php --version
$ pecl help version

Now install oci8.

$ sudo pecl install oci8

The install process requests the path to the Oracle Instant Client libraries that were already installed in the ORACLE_HOME directory, but including the /lib folder. We entered:

$ 'instantclient,/usr/lib/oracle/23/client64/lib'
Build process completed successfully
Installing '/usr/lib64/php/modules/oci8.so'
install ok: channel://pecl.php.net/oci8-3.3.0
configuration option "php_ini" is not set to php.ini location
You should add "extension=oci8.so" to php.ini

Looks like we can set a configuration option so pecl knows where the php.ini file is. Since we didn't, we will have to edit it manually. In addition to adding the oci8 extension, we need to make a few other changes to the php.ini file for testing purposes.

$ sudo vi /etc/php.ini

Scroll down to where the extensions are listed (almost all are commented out), and add "extension=oci8.so" at the end of the list and save the file.

The php.ini file is well documented, letting you know most of the settings and their default, development and production values. To show errors in php, find the display_errors setting and switch it to On.

Once all the software is installed and the configurations set, it is time to restart Apache:

$ sudo systemctl restart php-fpm
$ sudo apachectl restart

First, run a simple script to make sure php is working in both batch and in the web browser:

$ php --version
$ php -i
$ cd /var/www/html
$ sudo echo "<?php phpinfo(); ?>" > sudo tee test_php.php > /dev/null

Open a browser to http://123.456.78.9/test_php.php and you should see the PHP configuration, similar to what was dumped to stdout in the terminal window.

Look for the location of php.ini in both the browser and in batch. In our case, both environments are using the same php.ini file.

Next, do the same test with th test_oci.php script in both batch and in the web browser.

The batch test worked, but in the browser, we get a warning: "ORA-12546: TNS:permission denied Help: https://docs.oracle.com/error-help/db/ora-12546/"

ChatGPT had lots of stuff about config variables, but I am suspicious it is not accurate. The stuff about SELinux (Security Enhanced) looked more plausible, so I tried checking that:

$ sudo getentforce
Enforcing

Security contexts are above my pay grade, at least for now, but there was plenty to suggest that Apache did not have the proper context to leverage the oci8 libraries. So I (temporarily) disabled them:

$ sudo setenforce 0
$ sudo getenforce
Permissive

And the oci8 test script worked in the browser!

Next, I tried resetting the Enforcing and setting context for the Oracle Instant Client libaries per ChatGPT instructions:

$ sudo chcon -Rv -t httpd_sys_content_t /usr/lib/oracle/23/client64

The command seemed to work (after additional googling, I added the v=verbose directive to see a log of changes, and there were no issues), but the oci8 test script did not work in the browser.

We will return to this issue later, but for now will leave the server in Permissive mode.

Configure Users/Permissions

Now time to add the application user who is responsible for running the application.

$ sudo adduser avaion
$ sudo groupadd webapp
$ sudo usermod -a -G webapp avaion
$ sudo usermod -a -G webapp apache

Check group assignments

$ sudo lid avaion
$ sudo lid apache

Login as avaion and check group assignment

$ sudo su - avaion
$ groups

Create avaion directory

$ cd /opt
$ sudo mkdir avaion
$ sudo chown -R avaion:webapp /opt/avaion
$ sudo chown 2750 avaion
$ cd avaion

Change permissions to opt/avaion to give avaion access

$ sudo find /opt/avaion -type d -exec chmod 750 {} \;
$ sudo find /opt/avaion -type f -exec chmod 640 {} \;

For testing purposes, add a www directory under /opt/avaion (the real www directory will exist under a project controlled by git):

$ sudo su - avaion
$ cd /opt/avaion
$ mkdir www
$ exit

The last line exit above exits from the avaion user back to ec2-user to complete the rest of the configurations below.

In the web root, add a symbolic link to point to the newly created www directory:

$ cd /var/www/html
$ sudo ln -s /opt/avaion/www rpt

Move the previously created index.html, test_php.php and test_oci.php scripts from the web root to the www directory:

$ mv index.html rpt 
$ mv test_php.php rpt
$ mv test_oci.php rpt

NOTE: Symbolic links have 777 permissions, as they adopt permissions from the directory or file they actually point to.

Set all directory permissions:

$ cd /opt
$ sudo find avaion -type d -exec chmod 750 {} \;

Set all file permissions:

$ sudo find avaion -type f -exec chmod 640 {} \;

If you can now run all of the test scripts in the browser in their new rpt location, permissions are correct and the server is configured and ready for use.