Security

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.

TargetDescriptionPortRole
runtimePgBouncer pooler credentials6432axiom_pooler
directDirect PostgreSQL credentials5432axiom_direct
bothBoth runtime and direct6432 + 5432Both 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 runtime

Rotate both credential sets

axiom credentials rotate \
  --project my-project \
  --branch main \
  --target both

Output

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:

PropertyValue
Length32 characters
Character setA-Z, a-z, 0-9, !@#$%^&*
Entropy≥ 192 bits
AlgorithmCSPRNG (OS-level)
UniquenessNever reused across rotations or projects

Atomic Process

The rotation is designed to be atomic. If any step fails, the entire operation is rolled back:

  1. Generate password: A new password is generated in memory.
  2. Apply to PostgreSQL: ALTER ROLE updates the password in the database catalog.
  3. Update secret store: The new password is written to the managed secret store.
  4. Reload PgBouncer: PgBouncer is signaled to re-read credentials.
  5. Write audit event: The rotation is logged.
  6. 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  | t

Impact on Existing Connections

ScenarioBehavior
Active PgBouncer connectionsDrained gracefully within 30 seconds
Active direct connectionsTerminated after 60-second grace period
Idle connections in poolInvalidated immediately
New connectionsUse 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"
  }
}
FieldTypeDescription
enabledbooleanWhether automatic rotation is enabled.
interval_daysintegerHow often to rotate (in days).
warn_before_daysintegerDays before rotation to send warning notifications.
auto_rotatebooleanIf true, rotation happens automatically. If false, only notifications are sent.
notifyarrayRoles to notify before and after rotation.
targetstringWhich 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,admin

Audit 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

  1. Never log raw URLs. AxiomDB masks the password segment in all logs and UI displays.
  2. Single display. Credentials are shown exactly once after rotation. Store them in a secure vault immediately.
  3. Audit retention. Rotation events outlive branch deletion. Even after a branch is deleted, the audit record persists.
  4. Rate limiting. Rotation requests are rate-limited to 10 per hour per project to prevent abuse.
  5. Role requirement. Only admin and owner roles 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=require

Prisma 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:

  1. Verify the application is using the latest credentials from the secret store.
  2. If using environment variables, redeploy the application with updated values.
  3. If using a connection pool, ensure the pool refreshes credentials.

On this page