Connect your application to the database
This guide shows how to connect an application to Zenifra PostgreSQL or MariaDB with the most common Python, Node.js, and Bun drivers. Every connection uses TLS and verifies the database certificate and hostname.
Before you start
- Open the database in the Console and copy the connection URI. The full password is shown only at creation or after a password rotation.
- In the application project, open Environment Variables and create
DATABASE_URLwith that URI. - Always use the host shown in the Console, in the
postgresql-<id>.managed.zenifra.comormariadb-<id>.managed.zenifra.comformat. The certificate is issued for that name, so connections through the IP address fail verification.
Do not turn off certificate verification to "make it work": do not use sslmode=require, sslmode=disable, rejectUnauthorized: false, or equivalent options. The adjustments below solve the known cases while keeping full verification.
Summary by driver
| Database | Language | Driver | What to do |
|---|---|---|---|
| PostgreSQL | Terminal | psql | Use the Console URI as is |
| PostgreSQL | Python | psycopg / psycopg2 (binary packages) | Set SSL_CERT_FILE |
| PostgreSQL | Python | asyncpg | Remove sslrootcert from the URI and pass a TLS context |
| PostgreSQL | Node.js and Bun | pg (node-postgres) | Remove &sslrootcert=system from the URI |
| PostgreSQL | Node.js and Bun | postgres (postgres.js) | Use the Console URI as is |
| PostgreSQL | Bun | Bun.sql | Use the Console URI as is |
| MariaDB | Node.js | mariadb | Use the Console URI as is |
| MariaDB | Node.js and Bun | mysql2 | Configure ssl as an object, with verifyIdentity |
| MariaDB | Python | PyMySQL | Provide the CA bundle and enable identity verification |
PostgreSQL
The PostgreSQL URI in the Console ends with ?sslmode=verify-full&sslrootcert=system. These parameters require TLS, verify the certificate chain against the system trust store, and check the hostname. Some drivers do not understand sslrootcert=system and need the adjustments below.
psql
psql "$DATABASE_URL"psql versions that do not support sslrootcert=system accept the path of the system CA bundle instead of system, for example sslrootcert=/etc/ssl/certs/ca-certificates.crt.
Python with psycopg or psycopg2
The psycopg[binary] and psycopg2-binary packages ship their own TLS library, which cannot find the system certificates. Without an adjustment, the connection fails with SSL error: certificate verify failed.
Set the environment variable below in the project. It points the driver to the system CA bundle, and the URI stays the same:
SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crtimport os
import psycopg
with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
conn.execute("SELECT 1")Python applications published on Zenifra without their own Dockerfile already get this variable. If the application was published before 2026-10-21, run a new deploy. With your own Dockerfile, add ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt and make sure the image installs the ca-certificates package. Django and SQLAlchemy use psycopg or psycopg2 under the hood and follow the same rule.
Python with asyncpg
asyncpg treats sslrootcert=system as a file name and fails with FileNotFoundError. Remove sslrootcert from the URI and pass a TLS context that verifies the certificate and hostname:
import os
import ssl
import asyncpg
dsn = os.environ["DATABASE_URL"].split("?")[0]
conn = await asyncpg.connect(dsn, ssl=ssl.create_default_context())Node.js and Bun with pg (node-postgres)
pg treats sslrootcert=system as a file name and fails with ENOENT: no such file or directory, open 'system'. Remove &sslrootcert=system and keep sslmode=verify-full. The driver then verifies the certificate and hostname with the CAs bundled with Node.js:
DATABASE_URL=postgresql://app:<password>@<host>:<port>/app?sslmode=verify-fullimport pg from 'pg'
const client = new pg.Client({ connectionString: process.env.DATABASE_URL })
await client.connect()Node.js and Bun with postgres.js
import postgres from 'postgres'
const sql = postgres(process.env.DATABASE_URL)
await sql`SELECT 1`Bun with Bun.sql
const sql = new Bun.SQL(process.env.DATABASE_URL)
await sql`SELECT 1`MariaDB
The MariaDB URI in the Console uses the mariadb://app:<password>@<host>:<port>/app?ssl=true format.
Node.js with mariadb
import mariadb from 'mariadb'
const conn = await mariadb.createConnection(process.env.DATABASE_URL)
await conn.query('SELECT 1')On Bun, use mysql2 instead of the mariadb connector.
Node.js and Bun with mysql2
mysql2 does not accept ssl=true in the URI and fails with SSL profile must be an object. Pass the connection details as an object. verifyIdentity: true is required for the driver to check the certificate hostname:
import mysql from 'mysql2/promise'
const url = new URL(process.env.DATABASE_URL)
const conn = await mysql.createConnection({
host: url.hostname,
port: Number(url.port),
user: decodeURIComponent(url.username),
password: decodeURIComponent(url.password),
database: url.pathname.slice(1),
ssl: { rejectUnauthorized: true, verifyIdentity: true },
})Python with PyMySQL
import os
from urllib.parse import unquote, urlsplit
import pymysql
url = urlsplit(os.environ["DATABASE_URL"])
conn = pymysql.connect(
host=url.hostname,
port=url.port,
user=unquote(url.username),
password=unquote(url.password),
database=url.path.lstrip("/"),
ssl_ca="/etc/ssl/certs/ca-certificates.crt",
ssl_verify_cert=True,
ssl_verify_identity=True,
)The /etc/ssl/certs/ca-certificates.crt path exists in Debian, Ubuntu, and Alpine images with the ca-certificates package installed. On other systems, use the matching CA bundle.
Common errors
| Message | Cause | How to fix |
|---|---|---|
SSL error: certificate verify failed | A Python driver with its own TLS library cannot find the system CAs | Set SSL_CERT_FILE as in psycopg or psycopg2 |
ENOENT: no such file or directory, open 'system' | pg treats sslrootcert=system as a file | Remove &sslrootcert=system from the URI |
FileNotFoundError in asyncpg | asyncpg treats sslrootcert=system as a file | Remove sslrootcert and pass ssl=ssl.create_default_context() |
SSL profile must be an object | mysql2 does not accept ssl=true in the URI | Pass ssl as an object |
Hostname/IP does not match certificate's altnames or IP address mismatch | The connection uses an IP or another name instead of the Console host | Use the exact host shown in the Console |
password authentication failed or Access denied | The password in DATABASE_URL is not the current one | Check the password or generate a new one in the database settings and update the application variable |
Next steps
Last updated on