Vault Error: 'error creating database object' Database Secrets Engine Connection Failed
Fix Vault database secrets engine connection failures: correct plugin_name, connection_url templating, allowed_roles, root credential rotation, TLS trust, and network reachability.
- #vault
- #secrets
- #security-hardening
- #troubleshooting
- #errors
Stuck on this HashiCorp Vault error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Exact Error Message
Error writing data to database/config/app-postgres: Error making API request.
URL: PUT https://vault.example.com:8200/v1/database/config/app-postgres
Code: 400. Errors:
* error creating database object: error verifying connection: failed to connect to
`host=db.internal user=vault database=appdb`: dial error
(dial tcp 10.0.4.12:5432: connect: connection refused)
On credential issuance you may instead see:
Error reading database/creds/app-readonly: Error making API request.
URL: GET https://vault.example.com:8200/v1/database/creds/app-readonly
Code: 500. Errors:
* 1 error occurred:
* failed to create user: pq: password authentication failed for user "vault"
What It Means
The database secrets engine does not proxy your application’s queries — it holds one privileged root connection to the database and uses it to create short-lived, per-request users. Writing database/config/<name> opens that root connection immediately and verifies it, which is why the write itself fails rather than failing later at credential issuance. The error you get back is the driver’s own error, wrapped by Vault, so connection refused, password authentication failed, and x509: certificate signed by unknown authority all mean exactly what they would mean from psql on the same host.
The second shape — a config that writes cleanly but fails on database/creds/<role> — means the connection is fine but the root user cannot execute the role’s creation_statements. Typically the root user lacks CREATEROLE, or the statements reference a schema or grant that does not exist. Vault reports these as 500s because they occur inside the plugin’s execution of your SQL, not in request validation.
Common Causes
- The Vault server (not your laptop) cannot reach the database host or port — security group, firewall, or network policy.
connection_urlhas the credentials hardcoded instead of using the{{username}}/{{password}}templating, so root rotation silently breaks it.- The root credentials are wrong, or were rotated out-of-band by a DBA after Vault took ownership.
- The database enforces TLS and the Vault host does not trust the database’s CA.
allowed_rolesdoes not include the role you are reading, so issuance is refused even with a healthy connection.- The plugin is not registered or
plugin_nameis misspelled —postgresql-database-plugin, notpostgres. max_open_connectionsis set so low that concurrent issuance starves, producing intermittent timeouts.
Diagnostic Commands
Confirm the secrets engine is actually mounted and at the path you think:
vault secrets list -detailed | grep -i database
List the registered database plugins in the catalog. A missing plugin here is the fastest explanation for a config write that fails immediately:
vault read sys/plugins/catalog/database
vault read sys/plugins/catalog/database/postgresql-database-plugin
Read the current config. Note that Vault never returns the password — only the templated URL and role allowlist:
vault read database/config/app-postgres
Test reachability from the Vault server, since that is the host that dials the database:
nc -vz db.internal 5432
getent hosts db.internal
Test the root credential path directly with the database client, which separates network problems from auth problems:
PGPASSWORD='...' psql -h db.internal -U vault -d appdb -c 'select current_user, version();'
Watch Vault’s own log while retrying the write — the plugin logs the driver error with more context than the API response:
journalctl -u vault -f | grep -iE "database|plugin|connection"
Finally, confirm your token can even perform the operation, since a policy gap returns a 403 that is easy to confuse with a backend failure:
vault token capabilities "$(vault print token)" database/creds/app-readonly
Step-by-Step Resolution
- Register the plugin if it is missing from the catalog. Built-in plugins are present by default; only external builds need explicit registration:
vault write sys/plugins/catalog/database/custom-postgres-plugin \
sha256="$(sha256sum /etc/vault.d/plugins/custom-postgres | cut -d' ' -f1)" \
command="custom-postgres"
- Write the connection config with templated credentials. The
{{username}}and{{password}}placeholders are mandatory if you ever want root rotation to work — hardcoding them produces a config that breaks the moment you rotate:
vault write database/config/app-postgres \
plugin_name="postgresql-database-plugin" \
allowed_roles="app-readonly,app-readwrite" \
connection_url="postgresql://{{username}}:{{password}}@db.internal:5432/appdb?sslmode=verify-full" \
username="vault" \
password="$ROOT_PW" \
max_open_connections=8 \
max_idle_connections=4 \
max_connection_lifetime="5m"
- If the failure is TLS trust, install the database’s CA on the Vault host rather than weakening the connection string.
sslmode=disableandverify_connection=falseare diagnostics only — see step 4:
sudo cp db-ca.pem /usr/local/share/ca-certificates/db-ca.crt
sudo update-ca-certificates
sudo systemctl restart vault
- Diagnostic only: to prove that TLS is the failing layer and nothing else, you can write the config once with verification off. This stores a config that does not validate the database’s identity, so restore proper CA trust and rewrite the config immediately afterwards:
vault write database/config/app-postgres \
plugin_name="postgresql-database-plugin" \
allowed_roles="app-readonly" \
connection_url="postgresql://{{username}}:{{password}}@db.internal:5432/appdb?sslmode=disable" \
username="vault" password="$ROOT_PW" \
verify_connection=false
If that write succeeds where the verified one failed, the problem is CA trust, not credentials or networking. Go back to step 3.
- Rotate the root credential so only Vault knows it. Do this once, deliberately — the new password is never returned, and the old one stops working immediately:
vault write -f database/rotate-root/app-postgres
vault read database/config/app-postgres
- Define the role with
creation_statementsthe root user is actually privileged to run, then issue a credential to prove the whole path works:
vault write database/roles/app-readonly \
db_name="app-postgres" \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
revocation_statements="DROP ROLE IF EXISTS \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
vault read database/creds/app-readonly
If this returns password authentication failed or a permissions error on CREATE ROLE, grant the root user CREATEROLE in the database and retry — Vault cannot mint users it is not allowed to create.
Prevention
- Always template
{{username}}/{{password}}inconnection_urland rotate root immediately after the first successful write. - Keep
allowed_rolesexplicit rather than*, so a new role cannot accidentally borrow a privileged connection. - Set
max_ttlanddefault_ttldeliberately; long-lived dynamic credentials defeat the purpose of the engine and pile up orphaned database roles. - Size
max_open_connectionsagainst the database’s own connection limit, leaving headroom for the application’s pool. - Monitor lease counts and revocation failures — undeleted roles from failed revocations accumulate silently.
- Run a synthetic
vault read database/creds/<role>in monitoring so a broken root credential is detected before an application deploy hits it.
Related Errors
permission deniedon the creds path — a policy gap rather than a database problem. See Vault error: rate limit quota exceeded for the adjacent 429 case.no handler for route 'database/creds/...'— the engine is not mounted at that path, or you are in the wrong namespace. See Vault error: namespace not found.failed to revoke lease— revocation statements are wrong or the root credential no longer works, leaving orphaned users.Vault is sealed— nothing will work until the node unseals. See Vault error: auto-unseal KMS access denied.
Frequently Asked Questions
Why did the config write fail instead of just being stored? Vault verifies the connection at write time by default. This is deliberate — it surfaces a broken config at the moment you create it rather than during a production credential request. verify_connection=false skips that check but does not make the connection work.
Is verify_connection=false ever a legitimate long-term setting? Rarely. It is useful when the database is genuinely not reachable yet during bootstrapping, for example when Vault is configured before the database is provisioned. Outside that case it just hides a real failure until the first database/creds read.
What happens if a DBA changes the root password after rotation? Vault’s stored copy becomes stale and every issuance fails. Recover by writing the config again with the new password and immediately running vault write -f database/rotate-root/<name> so Vault is once again the only holder.
Do dynamic credentials get cleaned up if Vault is down? Leases are tracked by Vault, so revocation happens when it comes back, not while it is down. If a node was down past max_ttl you may need vault lease revoke -prefix database/creds/ to force cleanup. More engine-specific fixes live in the Vault guides.
Fixed it? Get 500 HashiCorp Vault & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.