Cario: Time for a Timer
Summary#
Another SadServers scenario in the books! This one was run because not everyone
within Enterprise environments touch the host firwalls for servers. I configure
the firwall on servers. But, not with iptables. This was a good challenge
thaat could potentially help people see why it’s important to not make mistakes
configuring your host firewall rules. Apart from that. I enjoyed reviewing
the configurations to see how things worked under the hood as well.
Senario#
Scenario: “Cairo”: Time for a Timer
Level: Easy
Type: Fix
Access: Email
Description:
A critical health check script at /opt/scripts/health.sh is supposed to run every 10 seconds. This check is triggered by a systemd timer.
The script’s job is to check the local Nginx server and write its status (e.g., “STATUS: OK”) to the log file at /var/log/health.log.
The log file is not being updated, and it appears the health check is failing.
Find out why the health check system is broken and fix it. The check will pass once the /var/log/health.log file is being correctly updated by the timer with a STATUS: OK message.
Root (sudo) Access: True
Test: The /opt/scripts/health.sh script writes STATUS: OK to /var/log/health.log every 10 seconds.
Solution#
For the solution here I need to get the health check script to work as expected.
This script is located in /opt/scripts/ and is named health.sh. Reading
through the script it looks like it tests the availability of the default Nginx
web root and returns 0 on success and 1 on failure. Simple enough.
#!/bin/bash
# This script logs status and exits with 0 on success, 1 on failure
if curl -s --max-time 2 http://localhost | grep -q "Welcome to nginx"; then
echo "$(date): STATUS: OK" >> /var/log/health.log
exit 0
else
echo "$(date): STATUS: FAILED" >> /var/log/health.log
exit 1
fi
I checked the services to find out they weren’t running. But, once it’s fixed. I’ll get them running.
Based on the health.service file. It will run the health.sh script once and
let it do its thing. Nothing jumped out at me as misconfigured.
[Unit]
Description=Health Check Service
[Service]
Type=oneshot
ExecStart=/opt/scripts/health.sh
[Install]
WantedBy=multi-user.target
The health.timer will run the health.service script every 10 seconds. For
this nothing seemed to be misconfigured either. So, I moved on and noted that I
would need to start them later.
[Unit]
Description=Run health check every 10 seconds
[Timer]
OnBootSec=10
OnUnitActiveSec=10
Unit=health.service
[Install]
WantedBy=timers.target
I did want to see the Nginx port that Nginx was listening on. But, I also wanted
to check to make sure the configuration for Nginx was good. Given that no errors
outputted for sudo nginx -t. It must be configured correctly to at least run.
I double-checked the port using two commands lsof and ss. Using lsof I
checked for processes listening on port 80.
sudo lsof -i TCP:80 -s TCP:LISTEN
Here is the output for this. Based on this there are four unique processes listenting on this port.
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 849 root 5u IPv4 7212 0t0 TCP *:http (LISTEN)
nginx 849 root 6u IPv6 7213 0t0 TCP *:http (LISTEN)
nginx 851 www-data 5u IPv4 7212 0t0 TCP *:http (LISTEN)
nginx 851 www-data 6u IPv6 7213 0t0 TCP *:http (LISTEN)
nginx 852 www-data 5u IPv4 7212 0t0 TCP *:http (LISTEN)
nginx 852 www-data 6u IPv6 7213 0t0 TCP *:http (LISTEN)
Next I checked it with the ss command. This will check for processes that are
listening on the server.
sudo ss -ltnp
Based on the output Nginx is listening on port 80 and there are no other web servers already listening on that port.
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 0.0.0.0:5355 0.0.0.0:* users:(("systemd-resolve",pid=315,fd=12))
LISTEN 0 4096 127.0.0.54:53 0.0.0.0:* users:(("systemd-resolve",pid=315,fd=21))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=852,fd=5),("nginx",pid=851,fd=5),("nginx",pid=849,fd=5))
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=315,fd=19))
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=820,fd=6))
LISTEN 0 4096 [::]:5355 [::]:* users:(("systemd-resolve",pid=315,fd=14))
LISTEN 0 4096 *:8080 *:* users:(("gotty",pid=771,fd=6))
LISTEN 0 4096 *:6767 *:* users:(("sadagent",pid=772,fd=7))
LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=852,fd=6),("nginx",pid=851,fd=6),("nginx",pid=849,fd=6))
LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=820,fd=7))
Given that Nginx seems to be working as expected through checking the configuration and confirming the listen port is correct for the health check script. It’s time to look at host firewall rules to see if there are any bad entries.
This host seems to be using iptables as the firewall so I run the command with
the -L option to list the iptables.
sudo iptables -L
Nothing seems to jump out with the exception of the OUTPUT policies. One of
which drops access to port 80 from anywhere to destination localhost. There is
also a comment that states “hidden problem”. So, the best bet. Is we’re looking
at a firewall misconfiguration here.
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy DROP)
target prot opt source destination
DOCKER-USER all -- anywhere anywhere
DOCKER-FORWARD all -- anywhere anywhere
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DROP tcp -- anywhere localhost tcp dpt:http /* The hidden problem (IPv4) */
Chain DOCKER (1 references)
target prot opt source destination
DROP all -- anywhere anywhere
Chain DOCKER-BRIDGE (1 references)
target prot opt source destination
DOCKER all -- anywhere anywhere
Chain DOCKER-CT (1 references)
target prot opt source destination
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
Chain DOCKER-FORWARD (1 references)
target prot opt source destination
DOCKER-CT all -- anywhere anywhere
DOCKER-ISOLATION-STAGE-1 all -- anywhere anywhere
DOCKER-BRIDGE all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target prot opt source destination
DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
Chain DOCKER-ISOLATION-STAGE-2 (1 references)
target prot opt source destination
DROP all -- anywhere anywhere
Chain DOCKER-USER (1 references)
target prot opt source destination
There may be better ways to delete policies. But, this is the method I chose. I
deleted by line number within the OUTPUT chain. Before doing this though. I
needed the line-numbers for this.
The following command was used to list the available line-numbers within the
OUTPUT chain.
sudo iptables -nL OUTPUT --line-numbers
Based on the output below. The policy number I’m deleting is number 1.
Chain OUTPUT (policy ACCEPT)
num target prot opt source destination
1 DROP tcp -- 0.0.0.0/0 127.0.0.1 tcp dpt:80 /* The hidden problem (IPv4) */
I use the following command to delete policy number 1.
sudo iptables -D OUTPUT 1
Once that is deleted. I list to see if it’s still around using the same command as last time.
sudo iptables -nL OUTPUT --line-numbers
Based on the output. The policy is no longer listed in the OUTPUT chain.
Chain OUTPUT (policy ACCEPT)
num target prot opt source destination
Next step is to confirm connectivity to the site. I did that using the curl command. Which is expected by the health check script.
curl localhost:80
Looks like I’m golden on that one! It outputted the full webpage with the “Welcome to nginx!” title. So there should be no issues with the grep command the output is piped to.
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
I ran the health check script using bash. Just to see if it would log an OK.
sudo bash /opt/scripts/health.sh
Read through the /var/log/health.log file and it looks like it posted its
first OK message.
Wed Mar 18 17:42:47 UTC 2026: STATUS: OK
Now it’s time to enable and start the health.service and health.timer
services so it will check the web service every 10 seconds.
sudo systemctl enable --now health.service health.timer
This will output that it created a symlink for each and if there are no errors
during startup. There will be no output. In this instance there weren’t any
errors. So things are looking promising.
Created symlink '/etc/systemd/system/multi-user.target.wants/health.service' → '/etc/systemd/system/health.service'.
Created symlink '/etc/systemd/system/timers.target.wants/health.timer' → '/etc/systemd/system/health.timer'.
I used the tail command on the /var/log/health.log file with the -f flag to
keep the file open and allow me to view updates to the file.
tail -f /var/log/health.log
Based on the otuput below. It updates ever 10ish or so seconds. All of which provide an OK status. So, I am definately golden on this challenge.
Wed Mar 18 17:42:47 UTC 2026: STATUS: OK
Wed Mar 18 17:43:03 UTC 2026: STATUS: OK
Wed Mar 18 17:47:12 UTC 2026: STATUS: OK
Wed Mar 18 17:47:24 UTC 2026: STATUS: OK
Wed Mar 18 17:47:34 UTC 2026: STATUS: OK
Wed Mar 18 17:47:58 UTC 2026: STATUS: OK
Wed Mar 18 17:48:09 UTC 2026: STATUS: OK
Wed Mar 18 17:48:24 UTC 2026: STATUS: OK
Wed Mar 18 17:48:25 UTC 2026: STATUS: OK
Wed Mar 18 17:48:54 UTC 2026: STATUS: OK
Conclusion#
I clicked the “Check Solution” button and there were party streamers and flashing lights. Another challenge down. Time to move to the next one(s).
This challenge taught me how to test and poke around if anything. The solution to this challenge is to correct a misconfigured firewall rule on the server. Once that’s corrected. I started the services and let them do their thing.