Files
ngx_http_monitoring_module/skills/ngx-http-monitoring-client/SKILL.md

13 KiB

name, description
name description
ngx-http-monitoring-client Use this skill when connecting clients or agents to an ngx_http_monitoring_module service, querying its JSON/SSE/Prometheus endpoints, reading API response structures, debugging access problems, or integrating dashboards/health checks. Supports deployments protected by Nginx Basic Auth and/or monitor_api_token.

ngx_http_monitoring_module Client

Use this skill to consume a deployed ngx_http_monitoring_module service from scripts, agents, dashboards, health checks, CI, or observability clients.

Inputs To Determine

Before calling the service, identify:

  • base_url: root URL that serves /monitor, for example http://127.0.0.1:8080
  • optional API token: sent as X-Monitor-Token, Authorization: Bearer, or ?token=...
  • optional Nginx Basic Auth credentials: username:password
  • target endpoint: dashboard, JSON API, SSE, Prometheus, or health

Never invent credentials. Ask the user, read documented deployment config, or use provided environment variables.

Recommended environment variables:

MONITOR_BASE_URL=http://127.0.0.1:8080
MONITOR_TOKEN=change-me
MONITOR_BASIC_AUTH=user:password

Endpoints

  • GET /monitor - embedded HTML dashboard
  • GET /monitor/api - full JSON metrics
  • GET /monitor/api/system - CPU, load, memory, swap, uptime
  • GET /monitor/api/nginx - Nginx connection/request/worker metrics
  • GET /monitor/api/network - network interfaces and counters
  • GET /monitor/api/disk - disk and filesystem metrics
  • GET /monitor/api/processes - process, TCP, socket, and worker stats
  • GET /monitor/api/upstreams - observed upstream/backend stats
  • GET /monitor/api/connections - connection/SSE/keepalive counters
  • GET /monitor/api/requests - request counters, histograms, percentiles, top URLs
  • GET /monitor/live - Server-Sent Events stream
  • GET /monitor/metrics - Prometheus text format
  • GET /monitor/health - lightweight JSON health check

API Structures

All JSON API responses are compact, versioned, and timestamped. Treat unknown fields as forward-compatible additions, and treat missing sections as disabled collectors, unsupported Nginx build options, or unavailable Linux /proc data.

Common envelope:

{
  "version": 1,
  "module": "1.0.0",
  "timestamp": 1710000000,
  "msec": 123,
  "scope": "full",
  "pid": 12345
}

Endpoint-specific API routes such as /monitor/api/system return the common envelope plus one top-level field matching the route. The examples below show that section's value unless explicitly shown as a complete response.

GET /monitor/api returns the envelope plus:

{
  "system": {},
  "nginx": {},
  "network": {},
  "disk": {},
  "processes": {},
  "upstreams": [],
  "connections": {},
  "requests": {},
  "history": []
}

GET /monitor/api/system:

{
  "cpu": {
    "usage": 12.5,
    "cores": 8,
    "load": [0.12, 0.20, 0.30]
  },
  "memory": {
    "total": 16777216,
    "available": 8388608,
    "free": 4194304,
    "used": 8388608,
    "used_pct": 50.0
  },
  "swap": {
    "total": 2097152,
    "free": 1048576,
    "used_pct": 50.0
  },
  "uptime": 123456
}

GET /monitor/api/nginx:

{
  "connections": {
    "accepted": 1000,
    "handled": 1000,
    "active": 20,
    "reading": 1,
    "writing": 4,
    "waiting": 15
  },
  "requests": {
    "total": 50000,
    "responses": 49990,
    "requests_per_sec": 125.5,
    "responses_per_sec": 125.3
  },
  "ssl": {
    "requests": 100,
    "handshakes": 20
  },
  "keepalive": {
    "requests": 250
  },
  "workers": []
}

GET /monitor/api/network:

{
  "rx_bytes": 123456789,
  "tx_bytes": 987654321,
  "rx_packets": 10000,
  "tx_packets": 12000,
  "rx_errors": 0,
  "tx_errors": 0,
  "interfaces": [
    {
      "name": "eth0",
      "rx_bytes": 123456789,
      "tx_bytes": 987654321,
      "rx_packets": 10000,
      "tx_packets": 12000,
      "rx_errors": 0,
      "tx_errors": 0,
      "up": true
    }
  ]
}

GET /monitor/api/disk:

{
  "reads": 1000,
  "writes": 2000,
  "read_bytes": 4096000,
  "write_bytes": 8192000,
  "devices": [
    {
      "name": "sda",
      "reads": 1000,
      "writes": 2000,
      "read_bytes": 4096000,
      "write_bytes": 8192000,
      "io_ms": 120
    }
  ],
  "filesystems": [
    {
      "path": "/",
      "type": "ext4",
      "total": 107374182400,
      "used": 53687091200,
      "free": 53687091200,
      "avail": 53687091200,
      "files": 1000000,
      "files_free": 750000
    }
  ]
}

