1gbits/hostdoctor

Health checks for hosts, servers and web services - HTTP, TLS, TCP, DNS, security headers and performance timing with a normalized report and a health score.

Maintainers

Package info

github.com/1gbitsofficial/hostdoctor-php

Homepage

pkg:composer/1gbits/hostdoctor

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-08-10 13:36 UTC

This package is not auto-updated.

Last update: 2026-08-11 11:55:20 UTC


README

Diagnose your server in one command.

HostDoctor is an SDK for checking the health of a host, server, URL or network service — HTTP, TLS, TCP, DNS, security headers and performance timing — condensed into one normalized report with a health score.

$ hostdoctor check https://example.com

HostDoctor

Target       https://example.com
Time         2026-08-10T12:00:00Z (684 ms)

Network      ✓  IPv4 203.0.113.10 | IPv6 available
TCP :443     ✓  open (34 ms)
TLS          ✓  TLSv1.3 | valid | 71 days remaining | Let's Encrypt
HTTP         ✓  200 | HTTP/2 | 182 ms | 1 redirect
Security     ⚠  4/6 headers present (score 70) | missing: content-security-policy
Timing       DNS 12 ms | TCP 34 ms | TLS 51 ms | TTFB 106 ms | Total 181 ms

Issues
  ⚠ SECURITY_HEADER_MISSING   Content-Security-Policy header is missing.

Score: 91/100
Status: Healthy

HTTP • TLS • TCP • Network • Performance • Security

Installation

composer require 1gbits/hostdoctor

Requires PHP 8.1+, ext-curl and ext-openssl.

Quick start

use OneGBits\HostDoctor\HostDoctor;

$doctor = new HostDoctor();
$report = $doctor->check('https://example.com');

$report->score;          // 91
$report->status->value;  // "healthy" | "warning" | "degraded" | "critical"
echo $report->toJson();  // normalized JSON report

Every method accepts a URL, hostname, IP, or host:port where it makes sense, and every method returns the same normalized Report.

$doctor->check('https://example.com');   // full pipeline
$doctor->check('192.0.2.10:22');         // network + TCP port
$doctor->check('example.com');           // network + port scan + HTTP/TLS if 80/443 open

$doctor->http('https://api.example.com/health', ['expected_status' => 200]);
$doctor->tls('example.com');             // certificate validity, expiry, hostname match
$doctor->port('192.0.2.10', 22);         // single TCP port (failure = issue)
$doctor->ports('192.0.2.10', [22, 80, 443, 3306]);  // parallel scan (facts, not failures)
$doctor->network('example.com');         // IPv4/IPv6 resolution
$doctor->redirects('http://example.com');// full redirect chain, loop detection
$doctor->security('https://example.com');// security response headers

Reading the report

$report = $doctor->check('https://example.com');

foreach ($report->issues as $issue) {
    // Codes are stable and machine-readable; messages are for humans.
    if ($issue->code === 'TLS_EXPIRING_SOON') {
        alertOps($issue->message);
    }
}

$report->check('tls')?->data['certificate']['days_remaining'];
$report->hasIssue('REDIRECT_LOOP');

The JSON structure is defined in spec/report.schema.json and is shared by every HostDoctor implementation:

{
  "version": "1.0",
  "target": { "input": "https://example.com", "type": "url", "host": "example.com", "scheme": "https", "port": null },
  "status": "healthy",
  "score": 91,
  "started_at": "2026-08-10T12:00:00Z",
  "duration_ms": 684,
  "checks": {
    "network": { "ipv4": ["203.0.113.10"], "ipv6": ["2001:db8::10"], "ipv4_available": true, "ipv6_available": true },
    "tcp": { "ports": { "443": { "status": "open", "connect_time_ms": 34.2 } } },
    "tls": { "enabled": true, "valid": true, "protocol": "TLSv1.3", "certificate": { "issuer": "Let's Encrypt", "days_remaining": 71 } },
    "http": { "status_code": 200, "protocol": "HTTP/2", "response_time_ms": 182.4, "redirects": { "count": 1, "loop": false } },
    "security": { "score": 70, "headers": { "content_security_policy": { "present": false } } },
    "performance": { "dns_ms": 12.1, "tcp_connect_ms": 34.2, "tls_handshake_ms": 51.0, "ttfb_ms": 106.3, "total_ms": 181.9 }
  },
  "issues": [
    { "code": "SECURITY_HEADER_MISSING", "severity": "warning", "message": "Content-Security-Policy header is missing.", "check": "security" }
  ],
  "metadata": { "engine": "hostdoctor-php", "engine_version": "1.0.0", "safe_mode": true }
}

CLI

hostdoctor check https://example.com
hostdoctor check https://example.com --json
hostdoctor tls example.com
hostdoctor port 203.0.113.10 22
hostdoctor ports 203.0.113.10 22,80,443,3306
hostdoctor redirects http://example.com
hostdoctor security https://example.com
hostdoctor network example.com

