Skip to main content

KodeKloud's 100 Days of DevOps: Day 11 - 20

·
Table of Contents
KodeKloud's 100 Days of DevOps - This article is part of a series.
Part 2: This Article

Introduction
#

Welcome back all to another post in the 100 Days of DevOps series! This set of problems has another round of Linux configuration and troubleshooting, interacting with various services - all key skills. You’ll see Nginx, MySQL, networking, and more in this group; this set of problems is particularly heavy on troubleshooting, which I’m always a big fan of. If you missed it, make sure to check out the first post in the series - I think you’ll enjoy it if you’re into this stuff as much as I am!

Spoiler alert!

In case you’re squeamish about this sort of thing, there are a bunch of spoilers ahead - proceed at your own (self-learning) risk. I’ll be diving into the nitty-gritty behind solutions where I can, so hopefully you’ll be able to learn a thing or two.

It’s also worth noting that if you’re working alongside me, you’ll see different users, IP addresses, passwords, or even completely different solutions occasionally - they rotate these with each challenge spawn on most challenges.

Day 11: Install and Configure Tomcat Server
#

Problem Prompt

The Nautilus application development team recently finished the beta version of one of their Java-based applications, which they are planning to deploy on one of the app servers in Stratos DC. After an internal team meeting, they have decided to use the tomcat application server. Based on the requirements mentioned below complete the task:

A. Install tomcat server on App Server 1.
B. Configure it to run on port 6300.
C. There is a ROOT.war file on Jump host at location /tmp.

Deploy it on this tomcat server and make sure the webpage works directly on base URL i.e curl http://stapp01:6300

For those unfamiliar, Tomcat is an open-source Apache web server based on Java. We can use it to deploy Java servlets for use on the web. It’s got quite a bit of features and documentation, but to get off the ground we can take a look at the RUNNING.txt file that comes bundled with the server. They host it at that link for convenience, but here are the important overarching steps:

  1. Download and Install a Java SE Runtime Environment (JRE)
  2. Download and Install Apache Tomcat
  3. Configure Environment Variables
  4. Start Up Tomcat

In addition to those steps, I’m also going to set up a separate tomcat user and group so that we’re running the eventual SystemD service under its own dedicated, non-root user. It’s good for system security as well as segmentation of data and privileges, and is generally considered best practice for production deployments. If you read through Day 1 of the series, this should all look really familiar:

[tony@stapp01 ~]$ sudo groupadd tomcat
[tony@stapp01 ~]$ sudo useradd -M -s /sbin/nologin -g tomcat -d /opt/tomcat tomcat
[tony@stapp01 ~]$ grep tomcat /etc/passwd
tomcat:x:1001:1001::/opt/tomcat:/sbin/nologin

Now that the user is in place, let’s check whether or not we have any version(s) of the Java JDK installed on the server. I’m choosing to do this based on experience, knowing that Tomcat is a project based on Java. We can easily do this with dnf:

[tony@stapp01 ~]$ dnf list --installed | grep java
java-11-openjdk.x86_64                         1:11.0.20.1.1-2.el9              @appstream    
java-11-openjdk-headless.x86_64                1:11.0.20.1.1-2.el9              @appstream    
javapackages-filesystem.noarch                 6.0.0-4.el9                      @appstream    
tzdata-java.noarch                             2024a-2.el9                      @appstream
[tony@stapp01 ~]$ java -version
openjdk version "11.0.20.1" 2023-08-24 LTS
OpenJDK Runtime Environment (Red_Hat-11.0.20.1.1-2) (build 11.0.20.1+1-LTS)
OpenJDK 64-Bit Server VM (Red_Hat-11.0.20.1.1-2) (build 11.0.20.1+1-LTS, mixed mode, sharing)

Another way to tell you’ll need one of the OpenJDK verions is by taking a look at the “Which Version?” page from the Tomcat site; you’ll see that we need Java 17 or above in order to run Tomcat 11. Currently, we show Java 11 locally - if we don’t change this out, we’ll get an error in $CATALINA_HOME/logs/catalina.out when we try to start the service later:

[tony@stapp01 tomcat]$ sudo bin/startup.sh
Using CATALINA_BASE:   /opt/tomcat
Using CATALINA_HOME:   /opt/tomcat
Using CATALINA_TMPDIR: /opt/tomcat/temp
Using JRE_HOME:        /
Using CLASSPATH:       /opt/tomcat/bin/bootstrap.jar:/opt/tomcat/bin/tomcat-juli.jar
Using CATALINA_OPTS:   
Tomcat started.
[tony@stapp01 tomcat]$ sudo cat logs/catalina.out
Unrecognized option: --enable-native-access=ALL-UNNAMED
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

Removing the old Java 11 JDK and installing a new version of Java is very straightforward - DNF has lots of versions above Java 17 available to choose from. Pick whichever your preferred version is and install it like so (I’m installing java-25-openjdk):

[tony@stapp01 tomcat]$ sudo dnf remove -y java-11-openjdk
Dependencies resolved.
=============================================================================
 Package                   Arch    Version                 Repository   Size
=============================================================================
Removing:
 java-11-openjdk           x86_64  1:11.0.20.1.1-2.el9     @appstream  1.0 M
Removing unused dependencies:
 copy-jdk-configs          noarch  4.0-3.el9               @appstream   19 k
 java-11-openjdk-headless  x86_64  1:11.0.20.1.1-2.el9     @appstream  169 M
 lua                       x86_64  5.4.4-4.el9             @appstream  593 k
 lua-posix                 x86_64  35.0-8.el9              @appstream  614 k

Transaction Summary
[...]

[tony@stapp01 tomcat]$ sudo dnf install -y java-25-openjdk
Last metadata expiration check: 0:45:52 ago on Sun Jul 12 23:06:32 2026.
Dependencies resolved.
=============================================================================
 Package                        Arch   Version               Repo       Size
=============================================================================
Installing:
 java-25-openjdk                x86_64 1:25.0.3.0.9-1.el9    appstream 385 k
Upgrading:
 glibc                          x86_64 2.34-273.el9          baseos    2.0 M
 glibc-common                   x86_64 2.34-273.el9          baseos    308 k
 glibc-devel                    x86_64 2.34-273.el9          appstream  39 k
 glibc-headers                  x86_64 2.34-273.el9          appstream 547 k
 glibc-minimal-langpack         x86_64 2.34-273.el9          baseos     23 k
 tzdata-java                    noarch 2026b-1.el9           appstream 223 k
Installing dependencies:
 java-25-openjdk-crypto-adapter x86_64 1:25.0.3.0.9-1.el9    appstream  46 k
 java-25-openjdk-headless       x86_64 1:25.0.3.0.9-1.el9    appstream  59 M
Installing weak dependencies:
 glibc-langpack-en              x86_64 2.34-273.el9          baseos    662 k

Transaction Summary
[...]

Perfect, we’re now set up with a Java that will function with Tomcat 11. Now we need to download the tomcat tarball and extract it somewhere. I set up a directory at /opt/tomcat and extract it there:

[tony@stapp01 ~]$ wget https://downloads.apache.org/tomcat/tomcat-11/v11.0.24/bin/apache-tomcat-11.0.24.tar.gz
--2026-07-12 20:03:48--  https://downloads.apache.org/tomcat/tomcat-11/v11.0.24/bin/apache-tomcat-11.0.24.tar.gz
Resolving downloads.apache.org (downloads.apache.org)... 95.216.224.44, 88.99.208.237, 2a01:4f9:2b:1cc2::2, ...
Connecting to downloads.apache.org (downloads.apache.org)|95.216.224.44|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 14443350 (14M) [application/x-gzip]
Saving to: ‘apache-tomcat-11.0.24.tar.gz’

apache-tomcat-11.0. 100%[================>]  13.77M  --.-KB/s    in 0.1s    

2026-07-12 20:03:49 (94.8 MB/s) - ‘apache-tomcat-11.0.24.tar.gz’ saved [14443350/14443350]

[tony@stapp01 ~]$ sudo mkdir /opt/tomcat
[tony@stapp01 ~]$ sudo tar xvf apache-tomcat-11.0.24.tar.gz -C /opt/tomcat --strip-components=1
apache-tomcat-11.0.24/conf/
apache-tomcat-11.0.24/conf/catalina.properties
apache-tomcat-11.0.24/conf/context.xml

        [...]

apache-tomcat-11.0.24/bin/tool-wrapper.sh
apache-tomcat-11.0.24/bin/version.sh

[tony@stapp01 ~]$ ls -la /opt/tomcat
total 172
drwxr-xr-x 9 root root  4096 Jul 12 20:18 .
drwxr-xr-x 1 root root  4096 Jul 12 20:17 ..
-rw-r----- 1 root root 26826 Jul  3 07:01 BUILDING.txt
-rw-r----- 1 root root  8626 Jul  3 07:01 CONTRIBUTING.md
-rw-r----- 1 root root 60517 Jul  3 07:01 LICENSE
-rw-r----- 1 root root  2333 Jul  3 07:01 NOTICE
-rw-r----- 1 root root  3224 Jul  3 07:01 README.md
-rw-r----- 1 root root  6470 Jul  3 07:01 RELEASE-NOTES
-rw-r----- 1 root root 16114 Jul  3 07:01 RUNNING.txt
drwxr-x--- 2 root root  4096 Jul 12 20:18 bin
drwx------ 2 root root  4096 Jul  3 07:01 conf
drwxr-x--- 2 root root  4096 Jul 12 20:18 lib
drwxr-x--- 2 root root  4096 Jul  3 07:01 logs
drwxr-x--- 2 root root  4096 Jul 12 20:18 temp
drwxr-x--- 7 root root  4096 Jul  3 07:01 webapps
drwxr-x--- 2 root root  4096 Jul  3 07:01 work