GET /monitor/api/processes:

{
  "process_count": 140,
  "tcp": {
    "established": 20,
    "listen": 8
  },
  "sockets": {
    "used": 512,
    "tcp": 120,
    "udp": 16
  },
  "workers": []
}

GET /monitor/api/connections:

{
  "accepted": 1000,
  "handled": 1000,
  "active": 20,
  "reading": 1,
  "writing": 4,
  "waiting": 15,
  "ssl_requests": 100,
  "ssl_handshakes": 20,
  "keepalive_requests": 250,
  "sse_clients": 2,
  "sse_events": 500,
  "rate_limited": 0
}

GET /monitor/api/requests:

{
  "total": 50000,
  "responses": 49990,
  "bytes": 104857600,
  "requests_per_sec": 125.5,
  "responses_per_sec": 125.3,
  "error_rate": 0.0078,
  "latency": {
    "avg": 8.4,
    "p50": 4,
    "p90": 15,
    "p95": 22,
    "p99": 80
  },
  "status": {
    "1xx": 0,
    "2xx": 49000,
    "3xx": 600,
    "4xx": 300,
    "5xx": 90
  },
  "methods": {
    "GET": 45000,
    "POST": 4000,
    "PUT": 100,
    "DELETE": 50,
    "HEAD": 500,
    "OPTIONS": 200,
    "PATCH": 50,
    "OTHER": 100
  },
  "latency_histogram": [
    {
      "le": 1,
      "count": 100
    },
    {
      "le": "+Inf",
      "count": 50000
    }
  ],
  "size_histogram": [
    {
      "le": 512,
      "count": 10000
    },
    {
      "le": "+Inf",
      "count": 50000
    }
  ],
  "top_urls": [],
  "user_agents": []
}

GET /monitor/api/upstreams:

[
  {
    "peer": "127.0.0.1:9000",
    "requests": 1000,
    "failures": 2,
    "status_4xx": 10,
    "status_5xx": 2,
    "avg_latency": 12.5,
    "last_seen": 1710000000
  }
]

History samples in GET /monitor/api:

[
  {
    "timestamp": 1710000000,
    "cpu": 12.5,
    "memory": 50.0,
    "swap": 0.0,
    "rps": 125.5,
    "responses_per_sec": 125.3,
    "network_rx_per_sec": 1024,
    "network_tx_per_sec": 2048,
    "disk_read_per_sec": 0,
    "disk_write_per_sec": 4096,
    "latency_p95": 22,
    "requests_total": 50000,
    "status_4xx": 300,
    "status_5xx": 90
  }
]

Shared nested structures:

{
  "worker": {
    "slot": 0,
    "pid": 12345,
    "active": true,
    "requests": 10000,
    "bytes": 10485760,
    "errors": 3,
    "vm_size": 102400,
    "vm_rss": 20480,
    "voluntary_ctxt": 100,
    "nonvoluntary_ctxt": 5,
    "last_seen": 1710000000
  },
  "top_url": {
    "url": "/api/orders",
    "hits": 1000,
    "errors": 3,
    "bytes": 1048576,
    "avg_latency": 7.8,
    "last_seen": 1710000000
  },
  "user_agent": {
    "user_agent": "curl/8.0.1",
    "hits": 100,
    "errors": 0,
    "bytes": 20480,
    "avg_latency": 2.1,
    "last_seen": 1710000000
  }
}

SSE GET /monitor/live sends metrics events whose data payload has this structure:

{
  "version": 1,
  "timestamp": 1710000000,
  "sequence": 42,
  "system": {
    "cpu": {
      "usage": 12.5,
      "cores": 8
    },
    "memory": {
      "used_pct": 50.0,
      "total": 16777216,
      "available": 8388608
    }
  },
  "requests": {
    "total": 50000,
    "requests_per_sec": 125.5,
    "responses_per_sec": 125.3,
    "latency": {
      "p95": 22,
      "p99": 80
    },
    "status": {
      "4xx": 300,
      "5xx": 90
    }
  },
  "connections": {
    "active": 20,
    "reading": 1,
    "writing": 4,
    "waiting": 15,
    "sse_clients": 2,
    "keepalive_requests": 250
  },
  "network": {
    "rx_bytes": 123456789,
    "tx_bytes": 987654321
  },
  "disk": {
    "read_bytes": 4096000,
    "write_bytes": 8192000
  }
}

GET /monitor/metrics exposes Prometheus text metrics including:

nginx_monitor_requests_total
nginx_monitor_active_connections
nginx_monitor_cpu_usage_ratio
nginx_monitor_memory_used_ratio
nginx_monitor_latency_p95_ms

GET /monitor/health returns a complete health response:

{
  "version": 1,
  "module": "1.0.0",
  "timestamp": 1710000000,
  "msec": 123,
  "scope": "health",
  "pid": 12345,
  "status": "ok",
  "generation": 100,
  "sse_clients": 2
}

