Skip to content

Self-hosting

Monitoring Beta

Health checks for every component, reading logs, the figures available in v1 and the alerts worth setting.

This page describes a beta feature: it works but may still change.

On this page

Health checks#

ComponentCheckHealthy whenNotes
o3o-proxy + o3o-gateGET /o3o/healthz200 {"ok": true}Does not depend on the editing server.
Gate is countingGET /o3o/statusupstream.coolwsd = "ok" and connections.enforcing = truedown: unreachable; auth_failed: wrong admin password.
Editing serverGET /hosting/discovery200, XML bodyThe container also has the image's built-in health check.
o3o-docbuilderGET /v1/status200, live workers (idle + busy > 0)No authentication.
o3o-nextcloudGET /status.php (8081)"installed": truenextcloud profile only.
GET/o3o/healthz

The gate process is alive. For Docker and monitoring tools.

Auth: noneCommunityEnterprise
GET/v1/status

DocBuilder status: version, document engine, workers, queue. No authentication.

Auth: noneCommunityEnterprise
200Example; the figures are illustrative.
{
  "service": "o3o-docbuilder",
  "version": "1.0.0",
  "api": "v1",
  "time": "2026-09-21T10:00:00Z",
  "edition": "community",
  "dev_mode": false,
  "core": {
    "name": "LibreOffice",
    "version": "7.4.7.2",
    "source": "debian-bookworm"
  },
  "workers": {
    "total": 2,
    "idle": 2,
    "busy": 0,
    "restarts": 0
  },
  "queue": {
    "queued": 0,
    "running": 0
  },
  "uptime_seconds": 3600
}
BashQuick check of the whole stack
#!/usr/bin/env bash
# Quick O3O Office Online check; non-zero exit when something fails
BASE="${BASE:-http://localhost:8080}"
fail=0
ok()  { echo "OK    $1"; }
bad() { echo "FAIL $1"; fail=1; }

curl -fsS "$BASE/o3o/healthz" >/dev/null && ok "proxy + gate" || bad "proxy + gate"
curl -fsS "$BASE/hosting/discovery" >/dev/null && ok "editing server" || bad "editing server"
curl -fsS "$BASE/o3o/status" \
  | jq -e '.upstream.coolwsd == "ok" and .connections.enforcing' >/dev/null \
  && ok "gate is counting connections" || bad "gate is counting connections"
curl -fsS "$BASE/v1/status" \
  | jq -e '.workers.idle + .workers.busy > 0' >/dev/null \
  && ok "DocBuilder" || bad "DocBuilder"
exit "$fail"

Logs#

  • The gate and DocBuilder log to stdout, one JSON line per event; the level is set by O3O_LOG_LEVEL.
  • Logs never contain document content, tokens or API keys.
  • Every response carries X-O3O-Request-Id; use it to find the log lines of one request.
  • The editing server image has no shell: read its logs with docker compose logs; docker exec is not possible.
  • Cap log size with Docker's logging options (for example the local driver with max-size and max-file).
BashReading logs
cd online
DC="docker compose --env-file .env -f docker/compose.dev.yml"

$DC logs -f --since 10m o3o-gate o3o-docbuilder     # follow the last 10 minutes
$DC logs o3o-online | tail -200                     # editing server
$DC logs o3o-gate o3o-docbuilder | grep req_0123456789abcdef   # search by request id

Figures available in v1#

SourceFigures
GET /o3o/statusconnections: current, pending, peak_5m, limit, readonly, views_total, documents, limit_reached, enforcing, sample_age_seconds; license: state, grace_days_left; embed: open_documents, sessions
GET /v1/statusworkers: total, idle, busy, restarts; queue: queued, running; uptime_seconds
GET /v1/limitsUsage for the minute and the day, running and queued jobs
usage-YYYY-MM.csvConnection peak every 5 minutes, see How connections are counted

Suggested alerts#

ConditionMeaningAction
connections.enforcing = false for a whileThe gate cannot count and lets everything through.Check upstream.coolwsd and the admin password.
upstream.coolwsd = "auth_failed"O3O_COOLWSD_ADMIN_PASSWORD does not match the editing server.Fix .env and recreate both services.
limit_reached = true often, or peak_5m close to limitNot enough connections at peak time.Reconcile with the CSV log and consider buying more; see Licensing.
license.state = "grace"The annual token expired, grace_days_left days remain.Renew and load the new token.
workers.restarts rising fastBroken files or repeated job timeouts.Check the o3o-docbuilder logs.
queue.queued close to max_queued_jobsAbout to return 503 queue_full.Spread the load, or raise O3O_DOCBUILDER_WORKERS in the enterprise edition.
embed.callback_signing = falseCallbacks are sent unsigned.Set O3O_EMBED_CALLBACK_SECRET.
PythonChecking alerts in Python
# pip install requests
import requests

BASE = "http://localhost:8080"


def alerts() -> list[str]:
    out = []
    s = requests.get(f"{BASE}/o3o/status", timeout=10).json()
    c, lic = s["connections"], s["license"]
    if not c["enforcing"]:
        out.append("Counter is failing open")
    if s["upstream"]["coolwsd"] != "ok":
        out.append("Editing server: " + s["upstream"]["coolwsd"])
    if c["limit_reached"]:
        out.append(f"Connection cap reached {c['current']}/{c['limit']}")
    if lic["state"] == "grace":
        out.append(f"Token in grace period, days left: {lic['grace_days_left']}")
    if not s["embed"]["callback_signing"]:
        out.append("Callbacks are unsigned")
    d = requests.get(f"{BASE}/v1/status", timeout=10).json()
    if d["workers"]["idle"] + d["workers"]["busy"] == 0:
        out.append("DocBuilder has no live worker")
    return out


print("\n".join(alerts()) or "OK")
Coming soon

A Prometheus-format metrics endpoint, a 30-day connection P95 and a usage chart page.