Credential Rotation
Rotate Postgres credentials atomically across all layers — database, pooler, and secret store — with zero downtime and full audit trails.
Credential Rotation
Keeping your database passwords fresh shouldn't be a headache. That's why AxiomDB provides atomic credential rotation. In a single, seamless operation, it updates passwords across PostgreSQL, PgBouncer, and your managed secret store. We designed this process from the ground up to ensure zero downtime and complete traceability.
How it works
Rotating your credentials is one of the best ways to keep your project secure. Under the hood, AxiomDB takes care of all the heavy lifting—updating passwords across multiple layers while making sure no active connection is left stranded with old, stale credentials.
┌──────────────────────────────────────────────────────────────────┐
│ Credential Rotation Flow │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────┐ │
│ │ Client │───▶│ Control Plane│───▶│ 1. Generate new │ │
│ │ Request │ │ │ │ password │ │
│ └──────────┘ └──────────────┘ └─────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ 2. Apply to PostgreSQL │ │
│ │ ALTER ROLE ... │ │
│ └─────────┬──────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ 3. Update secret store │ │
│ │ (vault/KMS) │ │
│ └─────────┬──────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ 4. Reload PgBouncer │ │
│ │ RELOAD │ │
│ └─────────┬──────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ 5. Write audit event │ │
│ │ 6. Return new URLs │ │
│ └────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘What can you rotate?
You can choose to rotate credentials for either connection type individually, or do both at the same time:
| Target | Description | Port | Role |
|---|---|---|---|
runtime | PgBouncer pooler credentials | 6432 | axiom_pooler |
direct | Direct PostgreSQL credentials | 5432 | axiom_direct |
both | Both runtime and direct | 6432 + 5432 | Both roles |
Runtime vs Direct
Think of the runtime role as your everyday helper—it connects through PgBouncer and is perfect for most application workloads. The direct role connects straight to PostgreSQL, which you'll need for specific tasks that PgBouncer doesn't support (like PREPARE, LISTEN/NOTIFY, or certain SET commands).
Rotating credentials using the CLI
Rotating runtime credentials
axiom credentials rotate \
--project my-project \
--branch main \
--target runtimeRotating both credential sets
axiom credentials rotate \
--project my-project \
--branch main \
--target bothWhat the output looks like
Rotating credentials for branch 'main' (target: both)...
✓ New password generated (32 chars, alphanumeric + symbols)
✓ PostgreSQL role 'axiom_pooler' updated
✓ PostgreSQL role 'axiom_direct' updated
✓ Secret store updated
✓ PgBouncer reloaded
✓ Audit event logged
New connection strings:
Runtime: postgresql://axiom_pooler:***@pooler.axiom.cloud:6432/mydb
Direct: postgresql://axiom_direct:***@direct.axiom.cloud:5432/mydb
⚠ These URLs are shown once. Store them securely.Rotating credentials using the API
curl -X POST "https://api.axiom.cloud/v1/projects/prj_abc123/branches/br_main/credentials/rotate" \
-H "Authorization: Bearer ptk_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"target": "both"
}'Response:
{
"rotation_id": "rot_9xK2mP3n",
"target": "both",
"status": "completed",
"credentials": {
"runtime": {
"host": "pooler.axiom.cloud",
"port": 6432,
"database": "mydb",
"user": "axiom_pooler",
"password": "kR7x...mP9n",
"url": "postgresql://axiom_pooler:kR7x...mP9n@pooler.axiom.cloud:6432/mydb?sslmode=require"
},
"direct": {
"host": "direct.axiom.cloud",
"port": 5432,
"database": "mydb",
"user": "axiom_direct",
"password": "Q3w8...nL5p",
"url": "postgresql://axiom_direct:Q3w8...nL5p@direct.axiom.cloud:5432/mydb?sslmode=require"
}
},
"rotated_at": "2026-01-15T10:30:00Z",
"rotated_by": "usr_8xK2mP"
}Grab those URLs now!
Keep in mind that the password and url fields are only shown in the rotation response. You won't be able to get them back through any other API call. If you lose them, you'll need to run another rotation to get new ones.
How We Generate Passwords
To keep things ultra-secure, AxiomDB creates cryptographically strong passwords with these specifications:
| Property | Value |
|---|---|
| Length | 32 characters |
| Character set | A-Z, a-z, 0-9, !@#$%^&* |
| Entropy | ≥ 192 bits |
| Algorithm | CSPRNG (OS-level) |
| Uniqueness | Never reused across rotations or projects |
An All-or-Nothing Process
We designed credential rotation to be completely atomic. This means if even a single step fails, the entire operation rolls back to where it started:
- Generate password: We create a new, secure password in memory.
- Apply to PostgreSQL: We update the role's password in the database catalog using
ALTER ROLE. - Update secret store: We write the new password securely to your managed secret store.
- Reload PgBouncer: We signal PgBouncer to reload and use the new credentials.
- Write audit event: We log the rotation event in your audit trail.
- Return URLs: We send the brand-new connection strings back to you.
If step 2 fails (for instance, if the database is down), the operation stops right there and nothing changes. If step 3 fails, we undo the password change in PostgreSQL. And if step 4 fails, we try reloading PgBouncer up to three times before rolling back the entire update.
SQL executed during rotation
-- Step 2: Update PostgreSQL role
ALTER ROLE axiom_pooler WITH PASSWORD 'kR7x...mP9n';
-- Verify the change
SELECT rolname, rolpassword IS NOT NULL AS has_password
FROM pg_authid
WHERE rolname IN ('axiom_pooler', 'axiom_direct'); rolname | has_password
---------------+-------------
axiom_pooler | t
axiom_direct | tWhat Happens to Active Connections?
Here is what to expect during a rotation:
| Scenario | Behavior |
|---|---|
| Active PgBouncer connections | Drained gracefully within 30 seconds |
| Active direct connections | Terminated after a 60-second grace period |
| Idle connections in pool | Cleaned up and invalidated immediately |
| New connections | Start using the new credentials right away |
Zero Downtime
If your applications use connection pools (which we highly recommend!), they'll enjoy zero downtime. The pooler takes care of refreshing the credentials seamlessly in the background. If you have apps using long-lived direct connections, they might experience a quick reconnection.
After-Rotation Checklist
Once you've rotated your credentials, it's a good idea to double-check that everything is working perfectly:
# 1. Test runtime connection
psql "$(axiom credentials url --project my-project --branch main --target runtime)" \
-c "SELECT current_user, current_database();"
# 2. Test direct connection
psql "$(axiom credentials url --project my-project --branch main --target direct)" \
-c "SELECT current_user, current_database();"
# 3. Verify application connectivity
curl -s https://your-app.example.com/health | jq '.database.status'
# Expected: "connected"
# 4. Check for connection errors in application logs
axiom logs --project my-project --branch main --filter "connection" --since "5m"The Credential Lifecycle
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Create │────▶│ Use │────▶│ Rotate │────▶│ Use │
│ │ │ │ │ │ │ (new pwd)│
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Secret │ │ Old pwd │ │ Secret │
│ Store │ │ invalid │ │ Store │
│ (v1) │ │ after │ │ (v2) │
└──────────┘ │ 60s │ └──────────┘
└──────────┘Automating Your Rotations
You can set up automatic rotation policies at the project level to handle this for you:
{
"rotation_policy": {
"enabled": true,
"interval_days": 90,
"warn_before_days": 7,
"auto_rotate": true,
"notify": ["owner", "admin"],
"target": "both"
}
}| Field | Type | Description |
|---|---|---|
enabled | boolean | Turn automatic rotation on or off. |
interval_days | integer | How often (in days) the rotation should happen. |
warn_before_days | integer | How many days in advance to send out a warning heads-up. |
auto_rotate | boolean | If set to true, we'll rotate them automatically. If false, we'll just send you a reminder. |
notify | array | Which roles should get notified before and after a rotation. |
target | string | Which credentials to target: runtime, direct, or both. |
Setting up a rotation policy
axiom credentials policy set \
--project my-project \
--interval 90 \
--auto \
--target both \
--notify owner,adminKeeping an Audit Trail
Every credential rotation is logged with complete metadata so you always have a history:
{
"event": "branch.credentials.rotated",
"timestamp": "2026-01-15T10:30:00Z",
"actor": {
"id": "usr_8xK2mP",
"email": "alice@example.com",
"role": "admin"
},
"resource": {
"project_id": "prj_abc123",
"branch_id": "br_main",
"branch_name": "main"
},
"details": {
"target": "both",
"rotation_id": "rot_9xK2mP3n",
"previous_rotation": "2025-10-15T10:30:00Z",
"trigger": "manual"
}
}A Few Security Guidelines
- No raw URLs in logs. AxiomDB automatically hides password details in all logs and UI views.
- One-time display. We only show the new credentials once right after rotation, so make sure to save them to a secure vault immediately.
- Persistent audit trails. Rotation records are kept even if you delete the branch later.
- Rate limits. To prevent abuse, you can rotate credentials up to 10 times per hour per project.
- Role permissions. You'll need to be an
adminorownerto trigger a rotation.
Password masking in logs
# What the audit log shows:
postgresql://axiom_pooler:****@pooler.axiom.cloud:6432/mydb?sslmode=require
# What the application receives (once):
postgresql://axiom_pooler:kR7x...mP9n@pooler.axiom.cloud:6432/mydb?sslmode=requireWorking with Prisma's Shadow Database
Only the owner role has the CREATEDB privileges needed to run Prisma's shadow database migrations.
// schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL") // Uses axiom_pooler (runtime)
shadowDatabaseUrl = env("SHADOW_URL") // Uses axiom_direct with owner role
}Owner credentials for Prisma
If you're using Prisma, make sure you use the owner role's direct credentials for the shadow database. Be sure to store these credentials securely and never commit them to your version control repository.
Troubleshooting Common Problems
Rotation failed: database unreachable
{
"error": "rotation_failed",
"message": "Could not connect to PostgreSQL to apply new credentials",
"step": "apply_to_postgres",
"retry_in": 30
}What went wrong: The database instance might be offline or unreachable by the control plane.
How to fix it: Check the health of your instance. If it looks healthy, wait about 30 seconds and try rotating again.
Rotation failed: secret store error
{
"error": "rotation_failed",
"message": "Failed to write new credentials to secret store",
"step": "update_secret_store"
}What went wrong: The managed secret store ran into a temporary issue.
How to fix it: This is usually a temporary hiccup. Give it another try, and if it keeps failing, reach out to support.
Application lost connection after rotation
FATAL: password authentication failed for user "axiom_pooler"What went wrong: Your application is likely still trying to use the old cached credentials.
How to fix it:
- Make sure your application has grabbed the latest credentials from your secret store.
- If you're using environment variables, redeploy your application with the new values.
- If you use a connection pool, ensure it's configured to refresh its credentials.
Related Pages
How is this guide?
