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.
Health checks#
| Component | Check | Healthy when | Notes |
|---|---|---|---|
| o3o-proxy + o3o-gate | GET /o3o/healthz | 200 {"ok": true} | Does not depend on the editing server. |
| Gate is counting | GET /o3o/status | upstream.coolwsd = "ok" and connections.enforcing = true | down: unreachable; auth_failed: wrong admin password. |
| Editing server | GET /hosting/discovery | 200, XML body | The container also has the image's built-in health check. |
| o3o-docbuilder | GET /v1/status | 200, live workers (idle + busy > 0) | No authentication. |
| o3o-nextcloud | GET /status.php (8081) | "installed": true | nextcloud profile only. |
GET
/o3o/healthzThe gate process is alive. For Docker and monitoring tools.
GET
/v1/statusDocBuilder status: version, document engine, workers, queue. No authentication.
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
}#!/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 execis not possible. - Cap log size with Docker's
loggingoptions (for example thelocaldriver withmax-sizeandmax-file).
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 idFigures available in v1#
| Source | Figures |
|---|---|
GET /o3o/status | connections: 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/status | workers: total, idle, busy, restarts; queue: queued, running; uptime_seconds |
GET /v1/limits | Usage for the minute and the day, running and queued jobs |
usage-YYYY-MM.csv | Connection peak every 5 minutes, see How connections are counted |
Suggested alerts#
| Condition | Meaning | Action |
|---|---|---|
connections.enforcing = false for a while | The 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 limit | Not 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 fast | Broken files or repeated job timeouts. | Check the o3o-docbuilder logs. |
queue.queued close to max_queued_jobs | About to return 503 queue_full. | Spread the load, or raise O3O_DOCBUILDER_WORKERS in the enterprise edition. |
embed.callback_signing = false | Callbacks are sent unsigned. | Set O3O_EMBED_CALLBACK_SECRET. |
# 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.