Ansible Vault is the built-in way to keep secrets out of plain-text YAML. You can encrypt structured data files used by Ansible, including group_vars, host_vars, files loaded through vars_files or include_vars, and variable files passed with -e @file.yml.
This guide walks through file and variable encryption end-to-end: create, view, edit, encrypt, decrypt, rekey, vault password handling, vault IDs, and practical layout for course labs. Use it with Ansible variables and project layout for complete secret management. For inventory layout with encrypted group_vars and host_vars, see Vault with group_vars and host_vars.
Tested on: Rocky Linux 10.2 (Red Quartz); kernel 6.12.0-211.16.1.el10_2.0.1.x86_64; ansible-core 2.16.16.
~/ansible-project, inventory group lab, and playbooks in playbooks/. Use your own host names and paths if yours differ.
What is Ansible Vault?
Ansible Vault encrypts sensitive content so you can keep it in the same repository as playbooks without exposing raw secrets. The encrypted file stays readable by Git and review tools as ciphertext, and Ansible decrypts it at runtime when you provide a valid vault secret.
Use Vault for passwords, tokens, private keys, certificates, and app secrets that should never live in plain text.
Why Use Ansible Vault?
Without Vault, secrets leak easily through:
- plain-text
group_varsfiles committed to Git - copied snippets in tickets and chat
- old backups and editor swap files
Vault reduces that risk while keeping your playbook workflow simple. Red Hat and Ansible docs explicitly treat plain-text credential storage as a security problem.
What Should You Encrypt with Ansible Vault?
Typical values to encrypt:
- database passwords and API tokens
- cloud access keys
- TLS private key material
- service account secrets
- environment-specific confidential variables
- password hashes for Linux accounts—see manage users, groups and sudo for the
usermodule workflow
Do not encrypt everything by default. Keep non-secret values readable for easier review and debugging.
Ansible Vault Command Overview
Core commands you will use most:
| Command | Purpose |
|---|---|
ansible-vault create |
Create a new encrypted file |
ansible-vault view |
Read encrypted file contents |
ansible-vault edit |
Edit encrypted files safely |
ansible-vault encrypt |
Encrypt an existing plain-text file |
ansible-vault decrypt |
Decrypt an encrypted file back to plain text |
ansible-vault rekey |
Change vault password for encrypted content |
ansible-vault encrypt_string |
Encrypt one variable value inline |
Prepare Demo Vault Password Files
For the examples, create separate demo password files and keep them out of Git:
printf 'old-demo-vault-pass\n' > .vault-pass
printf 'dev-demo-vault-pass\n' > .vault-dev.pass
printf 'prod-demo-vault-pass\n' > .vault-prod.pass
chmod 600 .vault-pass .vault-dev.pass .vault-prod.passThese are lab examples only. In real projects, store vault password files outside the repository and manage access through your team’s approved secret process.
Create an Encrypted Vault File
Use create when you are starting a brand-new secret file.
ansible-vault create created-vault.yml --vault-password-file .vault-dev.passWith --vault-password-file, Ansible reads the password from that file, opens your default editor, and encrypts the saved content before writing it back to disk.
The file created in the test lab decrypts as:
ansible-vault view created-vault.yml --vault-password-file .vault-dev.passSample output:
api_token: created-via-createView an Encrypted Vault File
view is the safest way to inspect encrypted content without writing a plain-text copy to disk.
For the next few examples, create a plain vars file first:
cat > vars-plain.yml << 'EOF'
db_user: app
db_password: plaintext-pass
EOFThen encrypt it:
ansible-vault encrypt vars-plain.yml --vault-password-file .vault-passansible-vault view vars-plain.yml --vault-password-file .vault-passSample output:
db_user: app
db_password: plaintext-passThis command decrypts in memory for display only.
Edit an Encrypted Vault File
Use edit for routine secret changes. Ansible Vault decrypts temporarily, opens your editor, and re-encrypts on save.
ansible-vault edit created-vault.yml --vault-password-file .vault-dev.passAfter editing in the test lab, the value changed to:
ansible-vault view created-vault.yml --vault-password-file .vault-dev.passSample output:
api_token: edited-valueEncrypt an Existing File
When you already have a plain-text vars file, encrypt it in place:
cat > app-secrets.yml << 'EOF'
api_key: plain-api-key
api_secret: plain-api-secret
EOFansible-vault encrypt app-secrets.yml --vault-password-file .vault-passencrypt modifies the file in place. After this command, app-secrets.yml is replaced by Vault ciphertext. Keep version control history or a secure backup if you need to audit the original content.
Decrypt an Encrypted File
Decrypt only when absolutely necessary.
ansible-vault decrypt vars-plain.yml --vault-password-file .vault-passThis writes plain text back to disk. Treat the file as exposed until you re-encrypt it:
ansible-vault encrypt vars-plain.yml --vault-password-file .vault-passChange Vault Password with rekey
Rotate vault passwords without changing file contents:
ansible-vault rekey vars-plain.yml --vault-password-file .vault-pass --new-vault-password-file .vault-dev.passSample output:
Rekey successfulUse this after team access changes, credential rotation policies, or suspected compromise. After rekey, future commands for this file should use the new password source (for example .vault-dev.pass in this lab).
Use Vault Files in Playbooks
Vault files load like normal vars files. Only runtime secret handling changes.
playbook-vault.yml in the test lab:
---
- hosts: localhost
connection: local
gather_facts: false
vars_files:
- vars-plain.yml
tasks:
- ansible.builtin.debug:
msg: "db_user={{ db_user }}"Run with the vault password file:
ansible-playbook playbook-vault.yml --vault-password-file .vault-dev.passSample output:
PLAY [localhost] ***************************************************************
TASK [ansible.builtin.debug] ***************************************************
ok: [localhost] => {
"msg": "db_user=app"
}debug. Use no_log: true on tasks that handle passwords, tokens, or private keys.
Ansible Vault is not a full secrets manager—it protects files at rest in your repo, not rotation, audit trails, or fine-grained access control. At runtime, decrypted values can still appear in Ansible output when you run with high verbosity (-vvv or higher) or when modules echo arguments. Avoid ansible-playbook --diff on tasks that template or copy vaulted content carelessly; diff output can expose secrets in your terminal and logs. Pair Vault with no_log: true and external secret stores when requirements grow beyond course labs.
Encrypt Variables Instead of Whole Files
When only one value is sensitive, use encrypt_string and keep the rest of the file readable:
ansible-vault encrypt_string --vault-password-file .vault-dev.pass --name 'vault_db_password' 'S3cret!Pass'Sample output:
vault_db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
30343034373663653265333835613366376562396462373065366534666161666532383732313236
3732633434303261636564383033653138353234326237640a313131303361653862353339386539
33326638353962313838313735306637366632366230336366643036663739643039663663643037
3064373939326366300a616664323639653434376361663434336636386339343065363433353237
3934The --name value becomes the variable name. The encrypted block becomes its value. In playbooks and templates, you still reference it normally as {{ vault_db_password }} after supplying a vault password at runtime.
Vault Password Options
Use --ask-vault-pass
For interactive runs:
ansible-playbook site.yml --ask-vault-passBest for ad hoc manual execution on trusted terminals.
Use --vault-password-file
For non-interactive CI/CD or repeatable local runs:
ansible-playbook site.yml --vault-password-file .vault-dev.passKeep password files outside Git and chmod 600 them.
Use ANSIBLE_VAULT_PASSWORD_FILE
Set a runtime default to avoid repeating the CLI flag:
ANSIBLE_VAULT_PASSWORD_FILE=.vault-dev.pass ansible-vault view created-vault.ymlSample output:
api_token: created-via-createYou can also configure this path in ansible.cfg with vault_password_file.
Use Vault IDs
Why vault IDs are useful
Vault IDs label encrypted content by context such as dev, stage, or prod. They help organize multiple vault passwords and guide which secret Ansible should try first. If you need strict matching, enable vault_id_match in ansible.cfg.
[defaults]
vault_id_match = trueCreate encrypted content with a vault ID
Start with separate plain-text files per environment, then encrypt each one with its own label and password source.
cat > dev-secrets.yml << 'EOF'
dev_token: DEV123
EOFNow encrypt the dev file with the dev vault ID:
ansible-vault encrypt dev-secrets.yml --vault-id [email protected]Repeat for the production file:
cat > prod-secrets.yml << 'EOF'
prod_token: PROD456
EOFansible-vault encrypt prod-secrets.yml --vault-id [email protected]Verify both files are encrypted and labeled:
head -1 dev-secrets.ymlSample output:
$ANSIBLE_VAULT;1.2;AES256;devhead -1 prod-secrets.ymlSample output:
$ANSIBLE_VAULT;1.2;AES256;prodThe vault ID label (dev or prod) in the header helps Ansible choose which secret to try first at runtime.
Run playbooks with a vault ID
If your playbook only needs one environment secret file, pass one matching vault ID:
ansible-playbook site.yml --vault-id [email protected]Use multiple vault IDs
playbook-vault-ids.yml in the test lab loads both files:
---
- hosts: localhost
connection: local
gather_facts: false
vars_files:
- dev-secrets.yml
- prod-secrets.yml
tasks:
- ansible.builtin.debug:
msg: "dev={{ dev_token }} prod={{ prod_token }}"Run with both IDs:
ansible-playbook playbook-vault-ids.yml --vault-id [email protected] --vault-id [email protected]Sample output:
TASK [ansible.builtin.debug] ***************************************************
ok: [localhost] => {
"msg": "dev=DEV123 prod=PROD456"
}Store Vault Files with group_vars and host_vars
A clean inventory layout:
inventory/
├── group_vars/
│ ├── all.yml
│ ├── web.yml
│ └── web_vault.yml # ansible-vault encrypted
└── host_vars/
├── web1.yml
└── web1_vault.yml # ansible-vault encryptedA common pattern is to keep non-secret values in web.yml and secrets in web_vault.yml. Both load for the same group, but only the vault file needs encryption.
# inventory/group_vars/web.yml
web_port: 8080
db_user: app
db_password: "{{ vault_db_password }}"# inventory/group_vars/web_vault.yml
vault_db_password: !vault |
...Keep naming explicit (*_vault.yml) so reviewers know files are expected to be encrypted.
Use Vault with ansible-playbook
ansible-playbook is the standard runtime path for Vault:
ansible-playbook site.yml --ask-vault-passor
ansible-playbook site.yml --vault-password-file .vault-dev.passFor automation, prefer password files or vault IDs over manual prompts.
Use Vault with ansible-navigator
ansible-navigator can pass Vault options through to the underlying playbook run, but execution environments add one extra concern: the password file or environment variable must be available inside the execution environment.
For local or non-EE usage, pass the same playbook options you use with ansible-playbook:
ansible-navigator run site.yml -- --vault-password-file .vault-dev.passFor execution-environment workflows, prefer ANSIBLE_VAULT_PASSWORD_FILE and pass the environment variable into the run based on your navigator configuration.
In this test environment, ansible-navigator was not installed, so runtime behavior was validated with ansible-playbook.
Common Ansible Vault Mistakes
| Mistake | Impact | Fix |
|---|---|---|
| Commit vault password files to Git | Secrets exposed | Add password files to .gitignore and use strict permissions |
| Decrypt a file and forget to re-encrypt | Plain-text secrets in repo | Re-encrypt immediately after edits |
| Encrypt non-secret values blindly | Harder review and troubleshooting | Encrypt only secret values or files |
| Mix vault IDs without naming discipline | Wrong password at runtime | Use clear labels (dev, prod) and matching filenames |
Assume -e @file.yml is safe by default |
Plain-text secrets on disk | Encrypt that vars file or switch to encrypt_string |
| No backup for vault password | Permanent data loss risk | Store recovery process in secure team docs |
Common Vault Errors and Fixes
| Error / symptom | Likely cause | Fix |
|---|---|---|
Attempting to decrypt but no vault secrets found |
No vault password option passed | Use --ask-vault-pass, --vault-password-file, or --vault-id |
Decryption failed |
Wrong password or wrong vault ID | Check the file header and password source |
| Playbook prompts for password in CI | Interactive option used | Use password file or vault ID in CI |
| Encrypted file starts with normal YAML | File was never encrypted | Run ansible-vault encrypt file.yml |
| Secret appears in output | Task/debug printed decrypted value | Avoid printing secrets; use no_log: true where needed |
Ansible Vault Best Practices
- Keep variable names readable even when values are encrypted.
- Encrypt secrets only; keep structure and non-sensitive metadata plain.
- Use vault IDs per environment and rotate with
rekey. - Store password files outside repositories and lock permissions.
- Prefer
viewandeditover manual decrypt/re-encrypt loops. - Audit CI jobs to ensure they never echo vault passwords.
Recommended Vault Structure for Small Labs
For small Ansible labs:
- Keep base defaults in
inventory/group_vars/all.yml. - Put secrets in
inventory/group_vars/lab_vault.ymlencrypted with Vault. - Use
--vault-password-file ~/.vault_lab.passlocally. - Add vault IDs only when splitting environments (
devandprod). - Use
encrypt_stringfor one-off secrets in otherwise readable files.
This keeps day-to-day tasks simple while preserving safe secret handling.
Summary
Ansible Vault secures secrets in playbook workflows by encrypting files or individual variable values. You can create, view, edit, encrypt, decrypt, and rekey vaulted content, then load it normally through vars_files, group_vars, host_vars, or -e @file.yml with runtime password options. Use vault IDs to separate environment secrets, keep password files out of Git, and encrypt only what is truly sensitive.

