Why a static health check is not enough
A site can serve index.html while a PHP endpoint fails because static HTML and PHP requests take different execution paths. Apache must map the request to the correct document root, invoke the FastCGI handler, reach the PHP-FPM socket or port, and receive a valid PHP response. Distinguish a 404 caused by an incorrect path from a 503 caused by an unavailable FastCGI backend.
Never diagnose by publishing a PHP file containing credentials or by returning PHP source to the browser. On the JoeC deployment, GET /api/contact.php should return HTTP 405 with JSON because that route accepts POST; this proves execution of the handler, unlike the static healthz.txt check.
Validate configuration and active processes
Confirm the host distribution, then test the correct Apache service and the installed PHP-FPM version. On Debian/Ubuntu, service names are typically apache2 and phpX.Y-fpm. On Rocky/Alma/RHEL, they are commonly httpd and php-fpm. Adapt commands to your system.
sudo apache2ctl configtest
systemctl --failed --no-pager
sudo ss -lntp | grep -E ':(80|8080|443)\b'
php -v
curl -i -H 'Host: joec.pw' http://127.0.0.1:8080/api/contact.php
A Syntax OK result only validates Apache syntax; it does not prove PHP-FPM has started. Check the default FPM pool for its listen directive, socket permissions, service health and the handler target in Apache. A Unix-domain socket path mismatch can prevent PHP requests even when Apache itself is healthy.
Follow the failing request through the stack
First inspect the HTTP status and response content; never assume the site's root page and form endpoint use identical handlers. Then review Apache's error log and the relevant FPM journal around the request time.
sudo journalctl -u apache2 --since '-20 minutes' --no-pager
sudo systemctl status 'php*-fpm' --no-pager
Some shells/systemd versions do not expand a wildcard unit argument as expected; replace it with the installed phpX.Y-fpm unit. Watch for connection refused, permission denied, missing PHP modules and script path errors. Avoid indiscriminately increasing timeouts or disabling SELinux/AppArmor to suppress a symptom.
Verify and preserve the security boundary
The JoeC origin normally listens only on 127.0.0.1:8080, behind a same-VM Cloudflare Tunnel. Keep that boundary unless a deliberate architecture review changes it. Ensure .php requests are routed into the FastCGI handler rather than served as downloads. Check the form with a safe GET first; then test a configured non-sensitive submission through an authorized mail workflow. Confirm that application failures do not expose source code, paths or private configuration.