PostgreSQL
Configure PostgreSQL for self-hosted Rivet deployments.
PostgreSQL is the recommended backend for multi-node self-hosted deployments. It is production-ready for light-to-moderate workloads, up to roughly 1,000 concurrent actors, but is not built for enterprise scale beyond that. For a single-node deployment, use the file system backend (RocksDB-based). Teams running larger or high-throughput realtime workloads should contact enterprise support about FoundationDB.
Overview
PostgreSQL is the storage and coordination backend for self-hosted Rivet deployments. It can run a single engine node on its own, or back multiple engine nodes when paired with NATS. Rivet handles leader election, failover, and version sequencing internally.
Use PostgreSQL when you need:
- A durable, managed system of record instead of local RocksDB storage.
- Multiple engine nodes behind a load balancer for redundancy and horizontal scaling (requires NATS).
- Multi-region deployments (deploy one PostgreSQL instance per region, see Multi-Region).
- High availability with a managed or self-managed primary/replica failover setup.
Choosing a Backend
Pick your database backend based on how many engine nodes you run:
- Single-node: Use the file system backend (RocksDB). It is the recommended production-ready backend for single-node deployments. RocksDB is local to one node and cannot be shared across engine instances, and no pub/sub is needed.
- Multi-node: Use PostgreSQL as the database and NATS for pub/sub. PostgreSQL can be shared across engine nodes and is production-ready for light-to-moderate workloads, up to roughly 1,000 concurrent actors.
- Enterprise scale: Beyond that, or for high-throughput realtime workloads, PostgreSQL is not the right fit. Contact enterprise support about FoundationDB.
Single-Node vs Multi-Node
Whether PostgreSQL runs Rivet single-node or multi-node depends on whether NATS is configured for pub/sub:
- Single-node (no NATS): Configure only
postgres. Pub/sub uses the in-memory driver and UniversalDB runs in single-node mode with an in-process commit resolver. Run exactly one engine node against this deployment. - Multi-node (with NATS): Configure
postgresand a top-levelnatsblock for pub/sub. Pub/sub runs over NATS, and UniversalDB inherits that NATS config to run in multi-node mode, using NATS for follower-to-leader commit transport. Multiple engine nodes can then share the same PostgreSQL instance.
Configure NATS for pub/sub to make PostgreSQL multi-node. PostgreSQL inherits the pub/sub NATS config automatically, so a single top-level nats block drives both pub/sub and UniversalDB multi-node coordination. See Multi-Node Configuration below.
Do not run more than one engine node against a PostgreSQL deployment without NATS configured. Without NATS, UniversalDB runs single-node: the first node takes leadership, and any additional node cannot obtain leadership and will eventually error.
Basic Configuration
A single-node deployment needs only a postgres block:
{
"postgres": {
"url": "postgresql://user:password@host:5432/database"
}
}
RIVET__POSTGRES__URL="postgresql://user:password@host:5432/database"
Multi-Node Configuration
To run multiple engine nodes against the same PostgreSQL instance, add a top-level nats block to configure NATS for pub/sub. PostgreSQL inherits this config to run multi-node. Point every engine node at the same PostgreSQL instance and the same NATS cluster:
{
"postgres": {
"url": "postgresql://user:password@host:5432/database"
},
"nats": {
"addresses": ["nats-1:4222", "nats-2:4222"]
}
}
RIVET__POSTGRES__URL="postgresql://user:password@host:5432/database"
RIVET__NATS__ADDRESSES="nats-1:4222,nats-2:4222"
The top-level nats block configures pub/sub, and UniversalDB inherits it to enable multi-node mode. This is the recommended way to make PostgreSQL multi-node. For NATS high availability, run at least two NATS replicas.
postgres.nats is for advanced setups only: set it to point UniversalDB at a different NATS cluster than the one used for pub/sub. Leave it unset and rely on inheritance from the top-level nats block unless you specifically need to separate the two.
Requirements and Recommendations
Version
Use PostgreSQL 14 or newer. Rivet is tested against PostgreSQL 18, which is recommended for new deployments.
Connection Limits
Each Rivet engine node opens a pool of direct connections to PostgreSQL and can use well over a hundred connections per node under load. PostgreSQL’s default max_connections of 100 is too low for even a single busy engine node.
- Set PostgreSQL
max_connectionsto comfortably exceed(number of engine nodes × 150)plus headroom for backups, monitoring, and your own queries. - If you use a managed PostgreSQL service, confirm its connection limit is high enough or pick a tier that allows raising it. Connection exhaustion shows up as engine startup failures or stalled requests under load.
Do not work around the connection limit with a connection pooler. See Do Not Use Connection Poolers below.
Resources
PostgreSQL is the system of record for the entire deployment, so size it accordingly:
- Give PostgreSQL dedicated CPU, memory, and fast disk (SSD/NVMe with high IOPS). Avoid co-locating it with other heavy workloads.
- Rivet generates steady write and row-turnover on its internal tables. Keep autovacuum enabled and healthy so dead tuples do not accumulate.
High Availability and Backups
A single PostgreSQL instance is a single point of failure for your whole deployment.
- Configure a standby replica with automatic failover (managed services such as Amazon RDS, Cloud SQL, and Azure Database provide this).
- Enable automated backups and point-in-time recovery, and periodically test restoring from them.
Multi-Region
Deploy one PostgreSQL instance per region or datacenter. Engine nodes connect to the PostgreSQL instance in their own region. See Multi-Region for the full topology.
Managed Postgres Compatibility
Some hosted PostgreSQL platforms require additional configuration due to platform-specific restrictions.
Use direct connection (not connection pooler).
{
"postgres": {
"url": "postgresql://pscale_api_<username>.<unique-id>:<password>@<region>.pg.psdb.cloud:5432/postgres?sslmode=require"
}
}
RIVET__POSTGRES__URL="postgresql://pscale_api_<username>.<unique-id>:<password>@<region>.pg.psdb.cloud:5432/postgres?sslmode=require"
Use direct connection on port 5432 (not connection pooler).
Without SSL
{
"postgres": {
"url": "postgresql://postgres:<password>@db.<project-ref>.supabase.co:5432/postgres?sslmode=disable"
}
}
RIVET__POSTGRES__URL="postgresql://postgres:<password>@db.<project-ref>.supabase.co:5432/postgres?sslmode=disable"
With SSL
Download the root certificate from your Supabase dashboard and specify its path. See Supabase SSL Enforcement for details.
{
"postgres": {
"url": "postgresql://postgres:<password>@db.<project-ref>.supabase.co:5432/postgres?sslmode=require",
"ssl": {
"root_cert_path": "/path/to/supabase-ca.crt"
}
}
}
RIVET__POSTGRES__URL="postgresql://postgres:<password>@db.<project-ref>.supabase.co:5432/postgres?sslmode=require"
RIVET__POSTGRES__SSL__ROOT_CERT_PATH="/path/to/supabase-ca.crt"
SSL/TLS Support
To enable SSL for Postgres, add sslmode=require to your PostgreSQL connection URL:
{
"postgres": {
"url": "postgresql://user:password@host.example.com:5432/database?sslmode=require"
}
}
RIVET__POSTGRES__URL="postgresql://user:password@host.example.com:5432/database?sslmode=require"
The sslmode parameter controls TLS usage:
disable: Do not use TLSprefer: Use TLS if available, otherwise connect without TLS (default)require: Require TLS connection (fails if TLS is not available)
To verify the server certificate against a CA or verify the hostname, use custom SSL certificates (see below).
Custom SSL Certificates
For databases using custom certificate authorities (e.g., Supabase) or requiring client certificate authentication, you can specify certificate paths in the configuration:
{
"postgres": {
"url": "postgresql://user:password@host:5432/database?sslmode=require",
"ssl": {
"root_cert_path": "/path/to/root-ca.crt",
"client_cert_path": "/path/to/client.crt",
"client_key_path": "/path/to/client.key"
}
}
}
RIVET__POSTGRES__URL="postgresql://user:password@host:5432/database?sslmode=require"
RIVET__POSTGRES__SSL__ROOT_CERT_PATH="/path/to/root-ca.crt"
RIVET__POSTGRES__SSL__CLIENT_CERT_PATH="/path/to/client.crt"
RIVET__POSTGRES__SSL__CLIENT_KEY_PATH="/path/to/client.key"
| Parameter | Description | PostgreSQL Equivalent |
|---|---|---|
root_cert_path | Path to the root certificate file for verifying the server’s certificate | sslrootcert |
client_cert_path | Path to the client certificate file for client certificate authentication | sslcert |
client_key_path | Path to the client private key file for client certificate authentication | sslkey |
All SSL paths are optional. If not specified, Rivet uses the default system root certificates from Mozilla’s root certificate store.
Do Not Use Connection Poolers
Rivet requires direct PostgreSQL connections for session-level features and does not support connection poolers.
Do not use:
- PgBouncer
- Supavisor
- AWS RDS Proxy
Troubleshooting
Too Many Connections
Errors like FATAL: sorry, too many clients already or engine nodes failing to start under load mean PostgreSQL’s max_connections is too low. Raise it to account for every engine node (see Connection Limits). Do not add a connection pooler to work around this.
Connection Refused or TLS Errors
- Confirm the engine connects directly to PostgreSQL and not through a pooler (PgBouncer, Supavisor, RDS Proxy). Rivet requires direct connections.
- For TLS errors, verify
sslmodematches your server and, for custom certificate authorities, thatssl.root_cert_pathpoints to the correct CA certificate. See SSL/TLS Support.