Ansible Vault as an At-Rest Secret Boundary: Architecture, Checks, and Failure Modes
How to encrypt sensitive Ansible variables at rest with Ansible Vault, keep decryption on the control node, verify behavior with dry runs, and recognize when a secrets manager is the better boundary.
10 Aug 2026, 22:07 UTC

The problem: credentials in version control
You need a database password inside group_vars/production/secrets.yml so a playbook can configure an application. Committing that file as plaintext exposes the credential to everyone with repository access, including CI logs and forks. The useful takeaway is that Ansible Vault encrypts the file at rest so repository readers see ciphertext, while the control node decrypts it during execution and passes only task arguments to managed nodes.
Requirements and version assumptions
Ansible Vault ships with ansible-core; the ansible-vault command is available on the control node. You need a vault password that is stored outside the repository, such as in a secrets manager or a local file with restricted permissions. Managed nodes do not need Ansible installed for vault decryption because decryption happens on the control node before tasks are dispatched.
If you plan to use multiple vault passwords with distinct labels, confirm your controller supports multiple --vault-id arguments. Older ansible-core releases before multi-vault-id support may require a single vault password for all encrypted content. Check the controller version before designing a multi-environment vault layout.
Smallest suitable design
For a single environment, the smallest workable design uses one vault password and one encrypted file. Store the password in a file readable only by the control-node user, then encrypt the variables file that holds secrets.
Run the following on the control node, where you have write access to the inventory directory and read access to the password file:
ansible-vault encrypt group_vars/webservers/secrets.yml --vault-id prod@/path/to/.vault_pass_prod
Replace prod with your chosen vault ID label and /path/to/.vault_pass_prod with the actual password file path. After encryption, the file begins with an $ANSIBLE_VAULT header; the original plaintext variables are no longer present in the working tree. Never place the password file inside the same Git repository as the vaulted content.
To preview changes, run the playbook in check mode. Decryption still occurs, so the vault credential is required even for a dry run:
ansible-playbook site.yml --check --diff --vault-id prod@/path/to/.vault_pass_prod
Expected check: the playbook parses variables and reports what would change without applying it. If the password is wrong or missing, the run fails before task execution.
Trust and data boundaries
The vault password is the root of trust. It must exist only on the control node and in your secrets store. Ansible decrypts vaulted variables on the control node, substitutes them into task arguments, and sends those arguments to managed nodes over the existing transport. Managed nodes never receive the vault password, so they sit outside the vault trust boundary.
That boundary has two important consequences. First, vault protects files at rest in version control; it does not encrypt data in transit or protect secrets once they are written to a managed node's filesystem. SSH transport security and become-password handling require separate controls. Second, decrypted values can leak through verbose output. When a task references a vaulted variable, set no_log: true on that task so callback and verbose logs do not print the value:
- name: Configure application database connection
template:
src: app.conf.j2
dest: /etc/app/app.conf
no_log: true
Without no_log, running with -v or -vvv can print the decrypted secret to the console and to any log aggregation system capturing controller output.
Operational checks
Verify the design with three concrete tests on the control node.
- Positive path. Run the playbook with the correct vault credential and confirm it succeeds and uses the decrypted value. You can inspect the ciphertext without decrypting to disk using
ansible-vault view group_vars/webservers/secrets.yml --vault-id prod@/path/to/.vault_pass_prod. - Negative path. Run the same playbook without any vault credential. The run should fail with a decryption error before applying changes. There is no automatic plaintext fallback, which prevents silent exposure but means password rotation must be coordinated.
- Leakage check. Run a task that consumes the secret with
-vand confirm withno_log: truethat the secret value does not appear in the output.
These checks confirm both the security boundary and the operational behavior. If the negative path succeeds instead of failing, the file was not actually encrypted or the variable is being read from an unencrypted source.
Failure modes
A missing or wrong vault ID causes a hard failure before task execution. During password rotation, this blocks runs until every controller and CI runner has the new credential. Plan rotation by staging the new password in your secrets store before revoking the old one.
Another failure mode is storing the vault password in the repository, in a CI variable visible to all jobs, or in a world-readable file on the control node. Any of these choices collapses the at-rest boundary. Use filesystem permissions and secrets-manager retrieval to limit access to the password itself.
Verbose debugging is a third failure mode. A debug task or a module argument that echoes a vaulted variable will print the plaintext value unless no_log suppresses it. Audit playbooks that consume secrets for missing no_log directives before enabling verbose logging in shared environments.
Conditions that would change the design
Ansible Vault is the right boundary when secrets are consumed by the control node during playbook execution and the encrypted files live in version control. Change the design when the access pattern no longer matches that boundary.
- Many target hosts or external systems need direct secret access. If applications on managed nodes must fetch credentials themselves, or if non-Ansible consumers need the same secrets, replace vaulted files with a secrets-manager lookup plugin. The lookup retrieves values at runtime from the manager rather than distributing encrypted files.
- Secrets must be protected on the target host. Vault does not encrypt files after they are written to a managed node. If the target filesystem is untrusted, use a different mechanism for at-rest protection on the host.
- Multiple environments with separate passwords. If you need distinct vault passwords per environment, confirm your controller supports multiple
--vault-idlabels. If it does not, either upgrade the controller or maintain separate inventories with a single password each.
Practical verification
After implementing the design, confirm the result by encrypting a test variable file, running a playbook with --vault-id, and observing that the task succeeds. Then remove the credential and observe the expected decryption failure. Finally, run with -v and confirm that no_log prevents the secret from appearing. If all three checks pass, the at-rest boundary is working as intended for your control-node-centric workflow.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.