Now that it’s extracted, we can move the ROOT.war servlet over to the webapps directory so that we can reach it over the web. We need to move it from the jumpbox to stapp01 first, then to /opt/tomcat/webapps. Note the change in terminal - you can either use two terminal tabs or disconnect and reconnect after scp‘ing the file:

[thor@jump-host ~]$ scp /tmp/ROOT.war tony@stapp01:/home/tony/ROOT.war
tony@stapp01's password: 
ROOT.war                                   100% 4529    10.0MB/s   00:00
[tony@stapp01 tomcat]$ sudo cp ~/ROOT.war /opt/tomcat/webapps/

Lastly, we’ll need to configure the server to serve over port 6300 per the project instructions. This can be done by modifying the file conf/server.xml and changing the port on the Connector that’s present. By default, you’ll see it as <Connector port="8080" [...]>. After you modify the file, this is how it should look:

[tony@stapp01 tomcat]$ sudo cat /opt/tomcat/conf/server.xml | grep -A2 6300
<Connector port="6300" protocol="HTTP/1.1"
            connectionTimeout="20000"
            redirectPort="8443" />

Finally, we need to give permissions to the Tomcat user so that it can start and stop the service as needed, as well as writing out the miscellaneous files it needs. The easiest way is to just chown all of the files and folders under /opt/tomcat to the tomcat user:

[tony@stapp01 ~]$ sudo chown -R tomcat:tomcat /opt/tomcat

Now that we’ve done all of the prerequisite setup, we can finally move to getting the service up and running. To ensure the server runs consistently, we can create a SystemD service file at /etc/systemd/system/tomcat.service. Use whatever editor you like, but this is how it needs to look once it’s all finished:

[Unit]
Description=Tomcat Server
After=syslog.target network.target

[Service]
Type=forking
User=tomcat
Group=tomcat

Environment=JAVA_HOME=/usr/lib/jvm/jre
Environment='JAVA_OPTS=-Djava.awt.headless=true'
Environment=CATALINA_HOME=/opt/tomcat
Environment=CATALINA_BASE=/opt/tomcat
Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid
Environment='CATALINA_OPTS=-Xms512M -Xmx1024M'
ExecStart=/opt/tomcat/bin/catalina.sh start
ExecStop=/opt/tomcat/bin/catalina.sh stop

[Install]
WantedBy=multi-user.target

Once that is saved, we can finally start the service. First we have to run a daemon-reload so that systemctl picks up the new service file, and then we enable and run the service. If all goes to plan, we’ll have a service that shows as active (running):

[tony@stapp01 tomcat]$ sudo systemctl daemon-reload
[tony@stapp01 tomcat]$ sudo systemctl enable tomcat
Created symlink /etc/systemd/system/multi-user.target.wants/tomcat.service → /etc/systemd/system/tomcat.service.
[tony@stapp01 tomcat]$ sudo systemctl start tomcat
[tony@stapp01 tomcat]$ sudo systemctl status tomcat.service
● tomcat.service - Tomcat Server
     Loaded: loaded (/etc/systemd/system/tomcat.service; enabled; preset: di>
     Active: active (running) since Sun 2026-07-12 23:55:32 UTC; 6s ago
    Process: 48935 ExecStart=/opt/tomcat/bin/catalina.sh start (code=exited,>
   Main PID: 48961 (java)
      Tasks: 31 (limit: 404712)
     Memory: 122.3M
        CPU: 1.861s
     CGroup: /system.slice/tomcat.service
             └─48961 /usr/lib/jvm/jre/bin/java -Djava.util.logging.config.fi>

Jul 12 23:55:32 stapp01 systemd[1]: Starting Tomcat Server...
Jul 12 23:55:32 stapp01 catalina.sh[48935]: Existing PID file found during s>
Jul 12 23:55:32 stapp01 catalina.sh[48935]: Removing/clearing stale PID file.
Jul 12 23:55:32 stapp01 catalina.sh[48935]: Tomcat started.
Jul 12 23:55:32 stapp01 systemd[1]: Started Tomcat Server.

Last step - we can quickly verify that our server is running by checking with curl:

[tony@stapp01 tomcat]$ curl http://localhost:6300
<!DOCTYPE html>
<!--
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->
<html>
    <head>
        <title>SampleWebApp</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <h2>Welcome to xFusionCorp Industries!</h2>
        <br>
    
    </body>
</html>

Day 12: Linux Networking Services
#

Problem Prompt

Our monitoring tool has reported an issue in Stratos Datacenter. One of our app servers has an issue, as its Apache service is not reachable on port 8088 (which is the Apache port). The service itself could be down, the firewall could be at fault, or something else could be causing the issue.

Use tools like telnet, netstat, etc. to find and fix the issue. Also make sure Apache is reachable from the jump host without compromising any security settings.

Once fixed, you can test the same using command curl http://stapp01:8088 command from jump host.

Note: Please do not try to alter the existing index.html code, as it will lead to task failure.

Okay, so based on the problem prompt we need to be ready to check out the problems bottom-up - we’ll start with sshing over as tony and taking a look at the service for httpd.

[tony@stapp01 ~]$ sudo systemctl status httpd.service
× httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; preset: disabled)
     Active: failed (Result: exit-code) since Tue 2026-07-14 14:00:23 UTC; 1min 7s ago
       Docs: man:httpd.service(8)
    Process: 13177 ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND (code=exited, status=1/FAILURE)
   Main PID: 13177 (code=exited, status=1/FAILURE)
     Status: "Reading configuration..."
        CPU: 34ms

Jul 14 14:00:23 stapp01 systemd[1]: Starting The Apache HTTP Server...
Jul 14 14:00:23 stapp01 httpd[13177]: AH00558: httpd: Could not reliably determine the server's fully qualified >
Jul 14 14:00:23 stapp01 httpd[13177]: (98)Address already in use: AH00072: make_sock: could not bind to address >
Jul 14 14:00:23 stapp01 httpd[13177]: (98)Address already in use: AH00072: make_sock: could not bind to address >
Jul 14 14:00:23 stapp01 httpd[13177]: no listening sockets available, shutting down
Jul 14 14:00:23 stapp01 httpd[13177]: AH00015: Unable to open logs
Jul 14 14:00:23 stapp01 systemd[1]: httpd.service: Main process exited, code=exited, status=1/FAILURE
Jul 14 14:00:23 stapp01 systemd[1]: httpd.service: Failed with result 'exit-code'.
Jul 14 14:00:23 stapp01 systemd[1]: Failed to start The Apache HTTP Server.

Based on that error, something else has the port that httpd needs to successfully start. In an enterprise situation, you’d likely investigate and need to see what’s taking up the port, if another team deployed it, if there are negative knock-on effects of removing the service…you get the gist. In a lab like this, though, we only care about httpd getting up and running so we can just nuke the service from orbit once we have a valid process ID. Let’s take a look at what’s listening on the TCP side with ss and the -p flag to grab the PID:

[tony@stapp01 ~]$ sudo ss -antp
State           Recv-Q           Send-Q                     Local Address:Port                      Peer Address:Port           Process                                                           
LISTEN          0                128                              0.0.0.0:8080                           0.0.0.0:*               users:(("ttyd",pid=60,fd=11))                                    
LISTEN          0                128                              0.0.0.0:22                             0.0.0.0:*               users:(("sshd",pid=1576,fd=3))                                   
LISTEN          0                10                             127.0.0.1:8088                           0.0.0.0:*               users:(("sendmail",pid=12753,fd=4))                              
ESTAB           0                0                          10.244.49.127:22                       10.244.97.158:42724           users:(("sshd",pid=36880,fd=4),("sshd",pid=36711,fd=4))          
LISTEN          0                128                                 [::]:22                                [::]:*               users:(("sshd",pid=1576,fd=4))
[tony@stapp01 ~]$ sudo kill 12753

Now that we’ve killed off the sendmail process, let’s try restarting the httpd service and with any luck, we’ll have a functioning web server. After the service comes up, we confirm it’s listening on the correct port with ss:

[tony@stapp01 ~]$ sudo systemctl restart httpd.service
[tony@stapp01 ~]$ sudo systemctl status httpd.service
● httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; preset: disabled)
     Active: active (running) since Tue 2026-07-14 14:02:35 UTC; 9s ago
       Docs: man:httpd.service(8)
   Main PID: 37462 (httpd)
     Status: "Total requests: 0; Idle/Busy workers 100/0;Requests/sec: 0; Bytes served/sec:   0 B/sec"
      Tasks: 177 (limit: 404712)
     Memory: 15.0M
        CPU: 64ms
     CGroup: /system.slice/httpd.service
             ├─37462 /usr/sbin/httpd -DFOREGROUND
             ├─37469 /usr/sbin/httpd -DFOREGROUND
             ├─37470 /usr/sbin/httpd -DFOREGROUND
             ├─37471 /usr/sbin/httpd -DFOREGROUND
             └─37472 /usr/sbin/httpd -DFOREGROUND

