Patroni REST API
Patroni exposes an HTTP REST API on each member, providing health check endpoints (for load balancers and Kubernetes probes), monitoring data (including Prometheus metrics), and cluster management operations.
Reference: Patroni official docs, Pigsty REST API.
Finding the REST API
Each member’s REST API port is assigned automatically by pgcli and stored in
pg.yaml under addons.patroni.<scope>.members.<name>.restapi_port. The port
is also embedded in the rendered patroni.yml as restapi.connect_address.
Authentication
pgcli enables basic-auth on the REST API (username postgres, auto-generated
password). But authentication is method-based, not blanket:
| Method | Auth required? | Endpoints |
|---|---|---|
GET / HEAD / OPTIONS |
No | All read endpoints — /, /primary, /replica, /health, /cluster, /config, /metrics, /patroni, … |
POST / PATCH / PUT / DELETE |
Yes | Write endpoints — /failover, /switchover, /config (modify), /reload, /restart, … |
This is by design: read-only health checks stay open so a load balancer or
Prometheus can poll them without credentials, while operations that change
cluster state (POST /failover, PATCH /config) require authentication. The
credentials exist so patronictl and other Patroni members can perform writes.
So a load balancer health check needs no auth:
Write operations do require auth. The password is the same restapi_password
used in patroni.yml’s restapi.authentication section. Export it with:
Health Check Endpoints
All health check endpoints respond with GET requests. Patroni returns a JSON
document describing the node state, along with an HTTP status code. Use HEAD
or OPTIONS instead of GET when only the status code is needed (no response
body).
Primary-only endpoints (200 only on leader)
These return HTTP 200 only when the node is the current leader holding
the leader lock:
| Endpoint | Description |
|---|---|
GET / |
Root — primary health check |
GET /primary |
Alias for / |
GET /read-write |
Alias for / |
GET /leader |
Like / but doesn’t distinguish primary vs standby_leader |
GET /master |
Legacy alias for /leader |
These endpoints are useful for load balancer health checks that should route writes only to the current primary.
Replica-only endpoints (200 only on replica)
| Endpoint | Description |
|---|---|
GET /replica |
Replica health check — 200 when node is running, role is replica, and noloadbalance tag is not set |
GET /replica?replication_state=streaming |
Only 200 when replica is actively streaming (not still catching up via archive recovery) |
GET /replica?lag=<max> |
Only 200 when replication lag is below the threshold (bytes or human-readable: 10MB, 1GB) |
Read-only endpoints (200 on both primary and replica)
| Endpoint | Description |
|---|---|
GET /read-only |
Any running node (primary or replica) |
GET /synchronous / GET /sync |
Sync standby only |
GET /asynchronous / GET /async |
Async standby only |
GET /read-only-sync |
Primary + sync standby |
GET /read-only-quorum |
Primary + quorum standby |
GET /quorum |
Quorum standby only |
PostgreSQL health
| Endpoint | Description |
|---|---|
GET /health |
200 when PostgreSQL is running (regardless of role) |
Kubernetes Probes
| Endpoint | Description |
|---|---|
GET /liveness |
200 if Patroni heartbeat loop is running. 503 if primary’s last heartbeat exceeds ttl seconds, or replica exceeds 2*ttl. Lightweight — no SQL queries. Suitable for livenessProbe. |
GET /readiness |
200 when node is leader, or when PostgreSQL is running, replicating, and within the allowed lag. Accepts ?lag=<max> (default: maximum_lag_on_failover) and ?mode=apply|write (default: apply). Suitable for readinessProbe. |
Monitoring Endpoints
GET /patroni
Returns detailed node status as JSON. Used internally by Patroni during leader election and also useful for monitoring:
Response fields:
| Field | Description |
|---|---|
state |
Node state: running, stopped, starting, etc. |
role |
primary or replica |
server_version |
PostgreSQL version as integer |
xlog.location |
Current WAL position (primary only) |
xlog.received_location |
WAL received from primary (replica only) |
xlog.replayed_location |
WAL replayed (replica only) |
timeline |
Current timeline number |
replication |
Array of connected replicas (primary only) |
cluster_unlocked |
true if no leader lock is held |
pause |
true if auto-failover is paused |
dcs_last_seen |
Epoch timestamp of last successful DCS contact |
patroni.version |
Patroni version |
patroni.scope |
Cluster scope name |
patroni.name |
Member name |
GET /cluster
Returns the full cluster topology — all members with their roles, states, and replication status:
GET /config
Returns the current dynamic configuration stored in the DCS:
This is equivalent to pg ha edit-config <scope> --show.
GET /history
Returns the timeline history. Empty array [] when no timeline switch has
occurred (fresh cluster).
GET /metrics
Returns monitoring data in Prometheus exposition format, suitable for scraping by Prometheus or compatible systems:
Key metrics:
| Metric | Description |
|---|---|
patroni_primary |
1 if this node is the leader |
patroni_replica |
1 if this node is a replica |
patroni_postgres_running |
1 if PostgreSQL is running |
patroni_postgres_streaming |
1 if PostgreSQL is streaming (replica) |
patroni_xlog_location |
Current WAL position (primary only) |
patroni_xlog_received_location |
WAL received (replica only) |
patroni_xlog_replayed_location |
WAL replayed (replica only) |
patroni_cluster_unlocked |
1 if no leader lock |
patroni_is_paused |
1 if auto-failover is disabled |
patroni_pending_restart |
1 if node needs restart |
patroni_postgres_timeline |
Current timeline |
patroni_dcs_last_seen |
Epoch of last DCS contact |
patroni_server_version |
PostgreSQL version |
Tag-based Filtering
Health check endpoints accept query parameters to filter by custom tags defined
in the member’s patroni.yml tags section:
Practical Patterns
Load balancer routing
Because GET health checks need no auth, a load balancer can poll the REST API
endpoints directly. The pattern (from Patroni’s example haproxy.cfg) is: the backend TCP port
is the PostgreSQL port, but the health check hits the REST API port with
GET /, which returns 200 only on the leader.
Single read-write endpoint (all traffic → current leader):
check port 8008/8009— the HTTP health check goes to the REST API port, not the PG port.GET /returns 200 only on the leader → HAProxy marks only the leader asUP.on-marked-down shutdown-sessions— on failover, sessions to the old leader are killed so clients reconnect to the new leader.fall 3/rise 2withinter 3s— ~9s to mark down, ~6s to mark up.
Read/write split — add a second listener that uses GET /replica (200 only on replicas) for read traffic:
app_rw(:5000) —GET /→ only the leader isUP→ writes land on the leader.app_ro(:5001) —GET /replica→ only replicas areUP→ reads spread across replicas.
The same GET /replica?lag=1MB refinement (from the replica endpoints above)
keeps a lagging replica out of the read pool.
Monitoring with curl
Quick health check script:
Prometheus scrape config
GET /metrics needs no auth, so the scrape config works without credentials: