Skip to content
Subnomic
← Back to blog
Fleet Nov 12, 2026 · 2 min read

Query a private Postgres without opening port 5432

No SSH tunnel, no bastion, no VPN, and nobody holding the database password.

The usual ways to query a database that is not on the internet all open something. An SSH tunnel (ssh -L 5432:localhost:5432) needs port 22 open and a key on every laptop. A bastion is a server whose only job is to be attacked. A VPN puts every laptop on the network the database lives on. And all three end with the database password in somebody’s .pgpass.

The connection goes the other way

In Subnomic, a small agent runs on a machine that can already reach the database, often the database host itself. It opens one outbound TLS connection to Subnomic and listens on nothing. When you open the database console, the query travels down that connection and the agent connects to 127.0.0.1:5432 on your behalf. Port 5432 stays closed to the internet; so does 22.

Two kinds of agent can carry it:

Nobody needs the password

You register the database once: the engine (PostgreSQL, MySQL, Redis or MongoDB), the agent that can reach it, the address, and a username and password. The credentials are encrypted at rest and never returned by the API, so the people who query the database never see them, and offboarding someone does not mean rotating them.

Reads and writes are different permissions

Every statement is classified before it runs. SELECT, SHOW and EXPLAIN, Redis’s GET family and Mongo’s find and aggregate need database.query_read; anything else counts as a write and needs database.query_write. A role for analysts can read production without being able to change it, enforced by the server, not by a read-only user someone forgot to create.

On Team and Enterprise, guardrails go further: a regular expression on the statement, checked before it runs. A rule that denies DELETE or UPDATE without a WHERE is two lines in the rule list. Sessions are recorded: the query text with obvious secrets redacted, the timing and the row count, never the rows themselves.

What it looks like

Open the database in the console and type a query; ⌘/Ctrl + Enter runs it, and results come back as a grid of up to 1,000 rows. The same access works for Runa: an agent access with database.query_read lets it explore schemas and run read-only SQL against the databases that access names, each query shown with its text.

What it is not

Setting it up takes the agent install and one form: see Agents and Databases. Why the agent dials out in the first place is in SSH without a single open port.