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:
- Download and Install a Java SE Runtime Environment (JRE)
- Download and Install Apache Tomcat
- Configure Environment Variables
- 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.targetOnce 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-8A 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 destinationNow 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:
- Install iptables and all its dependencies on each app host.
- Block incoming port 8084 on all apps for everyone except for LBR host.
- 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 @epelSo 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 destinationAnd 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
REJECTrule gets added - Next, we append a reject all rule to the bottom of the ruleset
- Finally, we add an allow for
stlb01to 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:
- Install and configure nginx on App Server 1.
- 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.
- Create an index.html file with content Welcome! under Nginx document root.
- 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 theindex.htmlfile 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_db1database - Create the
kodekloud_popuser - Grant all of the available privileges on
kodekloud_db1tokodekloud_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 httpdNow 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-8Day 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!