Exit codes are CI-friendly:

Code Meaning
0 Healthy
1 Warning
2 Degraded / Critical
3 Internal error / invalid usage
# e.g. verify a deployment in GitHub Actions
- name: Post-deploy health check
  run: vendor/bin/hostdoctor check https://api.example.com --expect=200 --timeout=10

Options

Pass options to the constructor (defaults for every call) or per call:

$doctor = new HostDoctor([
    'timeout' => 5.0,          // request timeout, seconds
    'connect_timeout' => 5.0,  // TCP connect timeout, seconds
    'retries' => 2,            // retries after timeouts only — a TLS failure is never retried
    'safe_mode' => true,       // SSRF guard (see below)
    'max_redirects' => 10,
    'max_body_bytes' => 1048576,      // response bodies are cut off after 1 MB
    'tls_expiry_warning_days' => 14,  // TLS_EXPIRING_SOON threshold
    'slow_response_ms' => 2000,       // HTTP_TOO_SLOW threshold
    'expected_status' => null,        // int or int[]; default: any status < 400
    'headers' => [],                  // extra request headers (redacted in reports)
    'method' => 'GET',                // or HEAD
    'verify_tls' => true,
    'user_agent' => null,             // default: HostDoctor/<version>
    'scoring' => [],                  // e.g. ['tls' => 30, 'performance' => 10]
    'ports' => [80, 443],             // ports scanned for bare-host checks
    'ca_file' => null,                // custom CA bundle
]);

$doctor->http($url, ['expected_status' => [200, 204], 'timeout' => 10.0]);

Health score

The score starts at 100. Every issue deducts points in its category, and each category's total deduction is capped at its weight:

Category Weight Example deductions
Connectivity 25 DNS failure −25, port refused −25
HTTP 20 unreachable −20, unexpected status −20
TLS 20 invalid/expired/mismatch −20, expiring soon −5
Performance 15 slow response −10
Security headers 10 CSP/HSTS −3 each, others −1
IPv6 5 no AAAA record −2
Redirects 5 loop −5, too many −5
Score Status
90–100 healthy
75–89 warning
50–74 degraded
0–49 critical

Weights are tunable — deductions scale with the weight:

new HostDoctor(['scoring' => ['tls' => 30, 'performance' => 10]]);

Issue codes

Issues carry stable machine-readable codes (full catalog in spec/issues.json):

DNS_RESOLUTION_FAILED · CONNECTION_TIMEOUT · CONNECTION_REFUSED · HTTP_UNREACHABLE · HTTP_UNEXPECTED_STATUS · HTTP_TOO_SLOW · REDIRECT_LOOP · TOO_MANY_REDIRECTS · REDIRECT_BLOCKED · TLS_UNAVAILABLE · TLS_INVALID · TLS_EXPIRED · TLS_EXPIRING_SOON · TLS_HOSTNAME_MISMATCH · IPV6_UNAVAILABLE · SECURITY_HEADER_MISSING

Safe mode (SSRF protection)

HostDoctor opens connections to caller-supplied targets. If those targets come from users (a "check my site" form, a public API), an attacker could point it at http://127.0.0.1 or http://169.254.169.254 to probe your internal network.

Safe mode is on by default and refuses to touch loopback, link-local, private/CGNAT, multicast and other special-purpose ranges — including targets that only become internal after DNS resolution or an HTTP redirect. In safe mode the vetted IP is pinned for the actual connection, so DNS rebinding between check and connect doesn't work either. Blocked targets throw a TargetBlockedException.

Checking your own infrastructure? Turn it off explicitly:

$doctor = new HostDoctor(['safe_mode' => false]);
hostdoctor port 10.0.0.5 22 --no-safe-mode

Two related guardrails are always on:

  • Response bodies are cut off after max_body_bytes (default 1 MB) — a health check never downloads a 10 GB file.
  • Authorization, Cookie and similar headers never appear in reports; they are replaced with [REDACTED].

What HostDoctor is (and isn't)

HostDoctor is a library, not a monitoring service: your application calls it, it runs the checks right now and hands back a structured result. No polling, no history, no alerting — those are things you can build with it.

The security check inspects observable configuration (response headers). A perfect security score means the headers are set — it is not a vulnerability scan and never a claim that a site is "secure".

Testing

composer install
composer test

Unit tests are network-free. The network PHPUnit group is reserved for integration tests and excluded by default.

Roadmap

  • 1.1 — batch checks, JSON body expectations for API endpoints, config file, CI mode, retry policies
  • 1.2 — probes for WebSocket, SSH banner, SMTP, MySQL, PostgreSQL, Redis
  • 2.0 — public plugin system ($doctor->use(new RedisProbe()))

The report schema, issue codes and scoring model live in spec/ and are the contract for upcoming implementations in other languages.

License

MIT