--- id: operations title: Operations description: Health checks, the admin reindex endpoint, a troubleshooting playbook, and running two instances on one host. tags: [admin, operations, monitoring] lang: en --- # Operations ## Health and readiness | Endpoint | Meaning | |---|---| | `GET /healthz` | Always `200` if the process is running — no database access. | | `GET /readyz` | `200` if a query against Postgres succeeds, `503` otherwise. Use this for orchestration readiness probes. | | `GET /admin/status` | Bearer-token-protected (`F451_ADMIN_TOKEN`, see [[configuration-reference]]). Returns in-memory counters (`webhookErrors`, `indexerErrors`, `driftErrors`) and a `since` timestamp. The counters reset on every process restart — treat this as "is something wrong right now", not as a historical audit log. | ## Triggering a reindex ```bash curl -X POST https:///admin/reindex \ -H "Authorization: Bearer $F451_ADMIN_TOKEN" \ -H 'Content-Type: application/json' \ -d '{}' ``` An empty body reindexes every configured space; `{"space":""}` reindexes just one. The response reports success or failure per space — `200` if at least one space succeeded, `502` only if all of them failed (check `GET /admin/status` and the `api` logs next). > [!NOTE] > If OIDC sign-in is active, `/admin/*` routes need a valid session **in > addition to** the bearer token — a plain `curl` call without a browser > session behind it gets a reproducible `401`. Run admin calls from a > browser context where you are already signed in (developer tools' > network/fetch panel with the session cookie present), not from a bare > terminal. ## Troubleshooting playbook | Symptom | Likely cause | |---|---| | Every API route is reachable without signing in | `F451_OIDC_ISSUER` is unset — auth is intentionally off in that state. | | Sign-in redirects to an error page after the identity provider | `F451_OIDC_REDIRECT_URL` (or the app registration's redirect URI) doesn't match the actual callback URL exactly. | | Every save/publish/release fails with `403` | `F451_PUBLIC_BASE_URL` doesn't match the URL users actually browse to — the CSRF origin check rejects the mismatch. See [[configuration-reference]]. | | A webhook call fails with `401` | `F451_WEBHOOK_SECRET_FORGEJO`/`_GITHUB` doesn't match the secret configured on the repository's webhook. | | A page edited directly in Git takes a few minutes to show up | Expected — the drift job catches up within about 5 minutes if the webhook was missed. See [[spaces-and-git-providers]]. | | `POST /admin/reindex` / `GET /admin/status` returns `503` | `F451_ADMIN_TOKEN` is unset — both endpoints are fail-closed without it. | | `POST /admin/reindex` returns `401` from a plain terminal call | Missing session — see the note above. | | The rate limits seem to apply to "everyone at once" instead of per person | `F451_TRUST_PROXY` is unset or wrong for the real proxy chain — see [[configuration-reference]]. | | The same person appears as two different users | They signed in through two different methods (e.g. OIDC once, GitHub once) — see [[github-sign-in]]. Not a bug, a consequence of having no shared identity across methods. | | A user reports "I don't see any spaces" | Their account is signed in but has no linked Forgejo/GitHub account yet, or that account lacks repository access — see [[sign-in-options]]. | ## Running a second instance on the same host Both the Forgejo stack and the application stack expose their ports and cookie naming as variables, so a second instance (for example a demo or a staging copy) can run alongside the first without conflicting: | Variable | What it separates | |---|---| | `F451_COOKIE_PREFIX` | Session cookie names — without a distinct value, two instances sharing a host would overwrite each other's cookies in the browser. | | `F451_WEB_PORT` | Host port for the web UI. | | `F451_DRAWIO_PORT` | Host port for the diagram editor. | | `F451_FORGEJO_HTTP_PORT` / `F451_FORGEJO_SSH_PORT` | Host ports for the second Forgejo instance. | | `F451_FORGEJO_ROOT_URL` | The second Forgejo instance's own public URL. | Give the second instance's Git stack its own Compose project name (`docker compose -p ...`) so its containers and volumes don't collide with the first instance's.