Build hosting-site-status
We now have separate diagnostic commands:
hosting-info DOMAIN
hosting-dns-check DOMAIN
hosting-health DOMAIN
The next step is to combine them into one site status engine.
Target:
sudo hosting-site-status example.com
1. Why hosting-site-status?
An administrator should not need to run:
hosting-info
hosting-dns-check
hosting-health
hosting-ssl
hosting-backup-list
individually just to understand one website.
Instead:
SITE STATUS
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
DOMAIN DNS SERVER
│ │ │
▼ ▼ ▼
SSL HTTP PHP-FPM
│ │
└─────────────────┬─────────────────┘
▼
APPLICATION
│
▼
BACKUP
│
▼
OVERALL STATE
2. Target Output
For a healthy site:
CresignSys Site Status
======================
Domain:
example.com
Overall:
HEALTHY
SERVICE
State: ACTIVE
DNS
IPv4: CORRECT
IPv6: NOT CONFIGURED
WEB
Nginx: OK
HTTP: OK
HTTPS: OK
SSL
Status: ACTIVE
Certificate: VALID
PHP
Version: 8.3
PHP-FPM: OK
Socket: OK
DATABASE
MySQL: OK
WordPress DB: OK
APPLICATION
WordPress: OK
BACKUP
Status: HEALTHY
Last Backup: 2026-08-13 16:00
FILES
Web Root: OK
Ownership: OK
Overall Health:
HEALTHY
3. Status Is Not the Same as Health
This distinction is important.
A site can be:
SERVICE_STATE=SUSPENDED
HEALTH_STATE=HEALTHY
That means:
The infrastructure is working, but the administrator intentionally suspended public access.
Therefore don’t collapse everything into one Boolean.
4. Site State Model
Use:
SERVICE_STATE
with:
ACTIVE
SUSPENDED
PROVISIONING
WAITING_DNS
ERROR
DELETING
DELETED
5. Health State Model
Use:
HEALTH_STATE
with:
HEALTHY
DEGRADED
CRITICAL
UNKNOWN
6. Example
Healthy active site
SERVICE_STATE=ACTIVE
HEALTH_STATE=HEALTHY
Suspended site
SERVICE_STATE=SUSPENDED
HEALTH_STATE=HEALTHY
DNS waiting
SERVICE_STATE=WAITING_DNS
HEALTH_STATE=DEGRADED
Database failure
SERVICE_STATE=ACTIVE
HEALTH_STATE=CRITICAL
This is much more expressive.
7. Status Categories
The unified checker should inspect:
1. Site metadata
2. Domain
3. DNS
4. Nginx
5. HTTP
6. HTTPS
7. SSL
8. PHP-FPM
9. PHP socket
10. Database
11. WordPress
12. Filesystem
13. Backup
14. Service state
8. Don’t Make Every Check Fatal
This is a common mistake.
Suppose:
DNS = PENDING
The script should not necessarily terminate.
It should continue:
DNS PENDING
HTTP UNKNOWN
HTTPS UNKNOWN
PHP OK
Database OK
WordPress OK
Then classify:
Overall:
WAITING_DNS
This gives much more useful information.
9. Check Execution Model
Use:
RUN CHECK
│
├── PASS
├── WARN
├── FAIL
└── UNKNOWN
Don’t immediately:
exit 1
after the first failure.
10. Check Result Structure
Each check should conceptually return:
NAME
STATUS
MESSAGE
SEVERITY
Example:
DNS
STATUS=FAIL
MESSAGE=Domain points to 198.51.100.20
SEVERITY=CRITICAL
11. Severity Levels
Use:
OK
WARNING
CRITICAL
UNKNOWN
Example:
DNS:
OK
SSL:
WARNING
Database:
CRITICAL
Overall health becomes:
CRITICAL
because the database is unavailable.
12. Overall Health Algorithm
A simple first version:
If any critical check fails:
CRITICAL
Else if any warning exists:
DEGRADED
Else:
HEALTHY
If checks cannot be performed:
UNKNOWN
where appropriate.
13. Example
DNS OK
Nginx OK
HTTP OK
HTTPS OK
SSL WARNING
PHP OK
Database OK
WordPress OK
Backup OK
Result:
HEALTH_STATE=DEGRADED
14. Example — Critical
DNS OK
Nginx OK
HTTP 500
HTTPS 500
SSL OK
PHP OK
Database FAIL
WordPress FAIL
Result:
HEALTH_STATE=CRITICAL
15. Example — Suspended
Service SUSPENDED
DNS OK
Nginx OK
HTTPS 503
SSL OK
Database OK
WordPress OK
Don’t report:
CRITICAL
if 503 is intentionally produced by the suspension configuration.
Report:
SERVICE_STATE=SUSPENDED
HEALTH_STATE=HEALTHY
16. Create Script
Create:
sudo nano /usr/local/bin/hosting-site-status
Start:
#!/usr/bin/env bash
set -Eeuo pipefail
17. Arguments
if [[ $# -ne 1 ]]; then
echo "Usage: hosting-site-status DOMAIN"
exit 1
fi
DOMAIN="$1"
Later we can add:
--json
--quiet
--verbose
18. Load Site Configuration
SITE_ROOT="/var/lib/cresignsys/sites/${DOMAIN}"
SITE_CONF="${SITE_ROOT}/site.conf"
if [[ ! -f "$SITE_CONF" ]]; then
echo "ERROR: Site not found: $DOMAIN"
exit 1
fi
source "$SITE_CONF"
19. Don’t Trust source Forever
There is an architectural warning here.
If:
site.conf
contains data controlled by an untrusted user, blindly doing:
source site.conf
can execute arbitrary shell commands.
For an administrator-controlled system this may be acceptable during early development, but a production multi-tenant platform should eventually use:
INI
JSON
SQLite
database
with safe parsing.
20. Site State
Read:
SERVICE_STATE
If missing:
SERVICE_STATE=UNKNOWN
Then display:
SERVICE
State: ACTIVE
21. Domain Check
Verify:
DOMAIN
matches the requested site.
Don’t allow:
hosting-site-status example.com
to accidentally load:
shop.com
metadata.
22. DNS Check
Call the DNS checking logic.
Preferably don’t execute:
hosting-dns-check "$DOMAIN"
and parse its human-readable output.
Instead, create a reusable library:
/etc/cresignsys/lib/dns.sh
For example:
check_dns()
returns structured variables.
23. Why Libraries Matter
Without libraries:
hosting-health
hosting-site-status
hosting-ssl
hosting-domain-list
may all implement DNS checking differently.
That causes inconsistent results.
Better:
dns.sh
│
┌────────┼────────┐
▼ ▼ ▼
health status ssl
24. Create Common Library
Directory:
sudo mkdir -p /etc/cresignsys/lib
Then:
sudo nano /etc/cresignsys/lib/dns.sh
Eventually:
dns.sh
domain.sh
nginx.sh
php.sh
mysql.sh
wordpress.sh
backup.sh
logging.sh
lock.sh
25. This Changes the Architecture
Instead of:
10 scripts
×
10 duplicated functions
we get:
10 commands
│
▼
shared libraries
│
▼
common behavior
This is a major improvement.
26. Nginx Check
Check service:
systemctl is-active --quiet nginx
If active:
Nginx:
OK
But this alone isn’t enough.
Nginx can be running while the site’s server block is broken.
27. Site-Specific Nginx Check
Verify the domain exists in the loaded Nginx configuration.
For example:
nginx -T
and inspect the generated configuration.
Don’t repeatedly run expensive full configuration dumps for every site on a large server.
Eventually maintain configuration metadata.
28. Better Nginx Check
Check:
site config exists
server_name exists
configuration enabled
nginx syntax valid
Then:
Nginx:
OK
29. HTTP Check
Use:
curl -I --max-time 10 "http://${DOMAIN}"
Possible result:
200
301
302
403
404
500
503
Not every non-200 response is necessarily a server failure.
30. HTTP Interpretation
For an active site:
200 → OK
301 → OK
302 → OK
403 → WARNING/possibly application policy
404 → WARNING
500 → CRITICAL
502 → CRITICAL
503 → CRITICAL
For a suspended site:
503 → EXPECTED
31. HTTPS Check
curl -Ik --max-time 10 "https://${DOMAIN}"
The -k option is useful for diagnostic separation when you want to test HTTP behavior even if certificate validation fails.
But don’t use -k to declare SSL healthy.
Certificate validation needs its own check.
32. SSL Check
Use:
openssl s_client
or an appropriate certificate inspection method.
Check:
certificate exists
certificate not expired
hostname covered
chain valid
33. SSL States
ACTIVE
EXPIRING_SOON
EXPIRED
MISMATCH
MISSING
ERROR
For example:
SSL:
EXPIRING_SOON
should be:
WARNING
while:
SSL:
EXPIRED
is:
CRITICAL
34. PHP-FPM Check
Find the configured PHP version:
PHP_VERSION=8.3
Then:
systemctl is-active php8.3-fpm
Expected:
PHP-FPM:
OK
35. PHP Socket Check
If your Nginx configuration uses:
/run/php/php8.3-fpm-example.com.sock
verify the socket exists.
For example:
PHP Socket:
OK
If:
Nginx → missing socket
you can get:
502 Bad Gateway
even though PHP-FPM itself is running.
36. Database Check
Check MySQL:
systemctl is-active --quiet mysql
Then test the site’s database credentials.
Don’t just check:
MySQL service = running
because the specific site’s database could still be inaccessible.
37. WordPress Database Check
Use:
sudo -u "$SITE_USER" \
wp --path="$WEB_ROOT" db check
Expected:
Database:
OK
38. WordPress Check
sudo -u "$SITE_USER" \
wp --path="$WEB_ROOT" core is-installed
Then optionally:
sudo -u "$SITE_USER" \
wp --path="$WEB_ROOT" core verify-checksums
Be aware that checksum verification may not be appropriate for sites with modified core files.
39. Filesystem Check
Verify:
WEB_ROOT exists
wp-config.php exists
permissions reasonable
owner correct
disk available
Example:
FILES
Web Root: OK
Ownership: OK
Disk: OK
40. Disk Space
A site can be perfectly configured but fail because:
DISK = 100%
Check:
df -P "$WEB_ROOT"
Use thresholds.
For example:
< 80% → OK
80-90% → WARNING
> 90% → CRITICAL
These are policy examples and can be configurable.
41. Inode Usage
Disk capacity isn’t the only problem.
A server can have:
Disk:
50% used
but:
Inodes:
100% used
Then new files cannot be created.
Check:
df -Pi "$WEB_ROOT"
This should eventually be part of site health.
42. Backup Check
Read the backup inventory.
Check:
backup enabled?
last successful backup?
backup age?
backup verification status?
Example:
BACKUP
Enabled: YES
Last: 2 hours ago
Status: HEALTHY
43. Backup Warning
Suppose:
Last backup:
10 days ago
while policy requires:
daily
Then:
Backup:
WARNING
Overall:
DEGRADED
44. Missing Backup
If:
BACKUP_ENABLED=true
but:
no valid backup exists
then:
Backup:
CRITICAL
depending on your hosting policy.
45. Status Summary
At the bottom:
================================
SERVICE:
ACTIVE
HEALTH:
HEALTHY
RESULT:
HEALTHY
Or:
SERVICE:
ACTIVE
HEALTH:
CRITICAL
RESULT:
CRITICAL
46. Machine-Readable Mode
This is extremely important for the future control panel.
Add:
sudo hosting-site-status example.com --json
Output:
{
"domain": "example.com",
"service_state": "ACTIVE",
"health_state": "HEALTHY",
"dns": "CORRECT",
"nginx": "OK",
"http": "OK",
"https": "OK",
"ssl": "ACTIVE",
"php": "OK",
"database": "OK",
"wordpress": "OK",
"backup": "HEALTHY"
}
47. Why JSON Matters
A future dashboard can consume:
hosting-site-status --json
without parsing:
[OK] Nginx
[OK] PHP
text.
This creates a clean boundary:
CHP Engine
↓
JSON
↓
Dashboard
48. Don’t Build the Dashboard Yet
First make the CLI engine reliable.
The future dashboard can then be:
Web UI
↓
API
↓
CHP Engine
rather than duplicating all hosting logic in PHP/JavaScript.
49. Exit Codes
The command should also return useful exit codes.
For example:
0 = HEALTHY
1 = DEGRADED
2 = CRITICAL
3 = UNKNOWN
Then automation can do:
if hosting-site-status example.com --quiet; then
echo "Healthy"
fi
50. Why Exit Codes Matter
Monitoring systems don’t read pretty terminal output.
They need:
0
1
2
to decide whether an alert should be triggered.
This allows later integration with monitoring systems.
51. Quiet Mode
Add:
sudo hosting-site-status example.com --quiet
Output:
HEALTHY
or nothing except an error.
This is useful for scripts.
52. Verbose Mode
Add:
sudo hosting-site-status example.com --verbose
to show:
DNS query
resolver
IP addresses
TTL
Nginx server block
PHP socket
database connection
certificate details
backup age
disk usage
53. Normal Mode Should Stay Simple
Normal:
hosting-site-status example.com
should be readable.
Don’t dump 200 lines of technical information by default.
54. Example — Healthy
CresignSys Site Status
======================
example.com
SERVICE
State: ACTIVE
DNS
Status: CORRECT
WEB
Nginx: OK
HTTP: OK
HTTPS: OK
SSL
Status: ACTIVE
PHP
PHP-FPM: OK
Socket: OK
DATABASE
MySQL: OK
WordPress: OK
FILES
Web Root: OK
Ownership: OK
Disk: OK
BACKUP
Status: HEALTHY
Last: 2 hours ago
--------------------------------
OVERALL: HEALTHY
55. Example — Waiting for DNS
CresignSys Site Status
======================
example.com
SERVICE
State: WAITING_DNS
DNS
Status: PENDING
WEB
Nginx: OK
HTTP: UNKNOWN
HTTPS: UNKNOWN
SSL
Status: PENDING
PHP
PHP-FPM: OK
DATABASE
MySQL: OK
APPLICATION
WordPress: OK
BACKUP
Status: HEALTHY
--------------------------------
OVERALL: WAITING_DNS
This is much more useful than simply saying:
ERROR
56. Example — Database Failure
example.com
SERVICE
State: ACTIVE
DNS
Status: CORRECT
WEB
Nginx: OK
HTTP: 500
HTTPS: 500
PHP
PHP-FPM: OK
DATABASE
MySQL: FAIL
APPLICATION
WordPress: FAIL
BACKUP
Status: HEALTHY
--------------------------------
OVERALL: CRITICAL
57. Example — Suspended
example.com
SERVICE
State: SUSPENDED
DNS
Status: CORRECT
WEB
Nginx: OK
HTTPS: EXPECTED_503
SSL
Status: ACTIVE
PHP
PHP-FPM: OK
DATABASE
MySQL: OK
APPLICATION
WordPress: OK
BACKUP
Status: HEALTHY
--------------------------------
OVERALL: SUSPENDED
58. Important: Expected Failures
This is a major monitoring principle.
A suspended site returning:
503
isn’t necessarily a failure.
Similarly:
HTTP 301
isn’t necessarily a failure.
The checker needs context.
HTTP result
+
SERVICE_STATE
+
DOMAIN_TYPE
determines the meaning.
59. Redirect Domain
For:
old.example.com
the expected HTTP result might be:
301
Therefore:
301 = OK
for a redirect domain.
This is why checks must understand domain type.
60. Alias Domain
For:
www.example.com
expected:
200
or perhaps:
301 → example.com
depending on your canonical URL policy.
Again, the status engine needs metadata.
61. Domain Policy
Add:
CANONICAL=true
REDIRECT_TARGET=
or equivalent domain metadata.
Then health checks know what response is expected.
62. Status Engine Architecture
The internal model becomes:
SITE STATUS
│
┌────────────┼────────────┐
▼ ▼ ▼
Metadata Domain Service
│ │ │
▼ ▼ ▼
Policy DNS State
│
▼
Web Layer
│
┌─────────┼─────────┐
▼ ▼ ▼
HTTP HTTPS SSL
│
▼
Application
│
┌────────┼────────┐
▼ ▼ ▼
PHP DB WordPress
│
▼
Backup
│
▼
Health Engine
63. Shared Libraries
At this stage, create:
/etc/cresignsys/lib/
├── common.sh
├── domain.sh
├── dns.sh
├── nginx.sh
├── php.sh
├── mysql.sh
├── wordpress.sh
├── backup.sh
├── ssl.sh
├── logging.sh
└── lock.sh
This is a major architectural milestone.
64. common.sh
Put shared functions such as:
log_info()
log_warn()
log_error()
die()
require_root()
command_exists()
there.
65. domain.sh
Functions:
validate_domain()
normalize_domain()
domain_exists()
site_exists()
66. dns.sh
Functions:
get_a_records()
get_aaaa_records()
get_cname()
get_nameservers()
check_dns()
67. nginx.sh
Functions:
nginx_is_running()
nginx_config_valid()
site_config_exists()
reload_nginx()
68. php.sh
Functions:
php_fpm_is_running()
php_socket_exists()
get_php_version()
69. mysql.sh
Functions:
mysql_is_running()
database_exists()
database_connection_test()
70. wordpress.sh
Functions:
wordpress_is_installed()
wordpress_db_check()
wordpress_core_check()
71. backup.sh
Functions:
latest_backup()
backup_exists()
backup_is_valid()
backup_age()
72. ssl.sh
Functions:
certificate_exists()
certificate_expiry()
certificate_matches_domain()
73. logging.sh
Functions:
audit_log()
operation_log()
74. lock.sh
Functions:
acquire_site_lock()
release_site_lock()
This prevents duplicate locking logic across scripts.
75. Command Layer
Now the commands become relatively thin:
hosting-create
↓
common libraries
hosting-health
↓
common libraries
hosting-site-status
↓
common libraries
This is a much cleaner architecture than having every command contain thousands of lines of Bash.
76. Future API
Once the CLI is structured this way:
CHP Core
│
├── CLI
│
├── API
│
└── Dashboard
can all use the same underlying state and logic.
77. The Future Control Panel
The dashboard can eventually display:
CresignSys Hosting
────────────────────────────────
Sites 42
Healthy 37
Degraded 3
Critical 1
Suspended 1
DNS Issues 2
SSL Issues 1
Backup Issues 1
Click:
example.com
and get the complete status.
78. Monitoring
The status engine also allows scheduled monitoring:
every 5 minutes
↓
hosting-site-status
↓
problem?
↓
alert
For example:
Database failure detected
Domain: example.com
Time: 18:42
79. Avoid Alert Spam
If a site remains broken for:
30 minutes
don’t send:
360 alerts
Instead use state transitions:
HEALTHY
↓
CRITICAL
send alert.
Then:
CRITICAL
↓
CRITICAL
don’t repeatedly alert unless configured.
Then:
CRITICAL
↓
HEALTHY
send recovery notification.
80. Status History
Eventually store:
site_id
timestamp
health_state
reason
Example:
18:00 HEALTHY
18:42 CRITICAL DATABASE
18:47 CRITICAL DATABASE
18:53 HEALTHY
This creates a basic uptime history.
81. Uptime Percentage
Once history exists:
total monitoring time
-
downtime
can calculate:
99.9%
99.95%
99.99%
This can eventually become a hosting dashboard metric.
82. Lesson 076 — Core Principle
hosting-site-status should become the single source of operational truth for a website.
It combines:
DOMAIN
DNS
NGINX
HTTP
HTTPS
SSL
PHP
DATABASE
WORDPRESS
FILES
BACKUP
SERVICE STATE
and converts those individual signals into:
HEALTHY
DEGRADED
CRITICAL
SUSPENDED
WAITING_DNS
83. Updated CHP Command Set
hosting-create DOMAIN
hosting-list
hosting-info DOMAIN
hosting-site-status DOMAIN
hosting-health DOMAIN
hosting-repair DOMAIN --php
hosting-repair DOMAIN --nginx
hosting-repair DOMAIN --permissions
hosting-repair DOMAIN --wordpress
hosting-ssl DOMAIN
hosting-backup DOMAIN
hosting-backup-list DOMAIN
hosting-backup-verify DOMAIN BACKUP_ID
hosting-backup-prune DOMAIN --dry-run
hosting-backup-prune DOMAIN --execute
hosting-restore DOMAIN BACKUP_ID
hosting-suspend DOMAIN
hosting-unsuspend DOMAIN
hosting-domain-add DOMAIN
hosting-domain-add DOMAIN --alias TARGET
hosting-domain-add DOMAIN --redirect TARGET
hosting-domain-list DOMAIN
hosting-domain-remove DOMAIN
hosting-dns-check DOMAIN
Next Lesson — 077
Build the Shared CHP Core Library
We have reached the point where continuing to add standalone Bash scripts will create duplicated code.
The next lesson should therefore consolidate the platform into:
/etc/cresignsys/
├── config/
├── lib/
│ ├── common.sh
│ ├── domain.sh
│ ├── dns.sh
│ ├── nginx.sh
│ ├── php.sh
│ ├── mysql.sh
│ ├── wordpress.sh
│ ├── ssl.sh
│ ├── backup.sh
│ ├── logging.sh
│ └── lock.sh
├── templates/
│ ├── nginx/
│ ├── php/
│ └── suspended/
└── sites/
and:
/usr/local/bin/
├── hosting-create
├── hosting-list
├── hosting-info
├── hosting-health
├── hosting-site-status
├── hosting-repair
├── hosting-ssl
├── hosting-backup
├── hosting-restore
├── hosting-suspend
├── hosting-unsuspend
├── hosting-domain-add
├── hosting-domain-list
├── hosting-domain-remove
└── hosting-dns-check
The goal is to move from a collection of scripts to a real CresignSys Hosting Platform core with shared validation, locking, logging, configuration, state management, and reusable service functions.
Leave a Reply