Jul 14 14:02:35 stapp01 systemd[1]: Starting The Apache HTTP Server...
Jul 14 14:02:35 stapp01 httpd[37462]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 10.244.49.127. Set the 'ServerName' directive globally to supp>
Jul 14 14:02:35 stapp01 httpd[37462]: Server configured, listening on: port 8088
Jul 14 14:02:35 stapp01 systemd[1]: Started The Apache HTTP Server.
[tony@stapp01 ~]$ sudo ss -antp
State     Recv-Q    Send-Q       Local Address:Port         Peer Address:Port     Process                                                                                                         
LISTEN    0         128                0.0.0.0:8080              0.0.0.0:*         users:(("ttyd",pid=60,fd=11))                                                                                  
LISTEN    0         128                0.0.0.0:22                0.0.0.0:*         users:(("sshd",pid=1576,fd=3))                                                                                 
ESTAB     0         0            10.244.49.127:22          10.244.97.158:42724     users:(("sshd",pid=36880,fd=4),("sshd",pid=36711,fd=4))                                                        
LISTEN    0         511                      *:8088                    *:*         users:(("httpd",pid=37472,fd=4),("httpd",pid=37471,fd=4),("httpd",pid=37470,fd=4),("httpd",pid=37462,fd=4))    
LISTEN    0         128                   [::]:22                   [::]:*         users:(("sshd",pid=1576,fd=4))

Perfect! We now have a functioning httpd instance. The next step is to see if we can hit index.html locally with curl; if it works, we can mostly rule out our web server setup being the culprit.

[tony@stapp01 ~]$ curl -I localhost:8088
HTTP/1.1 403 Forbidden
Date: Tue, 14 Jul 2026 14:03:37 GMT
Server: Apache/2.4.62 (CentOS Stream)
Last-Modified: Fri, 12 Dec 2025 14:14:29 GMT
ETag: "296919-645c1e12b9b40"
Accept-Ranges: bytes
Content-Length: 2713881
Content-Type: text/html; charset=UTF-8

A 403 Forbidden response isn’t a good sign - it could mean a few things. The easiest check, though, is to see if we even have an index.html living at the document root. To do that, I checked the httpd.conf to see where it expects the file, and then a simple ls -al to see if the files are present:

[tony@stapp01 ~]$ grep -i documentroot /etc/httpd/conf/httpd.conf 
# DocumentRoot: The directory out of which you will serve your
DocumentRoot "/var/www/html"
    # access content that does not live under the DocumentRoot.
[tony@stapp01 ~]$ ls -la /var/www/html
total 8
drwxr-xr-x 2 root root 4096 Jun  1 13:26 .
drwxr-xr-x 4 root root 4096 Jun 10 09:03 ..

Now that we’ve confirmed the file isn’t where it’s expected, we have to find it. The easiest way to find a file is, well, to use the aptly-named find. Once we’ve located a valid file, we can copy it over to /var/www/html to resolve the 403 Forbidden error:

[tony@stapp01 ~]$ find / -name "index.html" 2>/dev/null
/usr/share/doc/oniguruma/index.html
/usr/share/nginx/html/index.html
/usr/share/testpage/index.html
/usr/share/httpd/noindex/index.html
[tony@stapp01 ~]$ sudo cp /usr/share/testpage/index.html /var/www/html
[tony@stapp01 ~]$ ls -la /var/www/html
total 2664
drwxr-xr-x 1 root root    4096 Jul 14 14:04 .
drwxr-xr-x 1 root root    4096 Jun 10 09:03 ..
-rw-r--r-- 1 root root 2713881 Jul 14 14:04 index.html
[tony@stapp01 ~]$ curl -I localhost:8088
HTTP/1.1 200 OK
Date: Tue, 14 Jul 2026 14:05:01 GMT
Server: Apache/2.4.62 (CentOS Stream)
Last-Modified: Tue, 14 Jul 2026 14:04:52 GMT
ETag: "296919-65692b0d67019"
Accept-Ranges: bytes
Content-Length: 2713881
Content-Type: text/html; charset=UTF-8

If we test access from thor@jump-box, we’ll still see that there’s an error - No route to host. There’s a couple of networking issues that could be present for that, but we’ll start by identifying which firewall is present on the box (in this case, iptables) and then list out the rules that it has enabled:

[tony@stapp01 ~]$ which firewall-cmd
/usr/bin/which: no firewall-cmd in (/home/tony/.local/bin:/home/tony/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin)
[tony@stapp01 ~]$ which iptables
/usr/sbin/iptables
[tony@stapp01 ~]$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTABLISHED
ACCEPT     icmp --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     tcp  --  anywhere             anywhere             state NEW tcp dpt:ssh
REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

At first, you might think that there’s nothing blocking - so what gives? iptables rules process top-to-bottom, meaning that if it doesn’t match a rule, it continues down the list. Since we don’t have an explicit ACCEPT rule set up for port 8088, it slides all the way to the REJECT all at the end of the INPUT chain, ultimately leading to any outside source being stopped from connecting to our web server.

To fix this, we need to add a rule to the top of the chain (or at least above the REJECT rule).

[tony@stapp01 ~]$ sudo iptables -I INPUT 1 -i eth0 -p TCP --dport 8088 -j ACCEPT
[tony@stapp01 ~]$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:radan-http
ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTABLISHED
ACCEPT     icmp --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     tcp  --  anywhere             anywhere             state NEW tcp dpt:ssh
REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Now that we’ve added that rule, we test back from thor@jump-box to see if we can reach the web service from outside:

[thor@jump-host ~]$ curl -I http://stapp01:8088
HTTP/1.1 200 OK
Date: Tue, 14 Jul 2026 14:09:41 GMT
Server: Apache/2.4.62 (CentOS Stream)
Last-Modified: Tue, 14 Jul 2026 14:04:52 GMT
ETag: "296919-65692b0d67019"
Accept-Ranges: bytes
Content-Length: 2713881
Content-Type: text/html; charset=UTF-8

And with that, our web service is ready to go for this task!

Day 13: IPtables Installation and Configuration
#

Problem Prompt

We have one of our websites up and running on our Nautilus infrastructure in Stratos DC. Our security team has raised a concern that right now Apache’s port i.e 8084 is open for all since there is no firewall installed on these hosts. So we have decided to add some security layer for these hosts and after discussions and recommendations we have come up with the following requirements:

  1. Install iptables and all its dependencies on each app host.
  2. Block incoming port 8084 on all apps for everyone except for LBR host.
  3. Make sure the rules remain, even after system reboot.

As with other tasks in the series, I’ll show all of the examples with stapp01 only, but you’ll need to repeat all of the steps shown against all three of the app servers in order to complete the task successfully. First, let’s check the host to see what we have installed so that we can know what to grab from dnf:

[tony@stapp01 ~]$ dnf list --installed | grep iptables
iptables-libs.x86_64                           1.8.10-11.el9                    @baseos       
iptables-services.noarch                       1.8.10-11.1.el9                  @epel

So we have the supporting libraries, but not the iptables binaries themselves. Let’s install them and enable the service so that we can set up the firewall rules.

[tony@stapp01 ~]$ sudo dnf install -y iptables
Last metadata expiration check: 0:15:24 ago on Tue Jul 14 14:32:04 2026.
Dependencies resolved.
=================================================================================================================
 Package                            Architecture         Version                        Repository          Size
=================================================================================================================
Installing:
 iptables-legacy                    x86_64               1.8.10-11.1.el9                epel                50 k
Installing dependencies:
 iptables-legacy-libs               x86_64               1.8.10-11.1.el9                epel                38 k

                        [...]

  Verifying        : iptables-legacy-libs-1.8.10-11.1.el9.x86_64                                             2/2 

Installed:
  iptables-legacy-1.8.10-11.1.el9.x86_64               iptables-legacy-libs-1.8.10-11.1.el9.x86_64              

Complete!
[tony@stapp01 ~]$ sudo systemctl enable --now iptables.service
Created symlink /etc/systemd/system/multi-user.target.wants/iptables.service → /usr/lib/systemd/system/iptables.service.
[tony@stapp01 ~]$ sudo systemctl status iptables.service
● iptables.service - IPv4 firewall with iptables
     Loaded: loaded (/usr/lib/systemd/system/iptables.service; enabled; preset: disabled)
     Active: active (exited) since Tue 2026-07-14 20:48:11 UTC; 10s ago
    Process: 46099 ExecStart=/usr/libexec/iptables/iptables.init start (code=exited, status=0/SUCCESS)
   Main PID: 46099 (code=exited, status=0/SUCCESS)
        CPU: 12ms

Jul 14 20:48:11 stapp01 systemd[1]: Starting IPv4 firewall with iptables...
Jul 14 20:48:11 stapp01 iptables.init[46099]: iptables: Applying firewall rules: [  OK  ]
Jul 14 20:48:11 stapp01 systemd[1]: Finished IPv4 firewall with iptables.

By installing and enabling the service, we’re not actually enabling anything yet. No traffic will be blocked, no services protected. We can verify this by listing rules with iptables -L:

[tony@stapp01 ~]$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

And also by testing the connectivity from an outside source, in this case the jump box host:

[thor@jump-host ~]$ curl -I http://stapp01:8084
HTTP/1.1 403 Forbidden
Date: Tue, 14 Jul 2026 20:31:08 GMT
Server: Apache/2.4.62 (CentOS Stream)
Last-Modified: Fri, 12 Dec 2025 14:14:29 GMT
ETag: "296919-645c1e12b9b40"
Accept-Ranges: bytes
Content-Length: 2713881
Content-Type: text/html; charset=UTF-8