Auth Rules

If the service is behind Nginx Basic Auth, include Basic Auth on every request, including /monitor, /monitor/live, and /monitor/metrics.

If monitor_api_token is configured, include the token on non-dashboard endpoints. Prefer a header over query string unless the client cannot set headers.

Preferred headers:

-H "X-Monitor-Token: $MONITOR_TOKEN"

Alternative bearer header:

-H "Authorization: Bearer $MONITOR_TOKEN"

Basic Auth with curl:

-u "$MONITOR_BASIC_AUTH"

When both Basic Auth and API token are enabled, send both.

Do not print secrets in final answers, logs, or copied command output. Redact with <redacted> when summarizing.

Curl Patterns

Build arguments conditionally:

base="${MONITOR_BASE_URL:-http://127.0.0.1:8080}"
auth_args=""
token_args=""

if [ "${MONITOR_BASIC_AUTH:-}" != "" ]; then
  auth_args="-u $MONITOR_BASIC_AUTH"
fi

if [ "${MONITOR_TOKEN:-}" != "" ]; then
  token_args="-H X-Monitor-Token: $MONITOR_TOKEN"
fi

For exact shell execution, prefer explicit commands instead of string-expanded credentials when possible:

curl -fsS -u "$MONITOR_BASIC_AUTH" -H "X-Monitor-Token: $MONITOR_TOKEN" \
  "$MONITOR_BASE_URL/monitor/api"

Without token:

curl -fsS -u "$MONITOR_BASIC_AUTH" "$MONITOR_BASE_URL/monitor/api"

Without Basic Auth:

curl -fsS -H "X-Monitor-Token: $MONITOR_TOKEN" "$MONITOR_BASE_URL/monitor/api"

Health check:

curl -fsS -u "$MONITOR_BASIC_AUTH" -H "X-Monitor-Token: $MONITOR_TOKEN" \
  "$MONITOR_BASE_URL/monitor/health"

Prometheus:

curl -fsS -u "$MONITOR_BASIC_AUTH" -H "X-Monitor-Token: $MONITOR_TOKEN" \
  "$MONITOR_BASE_URL/monitor/metrics"

SSE sample:

curl -fsS -N -u "$MONITOR_BASIC_AUTH" -H "X-Monitor-Token: $MONITOR_TOKEN" \
  "$MONITOR_BASE_URL/monitor/live"

Browser And Dashboard

Open:

<base_url>/monitor

If the API token is required and the browser cannot set headers for EventSource, append the token:

<base_url>/monitor?token=<token>

The dashboard forwards the token query parameter to API and SSE calls. Basic Auth is handled by the browser after the Nginx auth challenge.

JavaScript Client

Use headers for JSON polling:

const baseUrl = process.env.MONITOR_BASE_URL ?? "http://127.0.0.1:8080";
const token = process.env.MONITOR_TOKEN;

const headers = token ? { "X-Monitor-Token": token } : {};
const res = await fetch(`${baseUrl}/monitor/api`, { headers });
if (!res.ok) throw new Error(`monitor api failed: ${res.status}`);
const metrics = await res.json();

For browser EventSource, headers are not available. Use query token if needed:

const url = new URL("/monitor/live", baseUrl);
if (token) url.searchParams.set("token", token);
const events = new EventSource(url);
events.addEventListener("metrics", (event) => {
  const metrics = JSON.parse(event.data);
});

If Basic Auth is required in browser clients, rely on the browser's authenticated session. Do not embed user:password in URLs.

Interpreting Responses

Every JSON response includes:

  • version
  • module
  • timestamp
  • scope

Common fields:

  • CPU usage: system.cpu.usage
  • memory used percent: system.memory.used_pct
  • request rate: requests.requests_per_sec or nginx.requests.requests_per_sec
  • latency: requests.latency.p50, p90, p95, p99
  • errors: requests.status["4xx"], requests.status["5xx"]
  • active connections: connections.active or nginx.connections.active

Treat missing fields as disabled collectors, unsupported Nginx build options, or insufficient traffic. For exact active/reading/writing/waiting connection counters, the Nginx build must include --with-http_stub_status_module.

Troubleshooting

  • 401: Basic Auth or API token is missing or wrong. If both are configured, send both.
  • 403: monitor_allow / monitor_deny blocked the client IP.
  • 404: monitor on, monitor_api on, or monitor_sse on may not be enabled in the matching location.
  • 429: monitor_rate_limit was exceeded.
  • Empty connection counters: Nginx may not have been built with stub status support.
  • SSE connects then drops: proxy buffering or idle timeout may be in front of the service. Set proxy_buffering off and allow long-lived responses.

Security Defaults

When integrating with another client:

  • prefer HTTPS
  • prefer header token over query token
  • avoid logging headers, URLs containing token=, or Basic Auth strings
  • use least-privilege network ACLs in front of /monitor
  • avoid embedding Basic Auth credentials in browser source