Build hosting-list, hosting-info & hosting-health
A hosting platform needs two capabilities:
PROVISION
↓
OBSERVE
↓
DIAGNOSE
↓
REPAIR
We already started the provisioning engine.
Now we build the observability layer.
1. Three New Commands
We want:
sudo hosting-list
sudo hosting-info example.com
sudo hosting-health example.com
They have different purposes.
2. hosting-list
Purpose:
Show all websites managed by CresignSys.
Example:
DOMAIN STATUS
------------------------------------------------------
learn.cresignsys.com ACTIVE
shop.cresignsys.com ACTIVE
medical.cresignsys.com ACTIVE
example.com ERROR
3. hosting-info
Purpose:
Show the configuration and metadata of one website.
Example:
Domain:
example.com
Site ID:
1027
Status:
ACTIVE
Linux User:
csp1027
Web Root:
/storage/websites/example.com/public
PHP Version:
8.3
Database:
csp1027_wp
4. hosting-health
Purpose:
Test whether the website is actually functioning.
Example:
Filesystem ✓
PHP-FPM ✓
PHP Socket ✓
Nginx ✓
Database ✓
WordPress ✓
HTTP ✓
HTTPS ✓
5. Why Separate Them?
Don’t make:
hosting-info
perform repairs.
Don’t make:
hosting-health
modify configuration.
Observability commands should ideally be:
READ ONLY
This is an important operational principle.
6. Source of Truth
We previously created:
/var/lib/cresignsys/sites/
For example:
/var/lib/cresignsys/sites/
├── site1.example.com/
├── site2.example.com/
└── learn.cresignsys.com/
Each site can contain:
state
site.conf
provision.log
7. site.conf
Example:
DOMAIN=learn.cresignsys.com
SITE_ID=1027
SITE_USER=csp1027
WEB_ROOT=/storage/websites/learn.cresignsys.com/public
DB_NAME=csp1027_wp
DB_USER=csp1027_db
PHP_VERSION=8.3
PHP_SOCKET=/run/php/csp1027.sock
STATUS=ACTIVE
Notice:
Do not store the database password here.
8. Build hosting-list
Create:
sudo nano /usr/local/bin/hosting-list
Start:
#!/usr/bin/env bash
set -Eeuo pipefail
STATE_DIR="/var/lib/cresignsys/sites"
if [[ ! -d "$STATE_DIR" ]]; then
echo "No CresignSys sites found."
exit 0
fi
9. Print Header
printf "%-35s %-12s\n" "DOMAIN" "STATUS"
printf "%-35s %-12s\n" "-----------------------------------" "------------"
10. Read Site Directories
for site_dir in "$STATE_DIR"/*; do
[[ -d "$site_dir" ]] || continue
DOMAIN="$(basename "$site_dir")"
STATE_FILE="${site_dir}/state"
STATUS="UNKNOWN"
if [[ -f "$STATE_FILE" ]]; then
STATUS="$(
awk -F= '$1=="STATUS" {print $2}' "$STATE_FILE"
)"
fi
printf "%-35s %-12s\n" "$DOMAIN" "$STATUS"
done
11. Make It Executable
sudo chmod 755 /usr/local/bin/hosting-list
Run:
sudo hosting-list
12. First Improvement — Sort the List
Instead of relying on filesystem ordering:
for site_dir in "$STATE_DIR"/*;
you can collect and sort the output.
This becomes useful once you have dozens or hundreds of websites.
13. Better Output
Eventually:
DOMAIN STATUS
------------------------------------------------------
acme.com ACTIVE
learn.cresignsys.com ACTIVE
shop.cresignsys.com ACTIVE
test.example.com ERROR
Alphabetical ordering makes administration easier.
14. Build hosting-info
Create:
sudo nano /usr/local/bin/hosting-info
Start:
#!/usr/bin/env bash
set -Eeuo pipefail
STATE_DIR="/var/lib/cresignsys/sites"
if [[ $# -ne 1 ]]; then
echo "Usage: hosting-info DOMAIN"
exit 1
fi
DOMAIN="$1"
SITE_DIR="${STATE_DIR}/${DOMAIN}"
SITE_CONF="${SITE_DIR}/site.conf"
15. Verify Site Exists
if [[ ! -f "$SITE_CONF" ]]; then
echo "ERROR: Site not found: $DOMAIN"
exit 1
fi
16. Load Configuration
source "$SITE_CONF"
Now variables become available:
DOMAIN
SITE_ID
SITE_USER
WEB_ROOT
DB_NAME
DB_USER
PHP_VERSION
PHP_SOCKET
STATUS
17. Display Site Information
echo
echo "CresignSys Site Information"
echo "============================"
echo
echo "Domain : $DOMAIN"
echo "Site ID : $SITE_ID"
echo "Status : $STATUS"
echo "Linux User : $SITE_USER"
echo "Web Root : $WEB_ROOT"
echo "PHP Version : $PHP_VERSION"
echo "PHP Socket : $PHP_SOCKET"
echo "Database : $DB_NAME"
echo "DB User : $DB_USER"
echo
18. Show Directory Status
Add:
if [[ -d "$WEB_ROOT" ]]; then
echo "Web Root : EXISTS"
else
echo "Web Root : MISSING"
fi
19. Show Disk Usage
Use:
du -sh "$(dirname "$WEB_ROOT")" 2>/dev/null || true
Example:
Disk Usage : 1.2G
20. Don’t Use du as a Quota System
This is important.
du tells you approximately:
how much space is currently used
It does not enforce:
maximum storage
Quota enforcement is a separate feature.
21. Show PHP-FPM Status
Use the configured service:
systemctl is-active "php${PHP_VERSION}-fpm" \
2>/dev/null || true
Output:
PHP-FPM : active
22. Check PHP Socket
if [[ -S "$PHP_SOCKET" ]]; then
echo "PHP Socket : EXISTS"
else
echo "PHP Socket : MISSING"
fi
This is a very useful diagnostic.
23. Check Nginx Configuration
NGINX_CONF="/etc/nginx/sites-available/${DOMAIN}.conf"
if [[ -f "$NGINX_CONF" ]]; then
echo "Nginx Config : EXISTS"
else
echo "Nginx Config : MISSING"
fi
24. Check Enabled Site
NGINX_LINK="/etc/nginx/sites-enabled/${DOMAIN}.conf"
if [[ -L "$NGINX_LINK" ]]; then
echo "Nginx Site : ENABLED"
else
echo "Nginx Site : NOT ENABLED"
fi
25. Check WordPress
if [[ -f "$WEB_ROOT/wp-config.php" ]]; then
echo "WordPress : CONFIGURED"
else
echo "WordPress : NOT CONFIGURED"
fi
26. Don’t Run wp as Root
If you need deeper WordPress information:
sudo -u "$SITE_USER" \
wp --path="$WEB_ROOT" core is-installed
The principle remains:
WordPress operations
↓
site user
not:
root
27. Build hosting-health
Now the most useful command.
Create:
sudo nano /usr/local/bin/hosting-health
Start:
#!/usr/bin/env bash
set -Eeuo pipefail
STATE_DIR="/var/lib/cresignsys/sites"
if [[ $# -ne 1 ]]; then
echo "Usage: hosting-health DOMAIN"
exit 1
fi
DOMAIN="$1"
SITE_DIR="${STATE_DIR}/${DOMAIN}"
SITE_CONF="${SITE_DIR}/site.conf"
28. Load Site Information
if [[ ! -f "$SITE_CONF" ]]; then
echo "ERROR: Site not found."
exit 1
fi
source "$SITE_CONF"
29. Health Check Philosophy
Each test should return:
PASS
or:
FAIL
Do not let one failed check hide the other checks.
30. Helper Function
Create:
check() {
local name="$1"
shift
if "$@" >/dev/null 2>&1; then
printf "[OK] %-20s\n" "$name"
return 0
else
printf "[FAIL] %-20s\n" "$name"
return 1
fi
}
Now:
check "Web Root" test -d "$WEB_ROOT"
prints:
[OK] Web Root
31. Don’t Stop After One Failure
For health checking, we want:
Filesystem ✓
PHP-FPM ✓
Socket ✗
Nginx ✓
Database ✓
WordPress ✓
rather than stopping at:
Socket failed
We want to know everything that is wrong.
32. Maintain Health Status
HEALTH_OK=1
If a test fails:
HEALTH_OK=0
At the end:
if [[ "$HEALTH_OK" -eq 1 ]]; then
echo
echo "Overall Status: HEALTHY"
exit 0
else
echo
echo "Overall Status: UNHEALTHY"
exit 1
fi
33. Filesystem Check
if ! check "Web Root" test -d "$WEB_ROOT"; then
HEALTH_OK=0
fi
34. WordPress Check
if ! check "WordPress" \
sudo -u "$SITE_USER" \
wp --path="$WEB_ROOT" core is-installed
then
HEALTH_OK=0
fi
35. Database Check
if ! check "Database" \
sudo -u "$SITE_USER" \
wp --path="$WEB_ROOT" db check
then
HEALTH_OK=0
fi
This tests the actual WordPress database connection.
That is better than merely checking whether MySQL is running.
36. PHP-FPM Service Check
PHP_SERVICE="php${PHP_VERSION}-fpm"
if ! check "PHP-FPM" \
systemctl is-active "$PHP_SERVICE"
then
HEALTH_OK=0
fi
37. PHP Socket Check
if ! check "PHP Socket" test -S "$PHP_SOCKET"; then
HEALTH_OK=0
fi
38. Nginx Check
Global Nginx:
if ! check "Nginx Service" \
systemctl is-active nginx
then
HEALTH_OK=0
fi
39. Nginx Configuration Check
if ! check "Nginx Config" nginx -t; then
HEALTH_OK=0
fi
40. Domain HTTP Check
Now test the actual website:
if ! check "HTTP" \
curl -fsSI --max-time 10 "http://${DOMAIN}"
then
HEALTH_OK=0
fi
This is important because:
Nginx service running
does not necessarily mean:
your domain works
41. HTTP Health Check
The path is now:
Browser
↓
DNS
↓
Server
↓
Nginx
↓
Domain server block
↓
WordPress
This tests multiple layers simultaneously.
42. HTTPS Check
If SSL is installed:
if ! check "HTTPS" \
curl -fsSI --max-time 10 "https://${DOMAIN}"
then
HEALTH_OK=0
fi
But if SSL hasn’t been configured yet, don’t mark HTTPS as a required failure.
43. SSL-Aware Health Check
You can detect whether HTTPS is expected.
For example:
SSL_STATUS=ACTIVE
in site.conf.
Then:
SSL ACTIVE
↓
test HTTPS
SSL NOT ACTIVE
↓
skip HTTPS test
44. Health Output
A good final result:
CresignSys Health Check
========================
Domain : learn.cresignsys.com
[OK] Web Root
[OK] PHP-FPM
[OK] PHP Socket
[OK] Nginx Service
[OK] Nginx Config
[OK] WordPress
[OK] Database
[OK] HTTP
[OK] HTTPS
Overall Status: HEALTHY
45. Failed Example
CresignSys Health Check
========================
Domain : learn.cresignsys.com
[OK] Web Root
[OK] PHP-FPM
[FAIL] PHP Socket
[OK] Nginx Service
[OK] Nginx Config
[OK] WordPress
[OK] Database
[FAIL] HTTP
Overall Status: UNHEALTHY
This immediately tells you where to investigate.
46. Why Socket Failure Can Cause HTTP Failure
The chain:
Nginx
↓
PHP-FPM socket
↓
PHP
If the socket is missing:
Nginx
↓
socket ✗
dynamic WordPress requests can return:
502 Bad Gateway
47. Health Checks Are Diagnostic Clues
For example:
Nginx ✓
Socket ✗
Likely area:
PHP-FPM pool
Socket ✓
HTTP ✗
Investigate:
Nginx routing
DNS
firewall
domain configuration
HTTP ✓
WordPress ✗
Investigate:
database
filesystem
WordPress installation
48. HTTP Status Codes
Your health checker can eventually inspect HTTP status.
For example:
200 → healthy
301/302 → redirect
403 → access problem
404 → routing/resource problem
500 → application error
502 → PHP-FPM problem
503 → service unavailable
This makes the health system more intelligent.
49. Capture HTTP Status
Instead of:
curl -fsSI
you can eventually capture:
curl -o /dev/null -s -w "%{http_code}"
Then:
HTTP_STATUS=200
and your health engine can interpret it.
50. Don’t Treat Every Redirect as Failure
A website may legitimately return:
301
because:
HTTP → HTTPS
So:
301
is not necessarily unhealthy.
Your health system should understand expected behavior.
51. Add SSL Status
In your site.conf:
SSL_STATUS=ACTIVE
Then:
echo "SSL Status : $SSL_STATUS"
52. hosting-info Should Be Read Only
This command should never do:
systemctl restart nginx
or:
mysql ...
to modify anything.
Its role is:
inspect
53. hosting-health Should Also Be Read Only
It can run:
wp db check
nginx -t
curl
systemctl is-active
but should not:
restart services
change permissions
recreate databases
That belongs to:
hosting-repair
54. This Creates a Clean Operational Model
hosting-create
↓
CREATE
hosting-list
↓
OBSERVE
hosting-info
↓
INSPECT
hosting-health
↓
DIAGNOSE
hosting-repair
↓
FIX
This separation is extremely useful.
55. Add hosting-list --json
Eventually your control panel will need machine-readable output.
Human output:
DOMAIN STATUS
Machine output:
{
"domain": "example.com",
"status": "ACTIVE"
}
This allows the web panel to consume the CLI safely.
56. But Don’t Parse Human Text
Avoid designing the control panel to parse:
DOMAIN STATUS
example.com ACTIVE
Human formatting can change.
Instead:
hosting-list --json
should produce structured JSON.
57. JSON Becomes the API Boundary
Eventually:
CHP Web Panel
↓
API
↓
Provisioning Engine
↓
JSON result
For example:
{
"domain": "example.com",
"status": "ACTIVE",
"php": "8.3",
"database": "healthy",
"http": 200
}
58. This Is Better Than Shell Scraping
Bad architecture:
Web Panel
↓
grep output from Bash
Better:
Web Panel
↓
structured API
↓
provisioning engine
We will eventually move toward the second architecture.
59. Add Timestamps
Health results should eventually contain:
checked_at
For example:
2026-08-13 15:45:22
This matters because:
healthy now
does not mean:
healthy forever
60. Health History
Eventually store:
site_id
timestamp
http_status
php_status
database_status
ssl_status
overall_status
Then the panel can show:
Health
──────────────
15:00 ✓
15:05 ✓
15:10 ✓
15:15 ✗
15:20 ✓
61. This Enables Monitoring
Once health checks exist, you can later build:
automatic monitoring
such as:
if website unhealthy
↓
alert administrator
This is where the observability layer becomes operational monitoring.
62. Disk Usage in hosting-health
Add:
du -sh "$(dirname "$WEB_ROOT")"
Then:
Disk Usage : 1.4G
Eventually compare against the hosting plan.
63. Storage Warning
Suppose the plan allows:
5 GB
and usage is:
4.8 GB
The system can report:
WARNING: 96% storage used
This is the beginning of resource monitoring.
64. Database Size
You can also measure database size.
Conceptually:
SELECT
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2)
FROM information_schema.tables
WHERE table_schema = 'database_name';
Then show:
Database Size : 420 MB
65. Don’t Query Every Table Excessively
For a small number of websites, this is fine.
For hundreds or thousands:
health check
↓
hundreds of SQL queries
can itself become a performance problem.
Later you will want:
cached metrics
and periodic collection.
66. Website Health vs Server Health
These are different.
Server health
CPU
RAM
Disk
MySQL
Nginx
PHP-FPM
Website health
DNS
HTTP
WordPress
Database
SSL
Filesystem
You need both.
67. Example
Server:
CPU ✓
RAM ✓
Disk ✓
Nginx ✓
MySQL ✓
But:
example.com → HTTP 502
Therefore:
server healthy
site unhealthy
This distinction prevents misleading diagnostics.
68. Site Health Model
Eventually:
SITE HEALTH
│
├── DNS
├── HTTP
├── HTTPS
├── Nginx
├── PHP-FPM
├── PHP socket
├── WordPress
├── Database
├── Filesystem
└── SSL
69. Server Health Model
SERVER HEALTH
│
├── CPU
├── Memory
├── Storage
├── Inodes
├── Nginx
├── PHP-FPM
├── MySQL
└── Network
70. Control Panel Dashboard
These commands now provide the foundation for a dashboard:
CHP DASHBOARD
Sites
────────────────────────────────────
Total: 42
Active: 40
Error: 2
Server
────────────────────────────────────
CPU: Healthy
Memory: Healthy
Storage: 62%
MySQL: Healthy
Nginx: Healthy
Websites
────────────────────────────────────
learn.cresignsys.com ✓
shop.cresignsys.com ✓
example.com ✗
71. The Architecture Is Growing
We now have:
CHP
│
┌─────────┴─────────┐
▼ ▼
Provisioning Observability
│ │
hosting-create hosting-list
hosting-info
hosting-health
Next:
Repair
Then:
SSL
Then:
Backup
72. Lesson 067 — Core Principle
A professional hosting platform must be able to answer:
What websites exist, how are they configured, and are they actually working?
That requires three separate capabilities:
hosting-list
↓
What exists?
hosting-info
↓
How is it configured?
hosting-health
↓
Is it working?
This is the foundation of reliable hosting operations.
Next Lesson — 068
Build hosting-repair
We now have:
CREATE
↓
LIST
↓
INFO
↓
HEALTH
The next logical component is:
HEALTH
↓
FAILURE
↓
REPAIR
We will build:
sudo hosting-repair example.com
with targeted repair operations such as:
sudo hosting-repair example.com --php
sudo hosting-repair example.com --nginx
sudo hosting-repair example.com --permissions
sudo hosting-repair example.com --wordpress
without deleting the website or database.
Leave a Reply