The rule system is fairly straightforward for iptables rules are processed one at a time, from top to bottom, in the chain that matches the traffic pattern that applies. Our goal is to prevent traffic to port 8084 from all sources except from the load balancer. The easiest way to do that is to put a “reject all” rule at the bottom of the ruleset and then allow the ports we do want enabled.

This is the rough order of operations for setting up the rules:

  • First, we have to add a rule allowing SSH - otherwise we won’t be able to connect to the box once the REJECT rule gets added
  • Next, we append a reject all rule to the bottom of the ruleset
  • Finally, we add an allow for stlb01 to reach the port in question

With that out of the way, let’s add our rules and then list out our ruleset to confirm. Note that because the KodeKloud labs are spawned via Kubernetes calls, stlb01 will translate automatically to 10-244-29-245.stlb01.3vvlmha5kdtyhwcz.svc.cluster.local:

[tony@stapp01 ~]$ sudo iptables -I INPUT -p TCP --dport 22 -j ACCEPT
[tony@stapp01 ~]$ sudo iptables -A INPUT -p TCP -j REJECT
[tony@stapp01 ~]$ sudo iptables -I INPUT -p TCP --dport 8084 -s stlb01 -j ACCEPT
[tony@stapp01 ~]$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     tcp  --  10-244-29-245.stlb01.3vvlmha5kdtyhwcz.svc.cluster.local  anywhere             tcp dpt:rfe
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:ssh
REJECT     tcp  --  anywhere             anywhere             reject-with icmp-port-unreachable

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

With those rules in place, let’s test them before making them permanent. We’ll re-run the curl from earlier on the jump box again:

thor@jump-host ~$ curl -I http://stapp01:8084
curl: (7) Failed to connect to stapp01 port 8084: Connection refused

And to confirm that the port is open to the load balancer, we test the same command from stlb01:

[loki@stlb01 ~]$ curl -I http://stapp01:8084
HTTP/1.1 403 Forbidden
Date: Tue, 14 Jul 2026 20:35:30 GMT
Server: Apache/2.4.62 (CentOS Stream)
Last-Modified: Fri, 12 Dec 2025 14:14:29 GMT
ETag: "296919-645c1e12b9b40"
Accept-Ranges: bytes
Content-Length: 2713881
Content-Type: text/html; charset=UTF-8

Perfect! We have the rules enabled, connectivity from stlb01, and all that’s left is to permanently save the rules so that they can be enabled from the get-go. To do that, we use the iptables-save command, saving the output to /etc/sysconfig/iptables since we’re on a CentOS host:

[tony@stapp01 ~]$ sudo sh -c '/sbin/iptables-save > /etc/sysconfig/iptables'

Now all that’s left is to repeat the process for both stapp02 and stapp03!

Day 14: Linux Process Troubleshooting
#

Problem Prompt

The production support team of xFusionCorp Industries has deployed some of the latest monitoring tools to keep an eye on every service, application, etc. running on the systems. One of the monitoring systems reported about Apache service unavailability on one of the app servers in Stratos DC.

Identify the faulty app host and fix the issue. Make sure Apache service is up and running on all app hosts. They might not have hosted any code yet on these servers, so you don’t need to worry if Apache isn’t serving any pages. Just make sure the service is up and running. Also, make sure Apache is running on port 8085 on all app servers.

From the problem prompt, we know that one or more hosts have some kind of issue. Since that’s nebulous and we know the final desired state, i.e. Apache is up and running on port 8085, let’s see if we can call the service on each of the hosts as-is:

thor@jump-host ~$ curl -I http://stapp01:8085
curl: (7) Failed to connect to stapp01 port 8085: Connection refused
thor@jump-host ~$ curl -I http://stapp02:8085
HTTP/1.1 403 Forbidden
Date: Tue, 14 Jul 2026 21:30:28 GMT
Server: Apache/2.4.62 (CentOS Stream)
Last-Modified: Fri, 12 Dec 2025 14:14:29 GMT
ETag: "296919-645c1e12b9b40"
Accept-Ranges: bytes
Content-Length: 2713881
Content-Type: text/html; charset=UTF-8

thor@jump-host ~$ curl -I http://stapp03:8085
HTTP/1.1 403 Forbidden
Date: Tue, 14 Jul 2026 21:30:32 GMT
Server: Apache/2.4.62 (CentOS Stream)
Last-Modified: Fri, 12 Dec 2025 14:14:29 GMT
ETag: "296919-645c1e12b9b40"
Accept-Ranges: bytes
Content-Length: 2713881
Content-Type: text/html; charset=UTF-8

Right off the bat, we know that stapp01 has issues - and it’s likely the only one, given stapp02 and stapp03 return the same response to our curl. We’ll SSH in to stapp01 and start poking around. Let’s start with the service status to see if that nets us any good troubleshooting info:

[tony@stapp01 ~]$ sudo systemctl status httpd.service
× httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; preset: disabled)
     Active: failed (Result: exit-code) since Tue 2026-07-14 21:16:53 UTC; 14min ago
       Docs: man:httpd.service(8)
    Process: 20807 ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND (code=exited, status=1/FAILURE)
   Main PID: 20807 (code=exited, status=1/FAILURE)
     Status: "Reading configuration..."
        CPU: 34ms

Jul 14 21:16:53 stapp01 systemd[1]: Starting The Apache HTTP Server...
Jul 14 21:16:53 stapp01 httpd[20807]: AH00558: httpd: Could not reliably determine the server's fully qualified >
Jul 14 21:16:53 stapp01 httpd[20807]: (98)Address already in use: AH00072: make_sock: could not bind to address >
Jul 14 21:16:53 stapp01 httpd[20807]: (98)Address already in use: AH00072: make_sock: could not bind to address >
Jul 14 21:16:53 stapp01 httpd[20807]: no listening sockets available, shutting down
Jul 14 21:16:53 stapp01 httpd[20807]: AH00015: Unable to open logs
Jul 14 21:16:53 stapp01 systemd[1]: httpd.service: Main process exited, code=exited, status=1/FAILURE
Jul 14 21:16:53 stapp01 systemd[1]: httpd.service: Failed with result 'exit-code'.
Jul 14 21:16:53 stapp01 systemd[1]: Failed to start The Apache HTTP Server.

From that error output, we can see that the service can’t get the port it’s requesting - let’s see if there’s something else taking up the port using ss with the PID flag:

[tony@stapp01 ~]$ sudo ss -antp
State         Recv-Q        Send-Q                  Local Address:Port                 Peer Address:Port         Process                                                                                                          
LISTEN        0             128                           0.0.0.0:8080                      0.0.0.0:*             users:(("ttyd",pid=60,fd=11))                                                                                   
LISTEN        0             128                           0.0.0.0:22                        0.0.0.0:*             users:(("sshd",pid=1466,fd=3))                                                                                  
LISTEN        0             10                          127.0.0.1:8085                      0.0.0.0:*             users:(("sendmail",pid=20159,fd=4))                                                                             
ESTAB         0             0                      10.244.234.248:22                   10.244.49.48:35050         users:(("sshd",pid=47384,fd=4),("sshd",pid=47258,fd=4))                                                         
LISTEN        0             128                              [::]:22                           [::]:*             users:(("sshd",pid=1466,fd=4))

So there’s a sendmail instance running once again - as mentioned in Day 12, we’d normally take more care, but in this lab scenario we’ll just kill the process that’s taking the port and restart our Apache service:

[tony@stapp01 ~]$ sudo kill 20159
[tony@stapp01 ~]$ sudo systemctl restart httpd.service
[tony@stapp01 ~]$ sudo systemctl status httpd.service
● httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; preset: disabled)
     Active: active (running) since Tue 2026-07-14 21:32:54 UTC; 6s ago
       Docs: man:httpd.service(8)
   Main PID: 48052 (httpd)
     Status: "Started, listening on: port 8085"
      Tasks: 177 (limit: 404712)
     Memory: 15.0M
        CPU: 61ms
     CGroup: /system.slice/httpd.service
             ├─48052 /usr/sbin/httpd -DFOREGROUND
             ├─48059 /usr/sbin/httpd -DFOREGROUND
             ├─48060 /usr/sbin/httpd -DFOREGROUND
             ├─48061 /usr/sbin/httpd -DFOREGROUND
             └─48062 /usr/sbin/httpd -DFOREGROUND

Jul 14 21:32:54 stapp01 systemd[1]: Starting The Apache HTTP Server...
Jul 14 21:32:54 stapp01 httpd[48052]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 10.244.234.248. Set the 'ServerName' directive globally to sup>
Jul 14 21:32:54 stapp01 httpd[48052]: Server configured, listening on: port 8085
Jul 14 21:32:54 stapp01 systemd[1]: Started The Apache HTTP Server.

Now that that’s up, we need to check from the jump box. That’s easily done with our curl from earlier:

thor@jump-host ~$ curl -I http://stapp01:8085
HTTP/1.1 403 Forbidden
Date: Tue, 14 Jul 2026 21:33:21 GMT
Server: Apache/2.4.62 (CentOS Stream)
Last-Modified: Fri, 12 Dec 2025 14:14:29 GMT
ETag: "296919-645c1e12b9b40"
Accept-Ranges: bytes
Content-Length: 2713881
Content-Type: text/html; charset=UTF-8

