Credential Rotation
Rotate Postgres credentials atomically across all layers — database, pooler, and secret store — with zero downtime and full audit trails.
Credential Rotation
AxiomDB provides atomic credential rotation that updates passwords across PostgreSQL, PgBouncer, and the managed secret store in a single transactional operation. The process is designed for zero downtime and full traceability.
Overview
Credential rotation is a critical security operation. AxiomDB handles the complexity of updating credentials across multiple layers while ensuring no connection is left with 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 │ │
│ └────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘Rotation Targets
You can rotate credentials for one or both connection types in a single operation.
| 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
The runtime role connects through PgBouncer and is suitable for most application workloads. The
direct role connects directly to PostgreSQL and is required for operations that PgBouncer does
not support (e.g., PREPARE, LISTEN/NOTIFY, certain SET commands).
Rotation via CLI
Rotate runtime credentials
axiom credentials rotate \
--project my-project \
--branch main \
--target runtimeRotate both credential sets
axiom credentials rotate \
--project my-project \
--branch main \
--target bothOutput
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.Rotation via 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"
}URLs shown once
The password and url fields are only included in the rotation response. They cannot be
retrieved via any other API call. If you lose the credentials, you must rotate again.
Password Generation
AxiomDB generates cryptographically secure passwords with the following properties:
| 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 |
Atomic Process
The rotation is designed to be atomic. If any step fails, the entire operation is rolled back:
- Generate password: A new password is generated in memory.
- Apply to PostgreSQL:
ALTER ROLEupdates the password in the database catalog. - Update secret store: The new password is written to the managed secret store.
- Reload PgBouncer: PgBouncer is signaled to re-read credentials.
- Write audit event: The rotation is logged.
- Return URLs: The new connection strings are returned to the caller.
If step 2 fails (e.g., database is unreachable), the operation aborts and no changes are persisted. If step 3 fails, the PostgreSQL password change is rolled back. If step 4 fails, the operation is retried up to 3 times before rolling back.
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 | tImpact on Existing Connections
| Scenario | Behavior |
|---|---|
| Active PgBouncer connections | Drained gracefully within 30 seconds |
| Active direct connections | Terminated after 60-second grace period |
| Idle connections in pool | Invalidated immediately |
| New connections | Use new credentials immediately |
Zero downtime
Applications using connection pools (recommended) will experience zero downtime. The pooler handles credential refresh transparently. Applications with long-lived direct connections may see a brief reconnection event.
Post-Rotation Checklist
After rotating credentials, verify the following:
# 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"Credential Lifecycle
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Create │────▶│ Use │────▶│ Rotate │────▶│ Use │
│ │ │ │ │ │ │ (new pwd)│
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Secret │ │ Old pwd │ │ Secret │
│ Store │ │ invalid │ │ Store │
│ (v1) │ │ after │ │ (v2) │
└──────────┘ │ 60s │ └──────────┘
└──────────┘Rotation Policies
You can configure automatic rotation policies at the project level:
{
"rotation_policy": {
"enabled": true,
"interval_days": 90,
"warn_before_days": 7,
"auto_rotate": true,
"notify": ["owner", "admin"],
"target": "both"
}
}| Field | Type | Description |
|---|---|---|
enabled | boolean | Whether automatic rotation is enabled. |
interval_days | integer | How often to rotate (in days). |
warn_before_days | integer | Days before rotation to send warning notifications. |
auto_rotate | boolean | If true, rotation happens automatically. If false, only notifications are sent. |
notify | array | Roles to notify before and after rotation. |
target | string | Which credentials to rotate: runtime, direct, or both. |
Setting a rotation policy
axiom credentials policy set \
--project my-project \
--interval 90 \
--auto \
--target both \
--notify owner,adminAudit Trail
Every rotation is recorded in the audit log with full metadata:
{
"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"
}
}Security Considerations
- Never log raw URLs. AxiomDB masks the password segment in all logs and UI displays.
- Single display. Credentials are shown exactly once after rotation. Store them in a secure vault immediately.
- Audit retention. Rotation events outlive branch deletion. Even after a branch is deleted, the audit record persists.
- Rate limiting. Rotation requests are rate-limited to 10 per hour per project to prevent abuse.
- Role requirement. Only
adminandownerroles can rotate credentials.
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=requirePrisma Shadow Database
The owner role is the only role with CREATEDB privileges. This is required for Prisma's shadow
database mechanism during 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 use Prisma, you need the owner role's direct credentials for the shadow database. These credentials should be stored securely and never committed to version control.
Troubleshooting
Rotation failed: database unreachable
{
"error": "rotation_failed",
"message": "Could not connect to PostgreSQL to apply new credentials",
"step": "apply_to_postgres",
"retry_in": 30
}Cause: The database instance is down or unreachable from the control plane.
Fix: Check instance health. If healthy, retry the rotation after 30 seconds.
Rotation failed: secret store error
{
"error": "rotation_failed",
"message": "Failed to write new credentials to secret store",
"step": "update_secret_store"
}Cause: The managed secret store is experiencing issues.
Fix: This is a transient error. Retry the rotation. If it persists, contact support.
Application lost connection after rotation
FATAL: password authentication failed for user "axiom_pooler"Cause: Application is using cached credentials from before the rotation.
Fix:
- Verify the application is using the latest credentials from the secret store.
- If using environment variables, redeploy the application with updated values.
- If using a connection pool, ensure the pool refreshes credentials.
Related Pages
Teams & Roles
Role-based access control for AxiomDB projects with granular permissions for owners, admins, developers, viewers, and billing users.
Audit Log
Comprehensive audit logging for all security-sensitive operations in AxiomDB — network changes, credential rotations, team modifications, and more.