{"generated_at":"2026-10-31T18:54:45.477066+00:00","node":{"api_address":"127.0.0.1:9997","api_ok":true,"api_status":200,"api_error":null,"api_latency_ms":0.9,"version":"v1.20.1"},"config":{"known":true,"auth_method":"jwt","jwks_url":"http://backend-ingest.tpulse.svc.cluster.local:8000/api/v1/ingest/jwks.json","jwt_in_query":false,"webhook_url":"","internal_users":2,"listeners":{"srt":{"enabled":false,"address":":8890","transport":"udp"},"rtmp":{"enabled":true,"address":":1935","transport":"tcp"},"hls":{"enabled":true,"address":":8888","transport":"tcp","variant":"lowLatency","segment_count":7,"segment_duration":"1s"},"webrtc":{"enabled":false,"address":":8889","transport":"tcp","udp_address":":8189","additional_hosts":[]},"rtsp":{"enabled":true,"address":":8554","transport":"tcp"},"api":{"enabled":true,"address":":9997","transport":"tcp"},"metrics":{"enabled":true,"address":":9998","transport":"tcp"},"moq":{"enabled":false,"address":"","transport":"quic"},"pprof":{"enabled":false,"address":":9999","transport":"tcp"}}},"backend":{"base":"http://backend-ingest.tpulse.svc.cluster.local:8000","source":"jwks","healthz":{"url":"http://backend-ingest.tpulse.svc.cluster.local:8000/healthz","ok":true,"status":200,"error":null,"latency_ms":5.9},"jwks":{"url":"http://backend-ingest.tpulse.svc.cluster.local:8000/api/v1/ingest/jwks.json","ok":true,"status":200,"error":null,"latency_ms":4.6,"keys":1,"kids":["ba4013006e620891"]},"webhook":null},"metrics":{"address":"127.0.0.1:9998","ok":true,"status":200,"error":null,"latency_ms":0.8,"series":53},"paths":[],"publishers":[],"readers":[],"bridges":[],"host":{"cpu":{"cores":2,"load1":0.09,"load5":0.12,"load15":0.09,"total":1297720658,"idle":1257723170},"memory":{"total_kb":7966652,"available_kb":6736804,"used_kb":1229848,"used_pct":15.4},"disks":[{"path":"/tmp","total_bytes":107294470144,"used_bytes":7971557376,"used_pct":7.4},{"path":"/","total_bytes":107294470144,"used_bytes":7971557376,"used_pct":7.4}],"temperatures":[],"gpus":[],"udp_buffers":{"rmem_max":4194304,"wmem_max":4194304,"target":26214400},"process":{"pid":7,"name":"mediamtx","rss_kb":51124,"threads":20,"fds":16,"fd_limit":999999,"cpu_jiffies":2021918},"uptime_s":6521181.0,"kernel":"6.18.39-79.141.amzn2023.x86_64","os":"Ubuntu 24.04.4 LTS"},"totals":{"paths":0,"ready":0,"publishers":0,"transcodes":0,"readers":0,"viewer_connections":0,"bridges":0,"bytes_in":0,"bytes_out":0,"bytes_fed":0},"wall_rendition":{"width":640,"height":360,"fps":12,"kbps":400},"checks":[{"id":"api","level":"ok","title":"MediaMTX control API answering","detail":"0.9 ms on 127.0.0.1:9997 \u00b7 v1.20.1","where":"media node","steps":[]},{"id":"auth_mode","level":"ok","title":"Publish and playback authorized by backend-minted JWTs","detail":"authMethod: jwt \u2014 the node verifies every token itself against the backend's JWKS; the backend is never called per segment.","where":"media node","steps":[]},{"id":"backend","level":"ok","title":"Backend reachable","detail":"http://backend-ingest.tpulse.svc.cluster.local:8000/healthz \u2192 200 in 5.9 ms (found via jwks).","where":"backend","steps":[]},{"id":"jwks","level":"ok","title":"Signing keys available (1 key)","detail":"http://backend-ingest.tpulse.svc.cluster.local:8000/api/v1/ingest/jwks.json \u2192 200 in 4.6 ms \u00b7 kid ba4013006e620891","where":"backend","steps":[]},{"id":"jwt_transport","level":"info","title":"Tokens are accepted in the Authorization header only","detail":"authJWTInHTTPQuery is off, so a bare playlist URL in a browser's address bar is refused with 401 even with a valid token; a player must send 'Authorization: Bearer <token>' on every request.","where":"media node config","steps":["This is the intended posture: a token in a URL lands in proxy logs and browser history.","To allow ?jwt=<token> for ad-hoc players, set authJWTInHTTPQuery: yes on the node."]},{"id":"ingest","level":"info","title":"Ingest listeners: RTMP :1935/tcp","detail":"SRT is off: a bridge whose PUBLISH_PROTOCOL is srt will fail to connect and retry forever.","where":"media node config","steps":["The firewall/NLB in front of this node must carry the same ports: 8890/udp for SRT, 1935/tcp for RTMP."]},{"id":"delivery","level":"ok","title":"Delivery listeners up","detail":"HLS :8888 (lowLatency, 7 \u00d7 1s) \u00b7 RTSP readers :8554","where":"media node config","steps":[]},{"id":"publishers","level":"info","title":"No stream bridge is publishing","detail":"Nothing is arriving on SRT or RTMP right now. In on-demand mode that is normal until somebody watches a camera.","where":"stream bridge","steps":["A publish starts only when a viewer opens the camera (POST /api/v1/ingest/cameras/<id>/watch/); the backend then sends camera.start to the site's bridge.","On the bridge host: docker logs -f tpulse-bridge \u2014 expect 'Authenticated with cloud', then 'Publishing camera=\u2026', then 'Stream ONLINE'.","'NetStream.Publish.Unauthorized' in that log = the bridge presented a token this node refuses: it needs the per-camera publish_jwt from camera.start (bridge \u2265 f862dcd) and a fresh watch after restarting.","'Failed to connect' = network: does the ingest hostname resolve to the RTMP NLB, and is 1935/tcp (or 8890/udp for SRT) open from the plant?","No 'Authenticated with cloud' at all = the control channel: check SITE_ID/BRIDGE_ID/BRIDGE_TOKEN in the bridge's .env and the close code it logs (4401 rejected, 4403 revoked).","Is the camera assigned to THIS bridge? An unassigned camera streams nowhere by design (PUT /api/v1/ingest/cameras/<id>/assignment/)."]},{"id":"api_exposure","level":"info","title":"Control API is bound off loopback","detail":"apiAddress :9997: the API can add and delete paths without credentials, so it must stay off every Service and Ingress. (This page reads it on loopback.)","where":"media node config / network","steps":["Keep port 9997 out of the Service; a NetworkPolicy is what should close it in-cluster."]},{"id":"metrics","level":"ok","title":"Prometheus metrics served","detail":"127.0.0.1:9998 \u2192 53 series in 0.8 ms","where":"media node","steps":[]},{"id":"host_cpu","level":"ok","title":"CPU load 0.09 across 2 cores (4% of capacity)","detail":"1-minute load average. MediaMTX copies packets, so CPU tracks stream count; a sustained load near the core count is a node at its stream ceiling.","where":"media node host","steps":[]},{"id":"host_memory","level":"ok","title":"Memory 15% used","detail":"1.2 GiB of 7.6 GiB in use on the node.","where":"media node host","steps":[]},{"id":"host_disk:/tmp","level":"ok","title":"Disk /tmp 7% used","detail":"7.4 GiB of 99.9 GiB. HLS muxing writes its segments here and the root filesystem is read-only, so a full /tmp stalls every stream with no error.","where":"media node host","steps":[]},{"id":"host_disk:/","level":"ok","title":"Disk / 7% used","detail":"7.4 GiB of 99.9 GiB.","where":"media node host","steps":[]},{"id":"srt_buffers","level":"info","title":"SRT kernel buffers small, which does not matter while SRT is off","detail":"net.core.rmem_max=4 MiB, wmem_max=4 MiB; the media node bootstrap and the Helm chart set 25 MiB and call it not optional for SRT. SRT is off on this node, so nothing uses them today.","where":"media node host (kernel)","steps":[]},{"id":"temperature","level":"info","title":"No temperature sensors on this host","detail":"This is normal on a virtualised cloud instance, which exposes no thermal sensor to the guest. A bare-metal or on-prem node reports its sensors here.","where":"media node host (hardware)","steps":[]},{"id":"gpu","level":"info","title":"No GPU on this host","detail":"The media node relays already-encoded packets; the only transcoding it does is the small wall rendition per watched camera, on CPU with GStreamer, which needs no GPU at the wall sizes a site watches. A GPU appears here on a node built for heavier transcoding.","where":"media node host (hardware)","steps":[]},{"id":"server_process","level":"ok","title":"MediaMTX process: 49.9 MiB RSS, 20 threads","detail":"pid 7, 16 of 999999 file descriptors open. 100+ streams pass the default 1024 descriptors, which is why the unit sets LimitNOFILE=65535.","where":"media node","steps":[]}],"overall":"ok"}