With that, we’re finished with this task - on to Day 15!

Day 15: Setup SSL for Nginx
#

Problem Prompt

The system admins team of xFusionCorp Industries needs to deploy a new application on App Server 1 in Stratos Datacenter. They have some pre-requites to get ready that server for application deployment. Prepare the server as per requirements shared below:

  1. Install and configure nginx on App Server 1.
  2. On App Server 1 there is a self signed SSL certificate and key present at location /tmp/nautilus.crt and /tmp/nautilus.key. Move them to some appropriate location and deploy the same in Nginx.
  3. Create an index.html file with content Welcome! under Nginx document root.
  4. For final testing try to access the App Server 1 link (via hostname) from jump host using curl command. For example: curl -Ik https:///.

This task is pretty straightforward - not too much in the way of thought process or variability in how to approach it. We’ll start by installing Nginx with dnf and then enabling and starting the service with systemctl:

[tony@stapp01 ~]$ sudo dnf install -y nginx
CentOS Stream 9 - BaseOS                     4.4 MB/s | 9.0 MB     00:02    
CentOS Stream 9 - AppStream                   13 MB/s |  28 MB     00:02    
CentOS Stream 9 - Extras packages             31 kB/s |  21 kB     00:00    
                  [...]
Complete!
[tony@stapp01 ~]$ sudo systemctl enable --now nginx.service
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.
[tony@stapp01 ~]$ sudo systemctl status nginx.service
● nginx.service - The nginx HTTP and reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset:>
     Active: active (running) since Wed 2026-07-15 00:38:24 UTC; 7s ago
    Process: 36164 ExecStartPre=/usr/bin/rm -f /run/nginx.pid (code=exited, >
    Process: 36172 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=0/SU>
    Process: 36185 ExecStart=/usr/sbin/nginx (code=exited, status=0/SUCCESS)
   Main PID: 36192 (nginx)
                  [...]

Now that the service is up, we need to configure the site. If you’re unfamiliar with how Nginx does things, all of the configuration files are housed under /etc/nginx/. The primary configuration file is /etc/nginx/nginx.conf, although occasionally you’ll see setups that use drop-ins under /etc/nginx/conf.d/*.conf. It’s nice for more complex setups, but for our use case with this lab, we’ll stick to working with just the main configuration file.

Looking through nginx.conf, starting on line 56 of the file we’ll see the SSL server section:

                  [...]
# Settings for a TLS enabled server.
#
#    server {
#        listen       443 ssl http2;
#        listen       [::]:443 ssl http2;
#        server_name  _;
#        root         /usr/share/nginx/html;
#
#        ssl_certificate "/etc/pki/nginx/server.crt";
#        ssl_certificate_key "/etc/pki/nginx/private/server.key";
#        ssl_session_cache shared:SSL:1m;
#        ssl_session_timeout  10m;
#        ssl_ciphers PROFILE=SYSTEM;
#        ssl_prefer_server_ciphers on;
#
#        # Load configuration files for the default server block.
#        include /etc/nginx/default.d/*.conf;
#
#        error_page 404 /404.html;
#            location = /40x.html {
#        }
#
#        error_page 500 502 503 504 /50x.html;
#            location = /50x.html {
#        }
#    }
                  [...]

As you can see, the parts we’re interested in are the ones that map to the problem prompt:

  • The SSL certificate is stored at /etc/pki/nginx/server.crt
  • The SSL private key is stored at /etc/pki/nginx/private/server.key
  • The document root is at /usr/share/nginx/html - this is where we’ll put the index.html file later

Now we just need to set up the files, uncomment the server block, and then restart the service.

First, we’ll make the index page:

[tony@stapp01 ~]$ sudo sh -c 'echo "Welcome!" > /usr/share/nginx/html/index.html'
[tony@stapp01 ~]$ sudo ls -la /usr/share/nginx/html/index.html
lrwxrwxrwx 1 root root 25 May 27 10:16 /usr/share/nginx/html/index.html -> ../../testpage/index.html
[tony@stapp01 ~]$ sudo cat /usr/share/nginx/html/index.html
Welcome!

Now, we move the certs. Note that we had to create the /etc/pki/nginx/private directory, since it doesn’t exist by default:

[tony@stapp01 ~]$ sudo ls -al /etc/pki/nginx/private/
ls: cannot access '/etc/pki/nginx/private/': No such file or directory
[tony@stapp01 ~]$ sudo mkdir -p /etc/pki/nginx/private/
[tony@stapp01 ~]$ sudo cp /tmp/nautilus.crt /etc/pki/nginx/server.crt
[tony@stapp01 ~]$ sudo cp /tmp/nautilus.key /etc/pki/nginx/private/server.key

And finally, we need to modify /etc/nginx/nginx.conf and uncomment all of the lines in the SSL server block. Once we’ve done that, we can restart the service and have an SSL server running!

[tony@stapp01 ~]$ # We uncomment all of the SSL server block in the
[tony@stapp01 ~]$ # nginx.conf file.
[tony@stapp01 ~]$ sudo vi /etc/nginx/nginx.conf
[tony@stapp01 ~]$ sudo systemctl restart nginx.service

Now we just need to check from the jump box to make sure that we have successfully deployed the SSL server. We can do so using the -k flag with curl to disable certificate checking, otherwise it won’t accept the self-signed certs we used:

thor@jump-host ~$ curl -Ik https://stapp01
HTTP/2 200 
server: nginx/1.20.1
date: Wed, 15 Jul 2026 00:46:20 GMT
content-type: text/html
content-length: 9
last-modified: Wed, 15 Jul 2026 00:41:09 GMT
etag: "6a56d725-9"
accept-ranges: bytes

Day 15 down!

Day 16: Install and Configure Nginx as an LBR
#

Problem Prompt

Day by day traffic is increasing on one of the websites managed by the Nautilus production support team. Therefore, the team has observed a degradation in website performance. Following discussions about this issue, the team has decided to deploy this application on a high availability stack i.e on Nautilus infra in Stratos DC. They started the migration last month and it is almost done, as only the LBR server configuration is pending. Configure LBR server as per the information given below:

A. Install nginx on the LBR (load balancer) server if it is not already installed. B. Configure load-balancing with the http context making use of all App Servers. Ensure that you update only the main Nginx configuration file located at /etc/nginx/nginx.conf. C. Make sure you do not update the apache port that is already defined in the apache configuration on all app servers, also make sure apache service is up and running on all the app servers. D. Once done, you can access the website by running curl http://stlb01:80 in the terminal.

So per the prompt, we need to set up load balancing to all of the backend servers. As before, I’m showing stapp01 and stlb01, but make sure to repeat the checks against the other two app servers to ensure completeness! With that said, let’s make sure Apache’s up on our app servers - there’s no point to configuring load balancing on stlb01 if there’s no server to receive the connection:

[tony@stapp01 ~]$ sudo systemctl status httpd.service
● httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; preset: disabled)
     Active: active (running) since Wed 2026-07-15 13:57:36 UTC; 1min 3s ago
       Docs: man:httpd.service(8)
   Main PID: 21620 (httpd)
     Status: "Total requests: 0; Idle/Busy workers 100/0;Requests/sec: 0; Bytes served/sec:   0 B/sec"
      Tasks: 177 (limit: 404242)
     Memory: 15.1M
        CPU: 91ms
     CGroup: /system.slice/httpd.service
             ├─21620 /usr/sbin/httpd -DFOREGROUND
             ├─21627 /usr/sbin/httpd -DFOREGROUND
             ├─21628 /usr/sbin/httpd -DFOREGROUND
             ├─21629 /usr/sbin/httpd -DFOREGROUND
             └─21630 /usr/sbin/httpd -DFOREGROUND

Jul 15 13:57:36 stapp01 systemd[1]: Starting The Apache HTTP Server...
Jul 15 13:57:36 stapp01 httpd[21620]: AH00558: httpd: Could not reliably determine the server's fully qualified >
Jul 15 13:57:36 stapp01 httpd[21620]: Server configured, listening on: port 5003
Jul 15 13:57:36 stapp01 systemd[1]: Started The Apache HTTP Server.
[tony@stapp01 ~]$ curl localhost:5003
Welcome to xFusionCorp Industries!

Repeat that same check on all of the app servers. On my end all look good, so let’s move on to setting up the actual load balancing piece of Nginx. Let’s start by logging into stlb01 and checking what’s already installed:

[loki@stlb01 ~]$ dnf list --installed | grep nginx
nginx.x86_64                                   2:1.20.1-30.el9                  @appstream    
nginx-core.x86_64                              2:1.20.1-30.el9                  @appstream    
nginx-filesystem.noarch                        2:1.20.1-30.el9                  @appstream
[loki@stlb01 ~]$ sudo systemctl status nginx.service
○ nginx.service - The nginx HTTP and reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; disabled; preset: disabled)
     Active: inactive (dead)

All the components are there, but the service isn’t running. Let’s enable it:

[loki@stlb01 ~]$ sudo systemctl enable --now nginx.service
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.
[loki@stlb01 ~]$ sudo systemctl status nginx.service
● nginx.service - The nginx HTTP and reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: disabled)
     Active: active (running) since Wed 2026-07-15 14:01:44 UTC; 16s ago
    Process: 23393 ExecStartPre=/usr/bin/rm -f /run/nginx.pid (code=exited, status=0/SUCCESS)
    Process: 23394 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=0/SUCCESS)
    Process: 23401 ExecStart=/usr/sbin/nginx (code=exited, status=0/SUCCESS)
   Main PID: 23408 (nginx)
      Tasks: 17 (limit: 404712)
     Memory: 16.1M
        CPU: 50ms
     CGroup: /system.slice/nginx.service

[...]

Jul 15 14:01:44 stlb01 systemd[1]: Starting The nginx HTTP and reverse proxy server...
Jul 15 14:01:44 stlb01 nginx[23394]: nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
Jul 15 14:01:44 stlb01 nginx[23394]: nginx: configuration file /etc/nginx/nginx.conf test is successful
Jul 15 14:01:44 stlb01 systemd[1]: Started The nginx HTTP and reverse proxy server.

Note the line nginx: the configuration file /etc/nginx/nginx.conf syntax is ok in the output. If we make a config change and it goes bad, we’ll know instantly by that same output - it’ll mention the config is bad and show what the issue is usually. On that note, I like to keep a copy of any configurations I’m changing in case I mangle it beyond quick repairs - we can do that by copying the file to any random extension, but I like using .bak to help me visually see what’s a backup or not.

[loki@stlb01 ~]$ sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak

With that, we can finally start editing the configuration file. It’s handy to have a copy of the Nginx Admin Guide for Load Balancing open on the side in a second window for quick reference.

The changes we’ll make our straightforward - first, we’ll define a group of servers and ports that our Nginx should forward traffic to; second, we need to define what route is forwarded, and to which server group it goes. We can achieve the first by adding an upstream <groupName> block at the first level of the http block, like so:

http {
  upstream webservers {
      server stapp01:5003;
      server stapp02:5003;
      server stapp03:5003;
  }
  [...]
}

Next, we have to tell it where to route - in the server block, we’ll add a location block with the proxy_pass directive to tell it to use the webservers group we created when someone requests http://stlb01:80/:

  server {
    [...]
      location / {
          proxy_pass http://webservers
      }
    [...]
  }

Once we’ve saved the file, we restart the servces and make sure everything comes up correctly:

[loki@stlb01 ~]$ sudo systemctl restart nginx.service
[loki@stlb01 ~]$ sudo systemctl status nginx.service
● nginx.service - The nginx HTTP and reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: disabled)
     Active: active (running) since Wed 2026-07-15 14:15:31 UTC; 4s ago
    Process: 27637 ExecStartPre=/usr/bin/rm -f /run/nginx.pid (code=exited, status=0/SUCCESS)
    Process: 27638 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=0/SUCCESS)
    Process: 27645 ExecStart=/usr/sbin/nginx (code=exited, status=0/SUCCESS)
   Main PID: 27652 (nginx)
      Tasks: 17 (limit: 404712)
     Memory: 16.2M
        CPU: 48ms
     CGroup: /system.slice/nginx.service

[...]

Jul 15 14:15:31 stlb01 systemd[1]: Starting The nginx HTTP and reverse proxy server...
Jul 15 14:15:31 stlb01 nginx[27638]: nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
Jul 15 14:15:31 stlb01 nginx[27638]: nginx: configuration file /etc/nginx/nginx.conf test is successful
Jul 15 14:15:31 stlb01 systemd[1]: Started The nginx HTTP and reverse proxy server.

And finally, we just need to test connectivity from the jump box!

[thor@jump-host ~]$ curl http://stlb01:80
Welcome to xFusionCorp Industries!

Day 17: Install and Configure PostgreSQL
#

Problem Prompt

The Nautilus application development team has shared that they are planning to deploy one newly developed application on Nautilus infra in Stratos DC. The application uses PostgreSQL database, so as a pre-requisite we need to set up PostgreSQL database server as per requirements shared below:

PostgreSQL database server is already installed on the Nautilus database server.
A. Create a database user kodekloud_pop and set its password to ksH85UJjhb.
B. Create a database kodekloud_db1 and grant full permissions to user kodekloud_pop on this database.

Note: Please do not try to restart PostgreSQL server service.

This one’s much, much quicker than the last few - all we have to do is some PostgreSQL management. To start, let’s connect to stdb01 as peter and verify that PostgreSQL is actually installed:

[peter@stdb01 ~]$ dnf list --installed | grep postg
postgresql.x86_64                              13.23-3.el9                      @appstream    
postgresql-contrib.x86_64                      13.23-3.el9                      @appstream    
postgresql-private-libs.x86_64                 13.23-3.el9                      @appstream    
postgresql-server.x86_64                       13.23-3.el9                      @appstream

And now that we’ve done that, they request that we give a user full access to a database. To do that, we need to do a few intermediate steps:

  • Create a the kodekloud_db1 database
  • Create the kodekloud_pop user
  • Grant all of the available privileges on kodekloud_db1 to kodekloud_pop

This is easily done with a few quick commands:

[peter@stdb01 ~]$ psql -U postgres
psql (13.23)
Type "help" for help.

postgres=# CREATE DATABASE kodekloud_db1;
CREATE DATABASE
postgres=# CREATE USER kodekloud_pop WITH PASSWORD 'ksH85UJjhb';
CREATE ROLE
kodekloud_db1=# GRANT ALL PRIVILEGES ON DATABASE kodekloud_db1 TO kodekloud_pop;
GRANT

Done!

Day 18: Configure LAMP Server (OLD)
#

Problem Prompt

xFusionCorp Industries is planning to host a WordPress website on their infra in Stratos Datacenter. They have already done infrastructure configuration—for example, on the storage server they already have a shared directory /vaw/www/html that is mounted on each app host under /var/www/html directory. Please perform the following steps to accomplish the task:

A. Install httpd, php and its dependencies on all app hosts.
B. Apache should serve on port 5004 within the apps.
C. Install/Configure MariaDB server on DB Server.
D. Create a database named kodekloud_db5 and create a database user named kodekloud_cap identified as password B4zNgHA7Ya. Further make sure this newly created user is able to perform all operation on the database you created.
E. Finally you should be able to access the website on LBR link, by clicking on the App button on the top bar. You should see a message like App is able to connect to the database using user kodekloud_cap

Problem Change

It looks like at some point after I wrote this, KodeKloud retired the old problem and swapped it out for a new one. I’m leaving this in here so that you can see it - it’s got some fun details scattered inside. It’s not structured the same as my old ones and may not be fully complete, but I can’t go back and re-do or check it now that they’ve replaced the problem set!

Okay, this shouldn’t be too bad. Let’s take stock of what’s already on the servers and see what we need to get this running. I’m starting on stapp01 and will repeat the steps where needed on the other app servers. First up, what packages are installed? We can check the installed packages with dnf and grep to filter:

[tony@stapp01 ~]$ dnf list --installed | grep httpd
[tony@stapp01 ~]$ dnf list --installed | grep php

It looks like we’ll need to install both the server and the language. To do that, we run the following:

[tony@stapp01 ~]$ sudo dnf install -y httpd php

Next, let’s check out the structure of the app we’re deploying:

[tony@stapp01 ~]$ ls -la /var/www/html
total 12
drwxr-xr-x 2 root root 4096 Jan  1 03:38 .
drwxr-xr-x 3 root root 4096 Jan  1 03:37 ..
-rw-r--r-- 1 root root  266 Jan  1 03:37 index.php

Just a single page. Here are the contents of index.php:

<?php

$dbname = 'kodekloud_db5';
$dbuser = 'kodekloud_cap';
$dbpass = 'B4zNgHA7Ya';
$dbhost = 'stdb01';

$link = mysqli_connect($dbhost, $dbuser, $dbpass) or die("Unable to Connect to '$dbhost'");
echo "App is able to connect to the database using user $dbuser";
?>

Trying to run the file with sudo php /var/www/html/index.php produces the error Fatal error: Call to undefined function mysqli_connect() - meaning we don’t have the MySQL library installed. After doing some searching, the way to rectify this is to install the php-mysqli package (php-mysqlnd on Fedora’s repositories) using either yum or dnf.

[tony@stapp01 ~]$ sudo dnf install php-mysqlnd

Next up - we need to set Apache to bind to the correct port so that way the load balancer can reach the server. The default config for httpd on Fedora lives at /etc/httpd/conf/httpd.conf. You can edit it using Vi, Vim, Nano, or any other text or stream editors you know - I’m using sed for repeatability and consistency across the other servers.

[tony@stapp01 ~]$ sudo sed -i 's/Listen 80/Listen 5004/g' /etc/httpd/conf/httpd.conf
[tony@stapp01 ~]$ sudo systemctl restart httpd

After restarting the service, you can verify the changes are in effect by running systemctl status httpd or checking the listening ports by using sudo ss -antp | grep 5004 and making sure everything appears correct. With that, we can move over to the database portion of the setup.

The database first needs installation - confirm that MariaDB isn’t already present using dnf, and then run the following if it’s missing:

[tony@stapp01 ~]$ sudo dnf install mariadb-server
[tony@stapp01 ~]$ sudo systemctl enable --now mariadb

Once it’s installed and the service is available, we can start configuring the defaults. Run through the mysql_secure_installation script, choosing options as you go along - most of the defaults are fine - and then edit the bind-address in our MariaDB configuration to listen on 0.0.0.0 to accept remote connections.

[tony@stapp01 ~]$ sudo mysql_secure_installation
[tony@stapp01 ~]$ # We'll uncomment the bind-address line with vi
[tony@stapp01 ~]$ sudo vi /etc/my.cnf.d/mariadb-server.cnf 

Finally, we need to set up the database user so that our apps can have access to run queries, update tables, etc. The demo site doesn’t do any of that, but will just check if the connection is live in the mysqli_connect call from the code at the beginning of this section. I chose to limit the IP range to 172.16.X, since that’s what range the containers were running with for both IP addresses:

[tony@stapp01 ~]$ sudo mysql -u root -p
Enter password:
MariaDB [(none)]> CREATE DATABASE kodekloud_db5;
MariaDB [(none)]> CREATE USER 'kodekloud_cap'@'172.16.%' IDENTIFIED BY 'B4zNgHA7Ya';
MariaDB [(none)]> GRANT ALL PRIVILEGES ON kodekloud_db5.* TO 'kodekloud_cap'@'172.16.%';
MariaDB [(none)]> FLUSH PRIVILEGES;

Day 18: Install and Configure DB Server
#

Problem Prompt

We need to setup a database server on Nautilus DB Server in Stratos Datacenter. Please perform the below given steps on DB Server:

A. Install/Configure MariaDB server.
B. Create a database named kodekloud_db3.
C. Create a user called kodekloud_top and set its password to YchZHRcLkL.
D. Grant full permissions to user kodekloud_top on database kodekloud_db3.

This one is basically identical to Day 17; the only difference is that we need to install MariaDB first. As always, check with dnf to see if the mariadb and mariadb-server packages are installed - spoiler alert, they’re not - and then install them with dnf:

[peter@stdb01 ~]$ sudo dnf install mariadb mariadb-server
Last metadata expiration check: 0:03:53 ago on Wed Jul 15 22:02:51 2026.
Dependencies resolved.
=======================================================================================================================================
 Package                                    Architecture           Version                             Repository                 Size
=======================================================================================================================================
Installing:
 mariadb                                    x86_64                 3:10.5.29-4.el9                     appstream                 1.7 M
 mariadb-server                             x86_64                 3:10.5.29-4.el9                     appstream                 9.7 M
Installing dependencies:
 iproute                                    x86_64                 6.17.0-2.el9                        baseos                    866 k
 libaio                                     x86_64                 0.3.111-13.el9                      baseos                     24 k

                        [...]

Installed:
  iproute-6.17.0-2.el9.x86_64                                    libaio-0.3.111-13.el9.x86_64                                         
  libbpf-2:1.5.0-3.el9.x86_64                                    mariadb-3:10.5.29-4.el9.x86_64                                       
  mariadb-backup-3:10.5.29-4.el9.x86_64                          mariadb-common-3:10.5.29-4.el9.x86_64                                
  mariadb-connector-c-3.2.6-1.el9.x86_64                         mariadb-connector-c-config-3.2.6-1.el9.noarch                        
  mariadb-errmsg-3:10.5.29-4.el9.x86_64                          mariadb-gssapi-server-3:10.5.29-4.el9.x86_64                         
  mariadb-server-3:10.5.29-4.el9.x86_64                          mariadb-server-utils-3:10.5.29-4.el9.x86_64                          
  perl-DBD-MariaDB-1.21-17.el9.x86_64                            perl-DBI-1.643-9.el9.x86_64                                          
  perl-File-Copy-2.34-483.el9.noarch                             perl-Math-BigInt-1:1.9998.18-460.el9.noarch                          
  perl-Math-Complex-1.59-483.el9.noarch                          perl-Sys-Hostname-1.23-483.el9.x86_64                                
  psmisc-23.4-3.el9.x86_64                                      

Complete!

After installation completes, we need to enable the service and then run the mariadb-secure-installation script. For the most part, you should be able to stick with the default options it presents:

[peter@stdb01 ~]$ sudo systemctl status mariadb.service
○ mariadb.service - MariaDB 10.5 database server
     Loaded: loaded (/usr/lib/systemd/system/mariadb.service; disabled; preset: disabled)
     Active: inactive (dead)
       Docs: man:mariadbd(8)
             https://mariadb.com/kb/en/library/systemd/
[peter@stdb01 ~]$ sudo systemctl enable --now mariadb.service
Created symlink /etc/systemd/system/mysql.service → /usr/lib/systemd/system/mariadb.service.
Created symlink /etc/systemd/system/mysqld.service → /usr/lib/systemd/system/mariadb.service.
Created symlink /etc/systemd/system/multi-user.target.wants/mariadb.service → /usr/lib/systemd/system/mariadb.service.
[peter@stdb01 ~]$ sudo mariadb-secure-installation
                      [...]

And with that, we have a MariaDB server up and running! Now we just need to authenticate, create a database and user, and grant privileges like before. Just like with PostgreSQL, it’s relatively straightforward with the mysql CLI:

[peter@stdb01 ~]$ sudo mysql -u root
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 15
Server version: 10.5.29-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> CREATE DATABASE kodekloud_db3;
Query OK, 1 row affected (0.000 sec)

MariaDB [(none)]> CREATE USER kodekloud_top IDENTIFIED BY 'YchZHRcLkL';
Query OK, 0 rows affected (0.000 sec)

MariaDB [kodekloud_db3]> GRANT ALL PRIVILEGES ON kodekloud_db3.* TO kodekloud_top;
Query OK, 0 rows affected (0.000 sec)

And as before, that’s it - we’ve given the user all the privileges requested. On to Day 19!

Day 19: Install and Configure Web Application
#

Problem Prompt

xFusionCorp Industries is planning to host two static websites on their infra in Stratos Datacenter. The development of these websites is still in-progress, but we want to get the servers ready. Please perform the following steps to accomplish the task:

A. Install httpd package and dependencies on app server 2.
B. Apache should serve on port 3001.
C. There are two website’s backups /home/thor/ecommerce and /home/thor/cluster on jump_host. Set them up on Apache in a way that ecommerce should work on the link http://localhost:3001/ecommerce/ and cluster should work on link http://localhost:3001/cluster/ on the mentioned app server.
D. Once configured you should be able to access the website using curl command on the respective app server, i.e curl http://localhost:3001/ecommerce/ and curl http://localhost:3001/cluster/

Checking installed packages:

[steve@stapp02 ~]$ dnf list --installed | grep httpd
[steve@stapp02 ~]$ dnf list --installed | grep apache
[steve@stapp02 ~]$ sudo dnf install -y httpd
CentOS Stream 9 - BaseOS                                                                               6.1 MB/s | 9.0 MB     00:01    
CentOS Stream 9 - AppStream                                                                             14 MB/s |  28 MB     00:02    
CentOS Stream 9 - Extras packages                                                                       26 kB/s |  21 kB     00:00    
Extra Packages for Enterprise Linux 9 - x86_64                                                          54 MB/s |  21 MB     00:00    
Extra Packages for Enterprise Linux 9 openh264 (From Cisco) - x86_64                                   4.5 kB/s | 2.5 kB     00:00    
Extra Packages for Enterprise Linux 9 - Next - x86_64                                                  1.1 MB/s | 265 kB     00:00    
Dependencies resolved.
=======================================================================================================================================
 Package                               Architecture              Version                            Repository                    Size
=======================================================================================================================================
Installing:
 httpd                                 x86_64                    2.4.62-14.el9                      appstream                     47 k
Installing dependencies:
 apr                                   x86_64                    1.7.0-12.el9                       appstream                    123 k
 apr-util                              x86_64                    1.6.1-23.el9                       appstream                     95 k
 apr-util-bdb                          x86_64                    1.6.1-23.el9                       appstream                     13 k
 centos-logos-httpd                    noarch                    90.9-1.el9                         appstream                    1.5 M
 httpd-core                            x86_64                    2.4.62-14.el9                      appstream                    1.5 M

 [...]

 Installed:
  apr-1.7.0-12.el9.x86_64                      apr-util-1.6.1-23.el9.x86_64                  apr-util-bdb-1.6.1-23.el9.x86_64        
  apr-util-openssl-1.6.1-23.el9.x86_64         centos-logos-httpd-90.9-1.el9.noarch          httpd-2.4.62-14.el9.x86_64              
  httpd-core-2.4.62-14.el9.x86_64              httpd-filesystem-2.4.62-14.el9.noarch         httpd-tools-2.4.62-14.el9.x86_64        
  mailcap-2.1.49-5.el9.noarch                  mod_http2-2.0.26-6.el9.x86_64                 mod_lua-2.4.62-14.el9.x86_64            

Complete!

Now that httpd is installed, we need to enable the service to start up the web server. We’ve done this plenty of times in this problem set, so this shouldn’t look too unfamiliar if you’ve read this far.

[steve@stapp02 ~]$ sudo systemctl status httpd.service
○ httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; preset: disabled)
     Active: inactive (dead)
       Docs: man:httpd.service(8)
[steve@stapp02 ~]$ sudo systemctl enable --now httpd.service
Created symlink /etc/systemd/system/multi-user.target.wants/httpd.service → /usr/lib/systemd/system/httpd.service.

Next up - we need to change the listening port from port 80 to port 3001. You can do this in any text or stream editor - sed is easy enough - and then restart the service once you’ve saved the configuration file. As always, if you’re using sed or another in-place editor, make sure to test out your changes with a dry run before you find out the hard way that it didn’t edit what you thought it did.

[steve@stapp02 ~]$ sudo sed -i "s/Listen 80/Listen 3001/g" /etc/httpd/conf/httpd.conf
[steve@stapp02 ~]$ sudo systemctl restart httpd

Now we need to set up the different sites - either log out of our SSH session as steve, or pop open a new terminal in the KK Engineer task pane. Once you’re in thor’s terminal, scp the files over to stapp02 so we can use them:

thor@jump-host ~$ ls -l
total 8
drwxr-xr-x 2 thor thor 4096 Jul 15 22:41 cluster
drwxr-xr-x 2 thor thor 4096 Jul 15 22:41 ecommerce
thor@jump-host ~$ scp -r ecommerce steve@stapp02:/home/steve/ecommerce
steve@stapp02's password: 
index.html                                                                                           100%  122   238.0KB/s   00:00    
thor@jump-host ~$ scp -r cluster steve@stapp02:/home/steve/cluster
steve@stapp02's password: 
index.html                                                                                           100%  120   191.9KB/s   00:00    

The files have been transferred, so hop back into steve’s terminal. We copy the files from the home directory to the DocumentRoot at /var/www/html (note the -r flag; it’s what’s creating the ecommerce and cluster directories in the destination folder):

[steve@stapp02 ~]$ sudo cp -r ecommerce/ /var/www/html/
[steve@stapp02 ~]$ sudo cp -r cluster /var/www/html/

Now all that’s left is to test each of the paths with curl. We’ve done this quite a bit so far, so this should be all-too-familiar territory.

[steve@stapp02 ~]$ curl -I localhost:3001/ecommerce/
HTTP/1.1 200 OK
Date: Wed, 15 Jul 2026 22:51:45 GMT
Server: Apache/2.4.62 (CentOS Stream)
Last-Modified: Wed, 15 Jul 2026 22:50:41 GMT
ETag: "7a-656ae272ad1e2"
Accept-Ranges: bytes
Content-Length: 122
Content-Type: text/html; charset=UTF-8

[steve@stapp02 ~]$ curl -I localhost:3001/cluster/
HTTP/1.1 200 OK
Date: Wed, 15 Jul 2026 22:51:47 GMT
Server: Apache/2.4.62 (CentOS Stream)
Last-Modified: Wed, 15 Jul 2026 22:50:51 GMT
ETag: "78-656ae27c45e0c"
Accept-Ranges: bytes
Content-Length: 120
Content-Type: text/html; charset=UTF-8

Day 20: Configure Nginx + PHP-FPM Using Unix Sock
#

Problem Prompt

The Nautilus application development team is planning to launch a new PHP-based application, which they want to deploy on Nautilus infra in Stratos DC. The development team had a meeting with the production support team and they have shared some requirements regarding the infrastructure. Below are the requirements they shared:

A. Install nginx on app server 3 , configure it to use port 8096 and its document root should be /var/www/html.
B. Install php-fpm version 8.2 on app server 3, it must use the unix socket /var/run/php-fpm/default.sock (create the parent directories if don’t exist).
C. Configure php-fpm and nginx to work together.
D. Once configured correctly, you can test the website using curl http://stapp03:8096/index.php command from jump host.

NOTE: We have copied two files, index.php and info.php, under /var/www/html as part of the PHP-based application setup. Please do not modify these files.

This one will be a fair bit more involved than the previous entries in the series. First, we log in to stapp03 and check what’s installed; when nginx returns that it’s not installed, we grab it and its dependencies with dnf:

[banner@stapp03 ~]$ dnf list --installed | grep nginx
[banner@stapp03 ~]$ sudo dnf install -y nginx
[...]
Dependencies resolved.
=======================================================================================================================================
 Package                               Architecture              Version                            Repository                    Size
=======================================================================================================================================
Installing:
 nginx                                 x86_64                    2:1.20.1-30.el9                    appstream                     37 k

[...]

Installed:
  centos-logos-httpd-90.9-1.el9.noarch          logrotate-3.18.0-12.el9.x86_64                   nginx-2:1.20.1-30.el9.x86_64         
  nginx-core-2:1.20.1-30.el9.x86_64             nginx-filesystem-2:1.20.1-30.el9.noarch         

Complete!

As with the times before, we immediately enable the service after install.

[banner@stapp03 ~]$ sudo systemctl status nginx.service
○ nginx.service - The nginx HTTP and reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; disabled; preset: disabled)
     Active: inactive (dead)
[banner@stapp03 ~]$ sudo systemctl enable --now nginx.service
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.

This should look familiar - we’re going to edit /etc/nginx/nginx.conf and make sure that it’s listening on the correct port and that it has the correct document root.

  server {
    listen       8096;
    listen       [::]:8096;
    server_name  _;
    root         /var/www/html;
    [...]
  }

Now we need to get PHP and PHP-FPM installed. This part isn’t too bad - I followed the instructions from the PHP website almost identically. The only bit that I skipped was the subscription-manager piece - since we don’t have a username and password, we wouldn’t be able to register any subscriptions. Either way, it works without that command flawlessly:

[banner@stapp03 ~]$ sudo dnf install -y dnf-plugins-core
[banner@stapp03 ~]$ sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-$(rpm -E %rhel).noarch.rpm
[banner@stapp03 ~]$ sudo dnf install -y https://rpms.remirepo.net/enterprise/remi-release-$(rpm -E %rhel).rpm
[banner@stapp03 ~]$ sudo dnf module reset php -y
[banner@stapp03 ~]$ sudo dnf module enable php:remi-8.2 -y
[banner@stapp03 ~]$ sudo dnf install -y php

Now we need to get all of the pieces of the site talking to each other. To do that, we pass the PHP-FPM socket we’re setting up in /etc/php-fpm.d/www.conf into a drop-in configuration file for Nginx at /etc/nginx/conf.d/php-fpm.conf.

Notice that as I’m making the changes with sed, I’ve changed the delimiter from / to # - I only do that to make it easier and cleaner to get the correct edits.

[banner@stapp03 ~]$ sudo mkdir -p /var/run/php-fpm/
[banner@stapp03 ~]$ sudo sed -i "s#listen = /run/php-fpm/www.sock#listen = /var/run/php-fpm/default.sock#g" /etc/php-fpm.d/www.conf
[banner@stapp03 ~]$ sudo sed -i "s#/run/php-fpm/www.sock#/var/run/php-fpm/default.sock#g" /etc/nginx/conf.d/php-fpm.conf

Here’s what the PHP-FPM config file looks like after our changes:

[steve@stapp02 ~]$ grep -E -B2 "listen = .*" /etc/php-fpm.d/www.conf
;   '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
listen = /var/run/php-fpm/default.sock

And likewise, the Nginx configuration drop-in file for PHP-FPM:

# PHP-FPM FastCGI server
# network or unix domain socket configuration

upstream php-fpm {
        server unix:/var/run/php-fpm/default.sock;
}

Now we need to test our configuration. We can quickly do this with nginx -t; once it passes the checks, we restart both the nginx and php-fpm services to make sure they pick up the changes.

[banner@stapp03 ~]$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[banner@stapp03 ~]$ sudo systemctl restart nginx.service
[banner@stapp03 ~]$ sudo systemctl restart php-fpm.service
[banner@stapp03 ~]$ sudo systemctl status nginx.service
● nginx.service - The nginx HTTP and reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: disabled)
    Drop-In: /etc/systemd/system/nginx.service.d
             └─php-fpm.conf
     Active: active (running) since Wed 2026-07-15 23:31:19 UTC; 22s ago
    Process: 45242 ExecStartPre=/usr/bin/rm -f /run/nginx.pid (code=exited, status=0/SUCCESS)
    Process: 45243 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=0/SUCCESS)
    Process: 45250 ExecStart=/usr/sbin/nginx (code=exited, status=0/SUCCESS)
   Main PID: 45257 (nginx)
      Tasks: 17 (limit: 404712)
     Memory: 16.1M
        CPU: 48ms
     CGroup: /system.slice/nginx.service

[...]

[banner@stapp03 ~]$ sudo systemctl status php-fpm.service
● php-fpm.service - The PHP FastCGI Process Manager
     Loaded: loaded (/usr/lib/systemd/system/php-fpm.service; disabled; preset: disabled)
     Active: active (running) since Wed 2026-07-15 23:31:22 UTC; 45s ago
   Main PID: 45489 (php-fpm)
     Status: "Processes active: 0, idle: 5, Requests: 0, slow: 0, Traffic: 0.00req/sec"
      Tasks: 6 (limit: 404712)
     Memory: 10.4M
        CPU: 46ms
     CGroup: /system.slice/php-fpm.service

[...]

Jul 15 23:31:22 stapp03 systemd[1]: Starting The PHP FastCGI Process Manager...
Jul 15 23:31:22 stapp03 systemd[1]: Started The PHP FastCGI Process Manager.

Finally, the last step - we need to check if we can properly reach the page. In a terminal on the jump box, run a curl against the page and see if it’s live:

[thor@jump-host ~]$ curl -I http://stapp03:8096/index.php
HTTP/1.1 200 OK
Server: nginx/1.20.1
Date: Wed, 15 Jul 2026 23:32:45 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/8.2.32

[thor@jump-host ~]$ curl http://stapp03:8096/index.php
Welcome to xFusionCorp Industries!

And with that - we’re done with days 11 through 20 out of 100! I hope you enjoyed the writeups, and we’ll pick back up with Days 21 through 30 soon!

KodeKloud's 100 Days of DevOps - This article is part of a series.
Part 2: This Article