Homelab Academy / Modules
Guided practice + break/fix + lab gate.
Module: Network Operations
Activity type: Lab (Practice)
Lab risk: low
Objective: Apply a layered troubleshooting workflow from local state through application response
Diagnose a Wrong-Port Failure Against a Local HTTP Service
Do not edit operating-system network configuration.
Do not install packages during the exercise.
Create or modify persistent files only under /opt/lab-classroom/class26/.
Use port 18080 for the controlled service and port 18081 for the deliberate failure.
Run each observation before making any corrective change.
1
Create the isolated workspace
LAB=/opt/lab-classroom/class26
sudo install -d -m 0755 "$LAB/site"
sudo chown -R "$(id -u):$(id -g)" "$LAB"
printf 'class26-ok\n' > "$LAB/site/health.txt"
Confirm that the health file exists only inside the assigned lab directory.
2
Start a loopback-only HTTP service
LAB=/opt/lab-classroom/class26
python3 -m http.server 18080 --bind 127.0.0.1 --directory "$LAB/site" > "$LAB/server.log" 2>&1 &
echo "$!" > "$LAB/server.pid"
sleep 1
The background process identifier is stored in server.pid, and service output is stored in server.log.
3
Establish local addressing and route evidence
ip address show dev lo
ip route get 127.0.0.1
getent ahosts localhost
The loopback interface should be active, the route lookup should select lo, and localhost should resolve to one or more loopback addresses.
4
Reproduce the deliberate failure
curl --connect-timeout 2 --max-time 4 -v http://127.0.0.1:18081/health.txt
The request should fail because the lab did not create a listener on port 18081. Record the exact curl message and exit status rather than paraphrasing it as a generic network failure.
5
Inspect transport listeners
ss -lntp '( sport = :18080 or sport = :18081 )'
A listener should appear on 127.0.0.1:18080, while no listener should appear on port 18081. Process details may be partially hidden on some systems, but the local address and port should remain visible.
6
Test the correct endpoint
curl --connect-timeout 2 --max-time 4 -v http://127.0.0.1:18080/health.txt
printf 'curl_exit=%s\n' "$?"
The client should connect, receive an HTTP success response, print class26-ok, and report curl_exit=0.
7
Correlate client and server evidence
LAB=/opt/lab-classroom/class26
cat "$LAB/server.log"
cat "$LAB/server.pid"
The log should contain the successful request for /health.txt. The failed request to port 18081 should not appear because it never reached this HTTP process.
8
Optionally inspect the local path
command -v tracepath >/dev/null 2>&1 && tracepath -n 127.0.0.1 || printf 'tracepath is not installed; optional step skipped\n'
If available, tracepath should show a host-local path rather than an external router.
9
Write the incident conclusion
LAB=/opt/lab-classroom/class26
printf '%s\n' 'Symptom: HTTP request to 127.0.0.1:18081 failed.' 'Evidence: route selected lo; ss showed a listener only on 127.0.0.1:18080; HTTP on 18080 returned class26-ok.' 'Conclusion: the client used the wrong destination port, not a broken route or failed HTTP service.' > "$LAB/conclusion.txt"
cat "$LAB/conclusion.txt"
The conclusion should connect each claim to collected evidence and should not claim that external networking was tested.
test -f /opt/lab-classroom/class26/site/health.txt && printf 'health file present\n' and confirm the success message.ss -lnt '( sport = :18080 )' and confirm that 127.0.0.1:18080 is in the LISTEN state.curl --fail --silent --show-error --max-time 4 http://127.0.0.1:18080/health.txt and confirm that the output is exactly class26-ok.curl --silent --max-time 2 http://127.0.0.1:18081/health.txt >/dev/null; test "$?" -ne 0 and confirm that the shell test succeeds.grep -F 'GET /health.txt' /opt/lab-classroom/class26/server.log and confirm that at least one successful request is logged.grep -F 'wrong destination port' /opt/lab-classroom/class26/conclusion.txt and confirm that the evidence-based conclusion was recorded.| Symptom | Likely cause | Fix |
|---|---|---|
| python3 reports that port 18080 is already in use. | Another process is already listening on the selected port, or a previous lab server is still running. | Inspect ss -lntp '( sport = :18080 )'. If the listener is the prior lab process recorded in server.pid, verify its command line and stop it using the controlled procedure. Do not terminate an unidentified service. |
| The request to port 18080 is refused. | The Python server failed to start, exited immediately, or is not bound to the expected address and port. | Read /opt/lab-classroom/class26/server.log, check the PID file, and inspect ss -lntp '( sport = :18080 )'. Correct the startup command only after identifying the failure. |
| The request to port 18080 connects but returns an HTTP 404 response. | The server is reachable, but health.txt is absent from the configured document directory or the requested path is incorrect. | Confirm that /opt/lab-classroom/class26/site/health.txt exists and that the request path is /health.txt. Treat this as an application resource problem, not a routing problem. |
| curl is not found. | The required client package is absent from the lab host. | Stop the exercise and install curl through the platform's approved package-management process outside this lesson, then repeat the lab. Do not substitute unreviewed download-and-execute commands. |
| getent returns IPv6 and IPv4 results in an unexpected order. | The system's host database and address-selection policy prefer one address family over the other. | Record the result as resolver evidence. Continue using the literal IPv4 loopback address for the controlled service test because the server is intentionally bound to 127.0.0.1. |
| ss shows the listener but omits the process name or PID. | The current user lacks permission to view complete process metadata. | Use the visible address, port, and LISTEN state as evidence. Correlate the service with the PID file and server log instead of broadening privileges unnecessarily. |
| The request hangs until the maximum time is reached instead of being refused immediately. | The environment is handling the destination differently than expected, or a local packet-filter policy is silently discarding traffic. | Confirm that the destination is exactly 127.0.0.1, inspect route and listener evidence again, record the timeout as distinct from refusal, and consult the host owner before changing any security policy. |
| server.log remains empty after a successful response. | Output buffering, inspection of the wrong file, or a different process serving the request. | Confirm the server command line associated with the recorded PID, repeat the request, wait briefly, and inspect the exact path /opt/lab-classroom/class26/server.log. |
Use read-only observations before corrective actions.
Bind the training service to 127.0.0.1 so it is not intentionally exposed through non-loopback interfaces.
Use explicit connection and total time limits for client probes.
Probe only systems and ports for which authorization has been granted.
Do not include credentials, session tokens, or sensitive response bodies in shared troubleshooting records.
Verify process identity before sending a termination signal because PID files can become stale.
Avoid changing routing, resolver settings, interface state, or security policy during evidence collection.
The lab serves only the text class26-ok. Logs and notes remain under /opt/lab-classroom/class26/ and should not contain secrets.
The service listens only on the IPv4 loopback address. This design limits direct access to processes on the same host, although local users may still be able to connect.
Commands demonstrated against loopback must not be redirected toward third-party systems without explicit permission.
Stop only the lab-owned Python process and remove only the class 26 workspace.
Set LAB=/opt/lab-classroom/class26.
If $LAB/server.pid exists, read the PID and inspect ps -p "$PID" -o pid=,args=.
Proceed only if the displayed process is the expected Python HTTP server using port 18080 and the class 26 site directory.
Run kill "$PID" and verify that ss -lnt '( sport = :18080 )' no longer shows the lab listener.
Run sudo rm -r -- /opt/lab-classroom/class26/ only after confirming the exact path.
Verify rollback with test ! -e /opt/lab-classroom/class26 && printf 'class26 workspace removed\n'.
After successful rollback, no lab-created files remain. The lesson does not intentionally change persistent host networking.