A PostgreSQL monitoring TUI inspired by pg_activity.
Supports PostgreSQL 14 and newer.
- Real-time views of:
pg_stat_activitypg_stat_replication/pg_replication_slotspg_stat_databasepg_lockspg_stat_io(PostgreSQL 16+)pg_stat_statements(if extension exists)
- Pg-activity-inspired
Activitydashboard with sampled TPS/DML/temp rates, session counts, and worker/process summaries - Activity subviews for active, waiting, blocking, and idle in transaction backends
- Contextual in-app help overlay (
?) with current-view shortcuts and metric explanations - TLS/SSL connections via rustls, including
sslmode=verify-fulland custom root certificates - Built-in themes plus runtime theme switching and config validation with
pgmon check-config - Interactive TUI (Tabs, Table navigation)
- Configurable refresh rate and top-N rows.
cargo build --releasepgmon --dsn "postgresql://user:password@localhost:5432/postgres"
# Connect using an alias from pgmon.yaml or pgmon.yml
pgmon prod
# Load a specific config file
pgmon --config ./pgmon.yaml prod
# Use the config's default connection
pgmon --config ./pgmon.yaml
# Validate config loading and connection resolution without starting the TUI
pgmon check-config --config ./pgmon.yaml prod
# Specific home view and sort
pgmon --dsn "..." --home-view statements --sort total_time --top-n 20 --refresh-ms 2000
# Fail faster on unreachable hosts
pgmon --connect-timeout-ms 1500
# Save selected queries into a persistent directory
pgmon --query-output-dir "$HOME/.local/share/pgmon/queries"
# Or rely on PGMON_DSN / ~/.pgpass
PGMON_DSN="postgresql://postgres@localhost/postgres" pgmonpgmon supports a pgmon.yaml or pgmon.yml configuration file for connection aliases, UI preferences, built-in theme selection, custom named themes, and per-view colors.
If the config file does not define any connections, pgmon still starts normally and falls back to PGMON_DSN, then .pgpass.
The file is looked for in the following locations (in order):
- Path passed with
-c, --config - Current working directory (
./pgmon.yaml, then./pgmon.yml) - User's configuration directory (
~/.config/pgmon/pgmon.yaml, then~/.config/pgmon/pgmon.ymlon Linux/macOS)
An example config is available at the repository root as pgmon.yaml.
# Optional default alias used when no positional alias is passed
default_connection: local
# Optional theme selection
# Built-in themes: calibrachoa, sky, mint, retro
theme: my_theme
# Named connections
connections:
local:
dsn: "postgresql://postgres:postgres@localhost:5432/postgres"
prod:
dsn: "postgresql://pgmon@prod.example.com/postgres"
staging:
dsn: "host=staging-db dbname=postgres user=pgmon password=secret"
# Optional custom theme templates
themes:
my_theme:
ui:
header_border_color: "#95a8b8"
footer_border_color: "#98aaa4"
views:
settings:
colors:
value: "#b4a9b7"
# Global UI preferences
# Top-level values override the selected theme
ui:
show_controls: true # Set to false to hide the Controls section
default_export_format: "csv" # or "json"
# Customize UI colors for specific views
# Top-level values override the selected theme
views:
settings:
colors:
value: "#a8a0b4" # Optional muted override for the 'Value' columnBuilt-in themes are available even without a config file: calibrachoa, sky, mint, and retro. Colors can be specified by name (e.g., "red", "green", "blue", "yellow", "cyan", "magenta", "white", "black") or as #RRGGBB hex values for softer palettes. The my_theme example above is only illustrative; you can remove it entirely and select a built-in theme by name.
Connection aliases are selected with pgmon <alias>. If default_connection is set, pgmon can start without a positional alias and will use that configured target. If --dsn is also provided, the explicit DSN takes precedence over both the positional alias and default_connection.
Current config support is limited to the keys above; PGMON_DSN_* environment switching remains future work.
When themes are available, press T inside the TUI to switch between them at runtime. Theme switching is applied immediately for the current session; edit pgmon.yaml or pgmon.yml if you want the new theme to persist across restarts. If a custom YAML theme uses the same name as a built-in theme, the custom definition overrides the built-in one.
-d, --dsn <STRING>: PostgreSQL connection string (optional ifPGMON_DSNor.pgpassis available)[ALIAS]: Optional connection alias frompgmon.yamlorpgmon.yml-c, --config <PATH>: Explicit path topgmon.yamlorpgmon.yml--connect-timeout-ms <u64>: Connection timeout in milliseconds (default: 3000)--query-output-dir <PATH>: Directory used when saving selected queries withEnter-r, --refresh-ms <u64>: Refresh interval (default: 1000)-n, --top-n <u32>: Rows to show, 0 = all (default: 0)--home-view <activity|statements>: Initial view-s, --sort <total_time|mean_time|calls>: Statements sort column (default:total_time)-v: Verbose logging
Connection precedence is: explicit --dsn, then positional alias from pgmon.yaml or pgmon.yml, then default_connection, then PGMON_DSN, then the first usable entry in PGPASSFILE or ~/.pgpass. If no aliases are configured at all, the resolution simply continues to PGMON_DSN and .pgpass.
pgmon is built with TLS support (rustls + ring), so encrypted connections work
out of the box. TLS parameters are read from the DSN and follow libpq naming:
# Encrypt, but do not verify the server certificate (see the table below)
pgmon --dsn "postgresql://user@db.example.com/postgres?sslmode=require"
# Encrypt and fully verify the certificate and hostname
pgmon --dsn "postgresql://user@db.example.com/postgres?sslmode=verify-full"
# Verify against a private CA
pgmon --dsn "postgresql://user@db.example.com/postgres?sslmode=verify-full&sslrootcert=/etc/ssl/certs/my-ca.crt"
# Client certificate authentication
pgmon --dsn "postgresql://user@db.example.com/postgres?sslmode=verify-full&sslcert=/path/client.crt&sslkey=/path/client.key"Supported parameters: sslmode, sslrootcert, sslcert, and sslkey. They
work in key/value DSNs too (host=db.example.com sslmode=require), and the
PGSSLMODE, PGSSLROOTCERT, PGSSLCERT, and PGSSLKEY environment variables
apply when the DSN does not set them.
The TLS connection is made by sqlx, so a few modes behave differently from
psql:
sslmode |
Encrypted | Server certificate checked | Differences from libpq |
|---|---|---|---|
disable |
no | no | none |
allow |
only when the server rejects the unencrypted attempt | when TLS is used and sslrootcert names an existing file |
tries without TLS first and, after an authorization error, once more with TLS, as libpq does |
prefer (default) |
when the server offers and accepts TLS | when sslrootcert names an existing file |
after a TLS error (a server alert or a failed certificate check) or an authorization error, retries once without TLS, like libpq except for the cases listed below; a server that does not offer TLS is used without it |
require |
yes | no, unless sslrootcert names an existing file |
with one, pgmon applies verify-ca, as libpq does; a missing file leaves the certificate unverified, also as in libpq |
verify-ca |
yes | chain only, not the hostname | any publicly trusted certificate is accepted, because the compiled-in root store is trusted too (see below) |
verify-full |
yes | chain and hostname | a publicly trusted certificate for the host name is accepted even when sslrootcert names a private CA |
Use verify-full whenever the server identity matters.
More differences:
- libpq retries
preferandallowafter any server error; pgmon retries only after an authorization error (SQLSTATE class 28, such as apg_hba.confrejection), so an unrelated failure like an unknown database is not attempted twice. - rustls reports TLS errors the same way during and after the handshake, so pgmon cannot tell which phase failed. A connection that drops or resets is never retried without TLS (libpq retries one that drops during the handshake), and a TLS protocol error is always retried (libpq fails on one after the handshake, such as a corrupt record). Handshake failures the server reports, such as an unsupported TLS version, are retried as in libpq.
- An
sslrootcertthat exists but cannot be read makespreferconnect without TLS and makesrequireand stricter modes fail, as in libpq. sslcertandsslkeymust be given together and both files must exist; libpq ignores a client certificate file that does not exist.sslrootcertis trusted in addition to the compiled-in root store (bundled Mozilla roots, or the host trust store), not instead of it as in libpq. sqlx offers no way to trust a private CA alone.sslrootcert=systemis rejected with an error, because sqlx would read it as a file namedsystem. Omitsslrootcertand useverify-fullto verify against the compiled-in root store.- libpq's default
~/.postgresql/root.crtis not read, so passsslrootcertexplicitly. - When a key/value DSN sets both
hostandhostaddr,verify-fullchecks the certificate against thehostaddrIP address rather than the host name.
Important
The default sslmode is prefer, which silently falls back to an
unencrypted connection when the server does not offer TLS or rejects the TLS
attempt, even after a failed certificate check. Set sslmode to
require or higher whenever encryption is not optional — otherwise a
misconfigured server yields a plaintext session rather than an error.
By default the trusted root certificates are the Mozilla set bundled into the
binary (webpki-roots), which keeps the static musl releases self-contained.
Point sslrootcert at your CA file for internal certificates, or build against
the host trust store instead:
cargo build --release --no-default-features --features tls-rustls-ring-native-rootsTo confirm which TLS backend a given binary was built with:
pgmon --version # long version: includes commit hash and TLS line
pgmon -V # short version: version number only
pgmon check-config # reports backend and root certificate sourceReleases up to and including 0.7.1 were built without any TLS backend and could not connect to servers requiring TLS (#6).
1-8: switch tabsh/l: previous or next tabj/k: move selection up or down/: search or filter the current viewe: export the current table as CSV or JSON inActivityandStatementsm: cycle the Activity chart between Connections, TPS, DML/s, Temp Bytes/s, and Growth Bytes/sT: open the theme picker when built-in or custom themes are available?: open contextual in-app help for the current viewq: quit or close the current modal
Use pgmon check-config to validate configuration loading and the effective connection resolution without starting the TUI.
Examples:
pgmon check-config
pgmon check-config prod
pgmon check-config --config ./pgmon.yaml prod
pgmon check-config --dsn "postgresql://postgres@localhost/postgres"The report includes:
- which config file was loaded, or whether built-in defaults are being used
- configured aliases, default connection, and active theme
- invalid color or alias/default-connection issues
- the effective connection source (
--dsn, alias,PGMON_DSN, or.pgpass) - a safe connection target summary without printing passwords
- the effective
sslmodefor that target, with a warning when it can connect unencrypted, does not check the hostname, trusts more thansslrootcert, or names a missingsslrootcertfile - the TLS backend compiled into the binary and where it loads root certificates from
- The Activity summary now shows the current connection target, observed refresh latency, and last successful refresh state.
- The footer highlights offline/reconnect state when background refreshes fail and shows a slow-link indicator when refreshes exceed the configured interval.
Statements,IO, andReplicationnow show explicit capability panels whenpg_stat_statements,pg_stat_io, or replication settings are unavailable instead of rendering synthetic placeholder rows.- On slower or remote links,
pgmonreduces extra metadata round trips by caching capability checks and uses the observed refresh latency to avoid overly aggressive background polling.
pgmon has two query-inspection flows:
- In
Statements, pression the selected row to open an info modal with the database name, full SQL text, and aggregated timing counters frompg_stat_statements. - In
Activity, pression the selected session to open an info modal for that backend query and optionally runEXPLAIN.
Inside the query info modal:
Entersaves the SQL text to--query-output-diror the system temp directory.xruns safeEXPLAINfrom bothActivityandStatements.pgmonalso shows whetherauto_explainis enabled and, when needed, how to enable it for real execution plans in PostgreSQL logs.- Unsupported SQL is refused before it reaches PostgreSQL; pgmon only explains single
SELECT/INSERT/UPDATE/DELETE/MERGEstatements.
pg_stat_statements often stores normalized SQL such as:
SELECT * FROM accounts WHERE id = $1Those placeholders do not include actual runtime values, so pgmon cannot produce a value-specific plan for them.
On PostgreSQL 16+, placeholders use EXPLAIN (GENERIC_PLAN, VERBOSE, SETTINGS) instead of executing the statement. This is safe, but it is intentionally value-agnostic: real bind values can change row estimates and even the chosen plan nodes.
On PostgreSQL 14 and 15, GENERIC_PLAN is not available yet, so pgmon leaves parameterized statements as info-only and shows a notice instead of attempting explain.
When PostgreSQL cannot infer one or more parameter types for a generic plan:
pgmonshows an error instead of executing the query- add explicit casts in the SQL or replace placeholders with real literals outside
pgmon
pgmon also refuses multi-statement SQL in explain mode so trailing statements in a batch cannot execute accidentally.
All plans shown by pgmon are estimated plans gathered from a fresh session. Session-local settings, temp objects, prepared statements, or different bind values can still make the real runtime plan differ.
- In the Database view, press
Enteron a selected database row to browse schemas and tables for that database, and pressEscto return to the summary view. - In the Activity view, use
ito open query info for the selected backend, and usea,w,b, andtto switch between active, waiting, blocking, and idle-in-transaction session subviews. - In the Activity view, press
mto cycle the chart between Connections, TPS, DML/s, Temp Bytes/s, and Growth Bytes/s without triggering a refresh. - Press
?in any main view to open contextual help with that view's shortcuts, important limitations, and brief metric explanations.