Production deployment
PgBouncer targets, auto-discovery, credentials and TLS, systemd, Docker, and Kubernetes.
Quick start
Install, connect, verify, scrape. One static binary reads one PostgreSQL instance and answers on :9630. There is no agent to deploy, no sidecar to schedule, and nothing to compile.
You need a reachable PostgreSQL 10–19+ (or PgBouncer 1.8+) and permission to create a role in it
Still on PostgreSQL 9.1–9.6? Check the compatibility matrix first
The whole path
Each one is independently verifiable, so a failure tells you which half of the pipeline to look at.
A repository package on a long-lived host, or the release archive anywhere else.
sudo apt install -y pg-exporterpg_monitor is a built-in role since PostgreSQL 10 and covers every read the default collectors make.
GRANT pg_monitor TO monitor;The URL may come from a flag, the environment, or a secret file — never from a shell history you will forget.
export PG_EXPORTER_URL='postgres://monitor:S3cret@localhost:5432/postgres'--dry-run prints the merged collector set and exits; without it the exporter listens on :9630.
pg_exporter --dry-run && pg_exporterpg_up 1 means every layer works. Only then does the target belong in a Prometheus job.
curl -s localhost:9630/metrics | grep '^pg_up 'Step 01
On Linux amd64 the release archive is the shortest path. For managed packages, other platforms, containers, Pigsty, and source builds, use the download page.
Confirm the installation:
The RPM/DEB route adds an upgrade path, file ownership, /etc/default/pg_exporter, and a systemd unit that runs as the prometheus user. The archive gives you the binary and leaves the service definition to you.
Step 02
A dedicated least-privilege role on the target instance. The built-in pg_monitor role, available since PostgreSQL 10, grants every read the default collectors need — and nothing else.
Trying it out locally as a superuser such as postgres? Skip this step — but do not carry that shortcut into a host anyone else can reach.
Step 03
Parse the configuration first, then start for real. Both commands read the same URL, so a typo fails before anything listens on a port.
With no URL at all, pg_exporter falls back to the local-first default postgresql:///?sslmode=disable, which fits running on the same host as PostgreSQL. The full precedence — --url › PG_EXPORTER_URL › PGURL › PG_EXPORTER_URL_FILE › default — is documented in the deployment guide.
Pull the metrics from another terminal:
What a healthy target looks like
pg_up 1 means the whole pipeline works — the remaining 600+ metrics (pg_db_*, pg_table_*, pg_wal_*, …) all come from the declarative collector definitions in pg_exporter.yml. If pg_up is 0, restart with --log.level=debug and read the connection error.
pg_up 1 # 1 when the target is reachable, 0 otherwise
pg_version 170000 # version in server_version_num format
pg_in_recovery 0 # 1 on replicas, 0 on primariesStep 04
Add one scrape target. The exporter is pull-based and holds no queue, so nothing else has to change.
Collectors cache their results for their own ttl — most realtime collectors use ttl: 10. As long as the TTL stays below the scrape interval, every scrape gets fresh data while high-frequency scraping can never overwhelm the database. That is also why setting scrape_interval below the common TTLs is not recommended.
That is the whole path. For Grafana, reuse the PostgreSQL dashboards from Pigsty, or explore the live demo.
If something is off
The exporter exposes the evidence needed to operate it, not just the database metrics it emits. Reach for /explain and /stat before reaching for the logs.
pg_up 0 — the connection failsRestart with pg_exporter --log.level=debug and read the error it prints. It is almost always one of three things: a URL that names the wrong host, database, or user; a pg_hba.conf rule that does not admit the monitoring role from that address; or a network path that never reaches the port.
Ask the exporter why. curl localhost:9630/explain prints every collector’s planning verdict — which branch was selected, which was skipped for a version gate, a role mismatch, a tag, or a failed predicate.
curl localhost:9630/stat returns per-collector error counters, durations, and cache state. A non-fatal collector fails in isolation: its siblings keep reporting, which is why a single broken query never takes the target down.
Find the slow collector in /stat, then either raise its ttl so its result is reused across scrapes or set skip: true if you do not need it. Collector cost is configuration, not a property of the binary.
/stat, /explain, and /reload describe and control the exporter. In production, protect them with --web.config.file for TLS and authentication, or keep the listener on a trusted network.
Then what
PgBouncer targets, auto-discovery, credentials and TLS, systemd, Docker, and Kubernetes.
GAUGE, COUNTER, HISTOGRAM and LABEL semantics, TTL, tags, and version gates.
Health checks and primary/replica traffic routing through /up, /primary, and /replica.
What the 58 shipped definition files actually measure, collector by collector.
Least-privilege roles, secret handling, TLS on both hops, and what the exporter never reads.
A longer catalogue of failure modes, with the endpoint that identifies each one.
Working end to end?Bring the rest of the stack — recording rules, alerts, and Grafana dashboards — or read how the collectors are written.