Operations: probes, metrics, logs, exit codes

Production knobs the CLI exposes for sidecar deployments (Kubernetes, Docker Compose, systemd, CI). Available in CLI v2.3.0+.

--ops-port: /healthz, /readyz, /metrics

Pass --ops-port 9091 (or set NGSRV_OPS_PORT) to start a tiny HTTP server next to the tunnel. Wire its endpoints into Kubernetes probes and Prometheus scraping.

EndpointPurposeUse as
GET /healthzProcess is alive and listener is up. Always 200 once the ops server has bound.livenessProbe
GET /readyz200 only when the tunnel has connected and registered. 503 with not_connected or connected_not_registered otherwise.readinessProbe
GET /metricsPrometheus text exposition: ngsrv_up, ngsrv_connected, ngsrv_requests_total, ngsrv_bytes_in_total, ngsrv_bytes_out_total, ngsrv_reconnects_total, ngsrv_uptime_seconds.scrape target
GET /JSON status snapshot — handy for curl / kubectl exec.manual debug
# Local example: tunnel + ops on :9091
$ ngsrv http 3000 --no-tui --ops-port 9091 --log-format json &

$ curl -s localhost:9091/healthz   # → ok
$ curl -s localhost:9091/readyz    # → ready (or "not_connected" while warming up)
$ curl -s localhost:9091/metrics | head
# HELP ngsrv_up 1 when the tunnel is connected and registered, 0 otherwise.
# TYPE ngsrv_up gauge
ngsrv_up 1
# HELP ngsrv_connected 1 when the WebSocket to the tunnel server is open.
ngsrv_connected 1
ngsrv_requests_total 14
ngsrv_bytes_in_total 982142
ngsrv_uptime_seconds 612.4

--log-format and --log-level

The CLI emits structured logs through log/slog. JSON is the right pick whenever a log scraper is reading stdout (Loki, Datadog, Cloud Logging, ELK, Vector, etc.). The default is text for interactive shells.

# JSON logs to stderr, INFO and above
ngsrv run --log-format json --log-level info

# Human-readable text logs, only warnings / errors
ngsrv http 3000 --log-format text --log-level warn

# Suppress info chatter, keep errors
ngsrv http 3000 --quiet            # equivalent to --log-level warn

Logs always go to stderr so commands that print URLs or tokens to stdout (e.g. for shell piping) stay clean. Setting --log-format json also auto-disables the TUI; the same happens when stdout is not a terminal (CI, k8s, systemd).

ngsrv run: many tunnels in one process

For sidecar deployments, declare every tunnel in one ngsrv.yml and run the whole bundle with ngsrv run. The ops server, logger, and signal handling are shared across all tunnels.

# ngsrv.yml
tunnels:
  - name: web
    port: 3000
    subdomain: web
  - name: api
    port: 8080
    subdomain: api
    security_policies:
      - ngsrv_ips_office
  - name: grpc
    port: 9000
    subdomain: grpc

# Run them all from one process.
$ ngsrv run --config ngsrv.yml --log-format json --ops-port 9091

/readyz reports 200 only when every declared tunnel is connected and registered. If any tunnel exits, the whole process exits so the supervisor (k8s, systemd) restarts it. See the config reference for the full schema.

Exit code contract

The CLI returns stable exit codes so CI gates, supervisors, and shell scripts can react to why the process exited rather than to a generic non-zero. These codes are part of the public surface and will not be repurposed.

CodeMeaningWhen you'll see it
0OK / clean shutdownOne-shot command succeeded, or SIGTERM received during a graceful drain.
64UsageUnknown command, missing required flag, malformed argument.
65AuthNo token, malformed token, expired or rejected token, plan does not allow the requested feature.
66Configngsrv.yml missing, unreadable, or fails schema validation.
69UpstreamLocal upstream service did not respond after the retry budget — the thing you're tunneling to is down.
70ServerThe NGSRV tunnel server / edge router returned an unrecoverable error.
71NetworkTransport-level failure to the tunnel server (DNS, TLS, TCP) that exhausted the reconnect budget.
99InternalCatch-all for unexpected internal errors (bugs). Please file an issue if you see one.