Skip to main content

Overview

Installing proxy-hopper-webserver (pip install proxy-hopper-webserver) adds a second HTTP server, on its own port (default 8081), exposing: The GraphQL API lets you query and mutate targets, IP pools, and providers at runtime — without restarting the server and without editing the config file.

Embedded vs separate process

There are two ways to run the admin server, and which one is correct depends entirely on which backend you’re using: Since backend: memory only ever supports a single proxy instance anyway (there’s no way to share its state across replicas without Redis), embedding admin doesn’t cost you anything you didn’t already have — there was never going to be more than one node.
If proxy-hopper-webserver isn’t installed, --admin fails startup immediately with a clear error rather than silently running without it — the same fail-fast behavior as requesting --backend redis without proxy-hopper-redis installed.

Enabling the admin server

Both the embedded (run --admin) and separate-process (admin subcommand) topologies share the same server.admin/adminPort/adminHost config fields:
Or via environment variable:
The admin server runs on 0.0.0.0 by default. In production, restrict access at the network level — bind it to an internal interface or place it behind a firewall/ingress that only your operations tooling can reach.

Read-only mode

By default, the admin server can both query and mutate runtime state — targets, pools, and providers created or edited through the GraphQL API are held in the config store (or, with no config store configured, an in-process store that doesn’t survive a restart). Set server.adminReadOnly to run it as monitoring-only instead: every GraphQL mutation is rejected, and all config comes from the YAML file alone.
Rejected mutations fail with a PermissionError in the GraphQL errors array — the same shape as a role-based permission failure, so existing error handling in a client or the admin UI needs no special case for it. Queries are completely unaffected: status, targets, pools, providers, and targetMetrics all keep working, which is the point — you still get a fully usable monitoring surface, just not a mutable one.
adminReadOnly is checked before role-based auth, so it applies even when auth.enabled is false. It’s also independent of configStoreUrl/configStore.dialect — the two combine into four real deployment shapes:
The status query (and GET /api/v1/status) reports the current mode via readOnly, so a client or dashboard can detect it without attempting a mutation first:

Authentication

If auth.enabled: true is set, every request to protected endpoints must include an Authorization: Bearer <token> header. The token can be:
  • A JWT obtained from POST /auth/login (requires auth.admin to be configured)
  • An API key declared in auth.apiKeys
  • An OIDC token (when auth.oidc is configured)
The /health endpoint is always public. When auth is disabled, all endpoints are accessible without credentials. This is acceptable for internal-only deployments where the admin port is not exposed outside your network.

Obtaining a JWT

Returns:
Pass the token in subsequent requests:

GraphQL playground

With the admin server running, open http://localhost:8081/graphql in a browser to access the interactive GraphQL playground. You can explore the schema, run queries, and test mutations directly from the browser. If auth is enabled, add your token via the playground’s “Headers” panel:
See GraphQL API for the full query and mutation reference.

Ports summary