Credential Files
A managed credential can tell the Smallstep Agent where to write its private key, certificate, and root CA certificate, and who may read each one. Applications such as PostgreSQL, Elasticsearch, or SQL Server then read their credential directly, without a hook that copies files and fixes their permissions after every renewal.
Credential files need Smallstep Agent 0.72.0 or later, and API version 2026-11-01.
Older agents don't apply most of these settings; update the agent before you use them.
How it works
A credential's files object holds one block per platform: linux, macos, and windows.
Each device receives only the block for its own operating system.
Inside a block, the key, certificate, and root entries each take their own path and permissions.
Every field is optional. A platform without a block, or a file without an entry, keeps the agent's default path and permissions.
Example: PostgreSQL on Linux
PostgreSQL won't start if anyone other than its own account can read its key. In this example, the agent writes a server credential straight into the PostgreSQL data directory, with the permissions PostgreSQL expects:
- The key, readable only by
postgres(0600). - The certificate chain, readable by everyone (
0644). - The root CA, readable by
rootand, through a POSIX ACL entry, by amonitoringgroup (0640).
1. Prepare the device
The agent writes files into existing folders, for existing accounts.
The PostgreSQL package creates the postgres account and the data directory; create the monitoring group if the device doesn't have one:
sudo groupadd --system monitoring
POSIX ACLs need a file system that supports them, such as ext4 or XFS.
2. Create the credential
Save the credential as postgres-credential.json.
Replace authorityID with your authority's ID, and the paths with your PostgreSQL version's data directory:
{
"slug": "postgres-server",
"certificate": {
"type": "X509",
"authorityID": "3c2e1115-49b7-4657-9b0f-301d7111bc6e",
"duration": "24h0m0s",
"fields": { "commonName": { "static": "db.example.internal" } }
},
"key": { "type": "ECDSA_P256", "protection": "NONE", "store": "MACHINE" },
"policy": { "operatingSystem": ["Linux"] },
"files": {
"linux": {
"key": {
"path": "/var/lib/postgresql/16/main/server.key",
"format": "PKCS8",
"owner": "postgres", "group": "postgres", "mode": "0600"
},
"certificate": {
"path": "/var/lib/postgresql/16/main/server.crt",
"contents": "CHAIN",
"owner": "postgres", "group": "postgres", "mode": "0644"
},
"root": {
"path": "/var/lib/postgresql/16/main/root.crt",
"mode": "0640",
"acl": [{ "type": "GROUP", "name": "monitoring", "access": "r" }]
}
}
}
}
Then create it with the API:
curl -X POST https://gateway.smallstep.com/api/credentials \
-H "Authorization: Bearer $SMALLSTEP_API_TOKEN" \
-H "X-Smallstep-Api-Version: 2026-11-01" \
-H "Content-Type: application/json" \
-d @postgres-credential.json
The root entry has no owner or group, so it keeps the agent's (root).
3. Check the files
Once the device has picked up the credential, check who owns each file and its mode:
$ cd /var/lib/postgresql/16/main
$ sudo stat -c '%U %G %a %n' server.key server.crt root.crt
postgres postgres 600 server.key
postgres postgres 644 server.crt
root root 640 root.crt
The root CA's access list includes the monitoring group:
$ sudo getfacl --omit-header root.crt | grep monitoring
group:monitoring:r--
If a file is missing, run sudo step-agent status: it names the file and the reason, such as an owner that doesn't exist yet.
See Credential files not written.
4. Point PostgreSQL at the files
In postgresql.conf:
ssl = on
ssl_cert_file = '/var/lib/postgresql/16/main/server.crt'
ssl_key_file = '/var/lib/postgresql/16/main/server.key'
ssl_ca_file = '/var/lib/postgresql/16/main/root.crt'
ssl_ca_file is needed only if PostgreSQL verifies client certificates.
Reload PostgreSQL to load the certificate:
sudo systemctl reload postgresql
The agent renews the certificate before it expires, but it doesn't reload PostgreSQL.
To have PostgreSQL pick up each renewed certificate, add systemctl reload postgresql to the workload's reload settings or to an after-renew hook.
Example: A service account on Windows
On Windows, each file takes an access list of principals instead of an owner and mode.
This credential lets the CORP\svc-sql domain account read the key, and nobody else besides SYSTEM and the Administrators:
{
"slug": "sql-server",
"certificate": { "type": "X509", "authorityID": "3c2e1115-49b7-4657-9b0f-301d7111bc6e", "fields": { "commonName": { "static": "sql.example.internal" } } },
"key": { "type": "ECDSA_P256", "protection": "NONE", "store": "MACHINE" },
"policy": { "operatingSystem": ["Windows"] },
"files": {
"windows": {
"key": {
"path": "D:\\certs\\sql\\server.key",
"acl": {
"inherit": false,
"entries": [{ "principal": "CORP\\svc-sql", "access": "READ" }]
}
},
"certificate": { "path": "D:\\certs\\sql\\server.crt" },
"root": { "path": "D:\\certs\\sql\\root.crt" }
}
}
}
A principal can be a local or domain account or group, a virtual service account such as NT SERVICE\MSSQLSERVER, a group managed service account such as CORP\gmsa-sql$, or a SID string such as S-1-5-21-….
By default, a Windows credential with a software key in the machine scope is also imported into the Windows key store and LocalMachine\My, next to its files.
Set "windowsStore": false on the key to keep it to its files alone, for software that reads the files and shouldn't find the credential in the stores.
It requires "protection": "NONE" and "store": "MACHINE".
Field reference
| Field | Applies to | Values | Default |
|---|---|---|---|
path | key, certificate, root | Absolute path in the platform's style | The agent's directory for the credential |
format | key | DEFAULT, PKCS8, CLASSIC, TSS2 | DEFAULT |
contents | certificate | CHAIN (leaf and intermediates) or LEAF | CHAIN |
owner, group | Linux, macOS | A name or a numeric ID, as a string | The agent's user and group |
mode | Linux, macOS | Octal string, "0000" to "0777" | "0640"; "0600" for SSH user credentials |
acl | Linux, macOS | List of {type: USER or GROUP, name, access}, access a combination of r, w, x | No extra entries |
acl.inherit | Windows | true keeps the access the folder grants | false once acl is set |
acl.entries | Windows | List of {principal, access}, access one of READ, MODIFY, FULL_CONTROL | See Windows defaults |
key.windowsStore | Windows | false keeps the credential out of the Windows stores | true |
The root CA is always written to its own file and never into the certificate file.
SSH credentials take key and certificate only.
Rules
The API refuses a credential that breaks these rules, with an error that names the field:
- A custom key path needs a software key.
key.pathis accepted only with"protection": "NONE"and withoutkey.pubFile. The agent writes the key there and nowhere else. - Paths are absolute in the platform's style (
/…on Linux and macOS,C:\…or\\server\share\…on Windows), and distinct within a block. - Settings stay on their platform.
owner,group, andmodecan't appear underwindows, and a Windows access list can't appear underlinuxormacos. A block for an operating system the credential'spolicy.operatingSystemexcludes is refused. SYSTEMkeeps full control on Windows. An access list that givesSYSTEM(NT AUTHORITY\SYSTEM,S-1-5-18) less thanFULL_CONTROLis refused, and each principal can be listed once.
The API doesn't check owners, groups, or principals: they exist only on the device. The agent reports a name it can't find when it writes the file.
Windows defaults
- The key file always grants
SYSTEMandBUILTIN\Administratorsfull control, with inheritance blocked;acl.entriesadd principals to that. Listing the Administrators withREADorMODIFYlowers their access. They can't be removed. - The certificate and root files inherit their folder's permissions unless they have an
acl. With one,SYSTEMgets full control in addition to the listed entries. - A virtual service account (
NT SERVICE\<name>) resolves only once its service is installed. Until then the agent reports the principal as not found and retries; the service's SID works before the service exists. - Domain names are resolved when the file is written, so the device needs to reach a domain controller.
How the agent writes files
- Atomically. The agent writes each file to a temporary file in the same folder, with its permissions already applied, and renames it over the target. An app never reads a half-written file, and a failed write leaves the previous file in place.
- Key first. The key is written before the certificate, and the certificate before the root. If the key can't be written, the other files aren't either, so a new certificate never sits next to an old key.
- Open files on Windows. A file another program holds open without delete sharing can't be replaced. The agent retries for about two seconds, then reports the error and tries again later.
- No folders. The agent never creates a folder for a custom path. Create it with the ownership and permissions your app expects.
- Old files stay. When a path changes, the agent writes the new path and leaves the file at the old one, including an old key. Delete it yourself.
- No restart. Writing the files doesn't restart the app. Use the workload's reload settings or hooks for that.
If a file can't be written as configured, the agent reports it on the credential's status for that device, and on Windows in the event log. See Credential files not written.
Compatibility
- API versions. The per-platform
filesobject is available in API version2026-11-01and later. Earlier versions keep the flatfilesobject (crtFile,keyFile,rootFile,uid,gid,mode), which applies the same settings on every device;2026-11-01refuses it. Earlier versions show a credential's per-platform settings in the flat shape when they can be expressed that way, and omit them otherwise. They refuse an update that would drop them. - Workloads on
2023-11-01. A workload'scertificateInfo.filestakes the same per-platform blocks. It can't be combined with the flat fields, and a save withoutfilesthat changes the flat fields is refused with412. Send"files": {}to go back to the flat fields. - Agents. Agents older than 0.72.0 apply the paths, the key format, and the key file's owner, group, mode, and ACL entries to all three files. They ignore everything else: per-file permissions,
contents, Windows access lists, andwindowsStore.
Last updated on October 26, 2026
Introducing
Device Identity
Ensure that only company-owned devices can access your enterprise's most sensitive resources.