Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HostDoctor

Health checks for hosts, servers and web services.

HTTP • TLS • TCP • Network • Performance • Security headers

HostDoctor diagnoses a host, server, URL or network service and returns one normalized report with a health score. It is an SDK you call from your own code — not a monitoring service. You ask, it checks right now, you get a structured answer.

$ hostdoctor check https://example.com

HostDoctor v1.0.0

Target         https://example.com
Status         warning
Score          85/100
Duration       414 ms

IPv4           ✓ 93.184.216.34
IPv6           · unavailable
TCP            ✓ 80 open (107 ms)   ✓ 443 open (109 ms)
HTTP           ✓ 200 (378 ms, HTTP/1.1)
TLS            ✓ TLSv1.3
Certificate    ✓ 77 day(s) remaining (Let's Encrypt)
Security       ⚠ 40/100 (missing: content_security_policy, referrer_policy)

Issues
  · [IPV6_UNAVAILABLE] example.com has no AAAA (IPv6) record.
  ⚠ [SECURITY_HEADER_MISSING] Content-Security-Policy header is missing.

Install

pip install hostdoctor

Python 3.11+. No runtime dependencies.

Use it from code

import hostdoctor

report = await hostdoctor.check("https://example.com")

print(report.status)   # "healthy" | "warning" | "degraded" | "critical"
print(report.score)    # 0-100
print(report.checks["tls"]["certificate"]["days_remaining"])
print(report.to_json())

Every method has a synchronous twin, so you do not need an event loop:

from hostdoctor import HostDoctor

doctor = HostDoctor(timeout=5, retries=2)
report = doctor.check_sync("https://example.com")

React to findings by code, never by message text — codes are stable across versions and across every HostDoctor language port:

for issue in report.issues:
    if issue.code == "TLS_EXPIRING_SOON":
        alert(f"certificate expires in {issue.data['days_remaining']} days")

The individual doctors

Each probe is also callable on its own, and each returns the same report envelope with only the checks it ran.

await doctor.http("https://api.example.com/health", expected_status=[200, 204])
await doctor.tls("example.com", 443)
await doctor.port("192.0.2.10", 22)
await doctor.ports("192.0.2.10", [22, 80, 443, 3306, 5432])
await doctor.network("example.com")
await doctor.security("https://example.com")
await doctor.redirects("http://example.com")

HTTP — status code, response time, HTTP version, final URL, content type, server header, and a per-phase timing breakdown (connect, TLS handshake, TTFB, total) so you can see where the latency is.

TLStrusted (the chain), hostname_match (the identity) and valid (both, inside the validity window) are reported separately, because they need different fixes. Certificate details are reported even when verification fails, so an expired or self-signed certificate still tells you its dates, issuer and SANs rather than just "invalid".

TCP — open/closed/timeout per port with connect latency. port() treats a closed port as critical (you asked about that specific service); ports() records it as a fact and leaves the score alone (you are surveying).

Network — A and AAAA records, IPv4/IPv6 availability, DNS timing.

Security headers — HSTS, CSP, X-Content-Type-Options, Referrer-Policy, Permissions-Policy and X-Frame-Options (a CSP frame-ancestors directive counts in place of the latter). This grades observable configuration. It is not a vulnerability scan, and a score of 100 is not a claim that the service is secure.

Redirects — the full chain with status codes, plus loop detection.

Safe mode (SSRF protection)

Safe mode is on by default. HostDoctor will not connect to loopback, link-local (including 169.254.169.254), private, CGNAT, multicast, reserved or documentation addresses.

It applies to every resolved address, not just the one it dials, and it is re-applied to every redirect hop — so a public URL that answers 302 → http://127.0.0.1:8080 is reported as REDIRECT_BLOCKED instead of being followed. Connections go to an address that has already been vetted, so DNS rebinding between the check and the connect does not get a second chance.

If you are checking your own infrastructure from inside it, turn it off deliberately:

HostDoctor(safe_mode=False)   # or: hostdoctor check ... --no-safe-mode

Related protections that are always on:

  • Response bodies are never downloaded — headers only, capped at 64 KiB.
  • Credential-bearing headers (Authorization, Cookie, X-API-Key, ...) are replaced with [REDACTED] before anything reaches a report or a log.
  • Those same headers are dropped when a redirect crosses origins, so a redirect cannot hand your bearer token to another host.

Health score

The score starts at 100 and issues deduct from it. Each category has a weight that also caps its total deduction, so a pile of missing security headers can never outweigh the site actually being down.

Category Weight
Connectivity 25
HTTP 20
TLS 20
Performance 15
Security headers 10
IPv6 5
Redirects 5
Score Status
90–100 healthy
75–89 warning
50–74 degraded
0–49 critical

Any critical issue forces critical status regardless of the arithmetic — an expired certificate is an outage even when everything else is perfect.

Weights are configurable, and overriding one scales its deductions with it:

HostDoctor(scoring={"tls": 30, "performance": 10})

Issue codes

Code Severity Meaning
DNS_RESOLUTION_FAILED critical No A or AAAA records
CONNECTION_TIMEOUT critical The port did not answer
CONNECTION_REFUSED critical The port actively refused
HTTP_UNREACHABLE critical No usable HTTP response
HTTP_UNEXPECTED_STATUS warning/critical Status outside the expected set (4xx warns, 5xx is critical)
HTTP_TOO_SLOW warning Slower than the threshold
REDIRECT_LOOP critical The chain returns to a visited URL
TOO_MANY_REDIRECTS warning Hit the redirect limit
REDIRECT_BLOCKED warning A redirect led somewhere safe mode forbids
TLS_UNAVAILABLE critical No TLS session could be established
TLS_INVALID critical The chain is untrusted, unparseable or not yet valid
TLS_EXPIRED critical Certificate is past its validity
TLS_EXPIRING_SOON warning Within the expiry window (default 14 days)
TLS_HOSTNAME_MISMATCH critical Valid chain, wrong name
IPV6_UNAVAILABLE info No AAAA records
SECURITY_HEADER_MISSING warning/info A recommended header is absent

CLI

hostdoctor check https://example.com
hostdoctor check https://api.example.com --expect-status 200,204
hostdoctor http https://example.com --json
hostdoctor tls example.com
hostdoctor port 192.0.2.10 22
hostdoctor ports 192.0.2.10 22,80,443,3306
hostdoctor network example.com
hostdoctor security https://example.com
hostdoctor redirects http://example.com

Exit codes make it usable as a CI gate:

Code Meaning
0 healthy
1 warning
2 degraded or critical
3 internal error (bad target, blocked by safe mode)
- name: Verify the deploy
  run: hostdoctor check https://api.example.com --expect-status 200

Useful flags: --json, --timeout, --retries, --no-safe-mode, --max-redirects, --tls-warn-days, --slow-ms, --ca-file.

Checking a service behind an internal CA:

hostdoctor check https://internal.corp --ca-file /etc/ssl/corp-ca.pem --no-safe-mode

Configuration

HostDoctor(
    timeout=5.0,                  # seconds per operation
    retries=1,                    # transient failures only, never TLS or policy errors
    safe_mode=True,               # SSRF protection
    max_redirects=10,
    tls_expiry_warning_days=14,
    slow_response_ms=2000.0,
    scoring={"tls": 30},          # category weight overrides
    headers={"Authorization": "Bearer ..."},   # redacted in the report
    user_agent="my-service/1.0",
    ca_file="/etc/ssl/corp-ca.pem",
)

Retries are deliberate, not blind: a timeout or connection error is retried, a failed certificate or a blocked target is not — retrying those only wastes time and produces the same answer.

Errors vs. findings

HostDoctor raises only for caller errors. Everything about the remote service — a refused port, an expired certificate, a 500 — is a finding inside the report, never an exception.

Exception When
TargetParseError The target string is not a host, host:port or URL
PolicyError Safe mode blocked the target you asked for
report = await hostdoctor.check("https://down.example.com")  # does not raise
assert report.status == "critical"

Cross-language contract

The report envelope is identical in every HostDoctor SDK. The contract lives in spec/:

  • spec/report.schema.json — the report envelope
  • spec/issues.json — codes, categories and default severities
  • spec/scoring.json — weights, deductions and the status ladder
{
  "version": "1.0",
  "target": {
    "input": "https://example.com",
    "type": "url",
    "host": "example.com",
    "scheme": "https",
    "port": 443
  },
  "status": "warning",
  "score": 85,
  "started_at": "2026-08-11T09:14:02Z",
  "duration_ms": 414,
  "checks": {
    "network": {}, "tcp": {}, "tls": {}, "http": {}, "security": {}, "performance": {}
  },
  "issues": [
    {
      "code": "SECURITY_HEADER_MISSING",
      "severity": "warning",
      "message": "Content-Security-Policy header is missing.",
      "check": "security",
      "data": { "header": "Content-Security-Policy", "penalty": 3 }
    }
  ],
  "metadata": {
    "engine": "hostdoctor-python",
    "engine_version": "1.0.0",
    "safe_mode": true
  }
}

The test suite asserts that this implementation and spec/ never drift apart, so a port in another language can be validated against the same files. The same contract already backs the PHP SDK (1gbitsofficial/hostdoctor-php).

Scope of v1.0

In: HTTP status and timing, redirect chains, TCP connectivity, TLS validity and expiry, IPv4/IPv6 resolution, security headers, health score, JSON report, CLI.

Not in v1: ping, traceroute, port scanning, vulnerability scanning, DNS record analysis, stored history and alerting. HTTP/2 and HTTP/3 are not negotiated; requests are made over HTTP/1.1.

License

MIT © 1Gbits

About

Python SDK and CLI for server health checks — HTTP, TLS, TCP, DNS, security and performance in one normalized report.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages