TL;DR — Quick Summary

Ansible Vault encrypts secrets in playbooks with AES-256. Learn vault create, encrypt_string, vault IDs, CI/CD integration, and password rotation.

ANSIBLE VAULT — AES-256 SECRETS ENCRYPTION PLAINTEXT (DANGER) db_pass: supersecret api_key: abc123xyz ssl_key: -----BEGIN... Visible in git history! ansible-vault encrypt ENCRYPTED (SAFE TO COMMIT) $ANSIBLE_VAULT;1.1;AES256 66386439653763616335616... 32663930343936663766303... 61373039636131336531353... AES-256 encrypted blob --vault-password -file / --ask-vault -pass RUNTIME (MEMORY) db_pass: supersecret api_key: abc123xyz ssl_key: -----BEGIN... Decrypted in memory only Vault password never touches disk during playbook execution — only the encrypted blob is stored in git

Storing database passwords, API keys, and SSL private keys in plain text inside your Ansible playbooks is one of the most common security mistakes in infrastructure automation. Once a secret lands in a git repository unencrypted, it is effectively compromised — git history persists forever, and anyone with read access to the repo can retrieve it. Ansible Vault solves this by encrypting secrets directly inside your repository using AES-256, so you can commit encrypted credential files alongside your playbooks without exposing anything sensitive.

Prerequisites

Before working through this guide, you should have:

  • Ansible 2.8 or later installed on your control node (ansible --version to verify)
  • Basic familiarity with Ansible playbooks and group_vars directory structure
  • An existing Ansible project with inventory and at least one playbook
  • A text editor configured as your $EDITOR (nano, vim, or VS Code with the remote extension)
  • For CI/CD sections: a GitHub Actions, GitLab CI, or Jenkins pipeline you can modify

New to Ansible? Read our Ansible for Beginners guide first to set up your project structure before adding Vault.

Why Plaintext Secrets in Playbooks Are Dangerous

Every secret stored in plain text in a git repository carries the same risk: exposure. Consider what typically ends up in Ansible variable files without Vault:

  • Database root passwords in group_vars/dbservers.yml
  • AWS access keys in host_vars/deploy.yml
  • SMTP credentials in roles/notifications/defaults/main.yml
  • SSL private key content in a vars: block inside a playbook

Any of the following events exposes these secrets instantly: a repository accidentally made public, a contributor’s GitHub account compromised, a leaked CI/CD log, or a disgruntled former employee. Ansible Vault prevents this by ensuring that the files stored in git contain only AES-256 encrypted ciphertext, not the actual secret values.

The vault password itself never enters your repository. It stays in your head, in a password manager, or in your CI/CD secrets store — separate from the code.


Creating Encrypted Files with ansible-vault create

The create subcommand opens your configured $EDITOR with an empty file, lets you type your secrets, and saves the file encrypted when you close the editor:

ansible-vault create group_vars/all/vault.yml

You will be prompted for a new vault password (twice for confirmation). The editor opens. Enter your secrets:

vault_db_password: "Tr0ub4dor&3"
vault_db_root_password: "R4nd0mS33d#9"
vault_api_key: "sk-live-abc123def456ghi789"
vault_smtp_password: "M@ilS3rv1c3P@ss"
vault_ssl_private_key: |
  -----BEGIN RSA PRIVATE KEY-----
  MIIEpAIBAAKCAQEA3cH7...
  -----END RSA PRIVATE KEY-----

Save and close. The file on disk now contains only the encrypted blob:

$ANSIBLE_VAULT;1.1;AES256
66386439653763616335616230333934356262313032393132376163633335636630
34663437326664303537373561373039636131336531353364386530636431343739
...

Editing and viewing encrypted files

# Open in editor for modifications
ansible-vault edit group_vars/all/vault.yml

# Display decrypted content without editing
ansible-vault view group_vars/all/vault.yml

# Encrypt an existing plain-text file
ansible-vault encrypt secrets.yml

# Decrypt a file back to plain text (use with caution)
ansible-vault decrypt secrets.yml

Encrypting Inline Variables with encrypt_string

Sometimes you want a single secret inside a non-vault file — perhaps because you want the surrounding context visible without decrypting everything. The encrypt_string subcommand generates an encrypted value you can paste directly into any YAML file:

ansible-vault encrypt_string --name 'vault_stripe_key' 'sk_live_abc123xyz'

Output:

vault_stripe_key: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  66653465346564383664396631316639
  37313739623731633864333736396230
  ...

Paste this block directly into any YAML variable file. Ansible decrypts it transparently at runtime, the same way it handles a fully encrypted vault file.

When to use encrypt_string vs encrypted files:

ApproachUse when
ansible-vault create vault.ymlMost cases — keeps secrets organized, easy to audit
encrypt_stringSingle secret in a mostly-plain file; inline secrets in roles
Encrypted entire playbookRarely needed; breaks readability

Vault IDs: Multiple Passwords for Different Secret Sets

Vault IDs let you label encrypted content so Ansible knows which password applies. This is essential when staging and production use different passwords, or when you want to rotate one password without re-encrypting everything.

Encrypting with a vault ID

# Encrypt with a named vault ID
ansible-vault create --vault-id prod@prompt group_vars/production/vault.yml
ansible-vault create --vault-id staging@prompt group_vars/staging/vault.yml

# Or point to a password file
ansible-vault create --vault-id prod@~/.vault_prod group_vars/production/vault.yml

Decrypting with multiple vault IDs

ansible-playbook site.yml \
  --vault-id prod@~/.vault_prod \
  --vault-id staging@~/.vault_staging

Ansible automatically matches each encrypted block to its vault ID label and uses the correct password. Content encrypted without an explicit vault ID defaults to the label default.


CI/CD Integration with —vault-password-file

Interactive password prompts break automated pipelines. The --vault-password-file flag accepts a path to a file containing only the vault password, enabling fully non-interactive execution.

GitHub Actions example

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Ansible
        run: pip install ansible

      - name: Write vault password to file
        run: echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > ~/.vault_pass
        # Note: file permissions are set automatically; delete in cleanup

      - name: Run playbook
        run: |
          ansible-playbook -i inventory/production site.yml \
            --vault-password-file ~/.vault_pass

      - name: Remove vault password file
        if: always()
        run: rm -f ~/.vault_pass

Store ANSIBLE_VAULT_PASSWORD in your repository’s GitHub Actions secrets (Settings → Secrets → Actions). The vault password never appears in logs because GitHub masks secret values.

GitLab CI example

# .gitlab-ci.yml
deploy:
  script:
    - echo "$ANSIBLE_VAULT_PASSWORD" > ~/.vault_pass
    - ansible-playbook -i inventory/production site.yml --vault-password-file ~/.vault_pass
    - rm -f ~/.vault_pass
  variables:
    ANSIBLE_VAULT_PASSWORD: $ANSIBLE_VAULT_PASSWORD

Using a vault password script

For dynamic vault passwords (fetched from HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault at runtime), create an executable script that outputs the password:

#!/bin/bash
# ~/.get_vault_pass.sh
aws secretsmanager get-secret-value \
  --secret-id ansible/vault-password \
  --query SecretString \
  --output text
chmod +x ~/.get_vault_pass.sh
ansible-playbook site.yml --vault-password-file ~/.get_vault_pass.sh

Best Practices: Organizing Vaulted Variables

The most maintainable pattern separates your variable files into two parallel files per group:

group_vars/
  all/
    vars.yml      # plain variables + references to vault_ vars
    vault.yml     # encrypted, only vault_ prefixed variables
  production/
    vars.yml
    vault.yml
  staging/
    vars.yml
    vault.yml

group_vars/all/vars.yml — plain text, safe to inspect in git:

# group_vars/all/vars.yml
db_host: "db.internal.example.com"
db_port: 5432
db_name: "myapp_prod"
db_password: "{{ vault_db_password }}"    # reference, not the value

api_endpoint: "https://api.stripe.com/v1"
stripe_key: "{{ vault_stripe_key }}"

smtp_host: "smtp.sendgrid.net"
smtp_user: "apikey"
smtp_password: "{{ vault_smtp_password }}"

group_vars/all/vault.yml — encrypted, all vault_ prefixed:

# group_vars/all/vault.yml  (this file is encrypted on disk)
vault_db_password: "Tr0ub4dor&3"
vault_stripe_key: "sk_live_abc123"
vault_smtp_password: "SG.abc123xyz"

Benefits of this pattern:

  • Playbooks reference human-readable names (db_password), not vault variables directly
  • git diff shows changes to vars.yml structure without requiring vault password
  • You can audit which variables exist in the vault file by name (run ansible-vault view)
  • New team members can read vars.yml to understand the variable structure without vault access

Rotating Vault Passwords with rekey

Rotating the vault password is a one-command operation:

ansible-vault rekey group_vars/all/vault.yml

You will be prompted for the current vault password, then asked to enter and confirm the new password. The file is re-encrypted in place. No secrets are decrypted to disk during this process.

For multiple vault files at once:

# Rekey all vault files in the project
find . -name "vault.yml" -exec ansible-vault rekey {} \;

# Or with --new-vault-password-file for non-interactive rotation
ansible-vault rekey \
  --vault-password-file ~/.old_vault_pass \
  --new-vault-password-file ~/.new_vault_pass \
  group_vars/all/vault.yml group_vars/production/vault.yml

After rekeying, update your CI/CD secret store with the new password before the next pipeline run.


Using Ansible Vault with AWX and Semaphore

AWX / Ansible Tower

AWX has a native credential type for Ansible Vault. Create a credential of type Vault (Settings → Credentials → Add → Vault), enter the vault password, and assign it to your job template. AWX handles passing the password securely without exposing it in job output.

For multiple vault IDs in AWX, create one Vault credential per ID and assign all of them to the job template.

Semaphore UI

Semaphore stores vault passwords in its Key Store. Navigate to Key Store → New Key, select type Password, enter your vault password, and name it (e.g., vault-production). When creating a task template, select the vault key in the Vault Password field.

Semaphore passes the key to Ansible via a temporary file that is deleted after the playbook run, following the same security model as the CI/CD password-file approach.


Comparison: Ansible Vault vs Alternatives

ToolEncryptionGit-nativeCI/CDDynamic secretsComplexity
Ansible VaultAES-256Yes — files in repoPassword fileVia scriptLow
HashiCorp VaultAES-256-GCMNo — external serviceAPI + tokenYes (leases)High
SOPSAES-256 / PGP / KMSYes — encrypted valuesKMS keyNoMedium
git-cryptAES-256Yes — transparentGPG keyNoMedium

Choose Ansible Vault when your secrets only need to be accessible by Ansible playbooks and you want zero additional infrastructure. Choose HashiCorp Vault when you need dynamic secrets, fine-grained lease expiration, or multi-application secret access. SOPS is useful when you need encrypted files readable by tools other than Ansible (Terraform, Helm). git-crypt works well for general encrypted files in a repo but lacks Ansible-specific integrations.


Real-World Scenario: Database Credentials and API Keys Across Environments

You manage three environments (development, staging, production) for a web application. Each environment has a PostgreSQL database, a Stripe API key, and a SendGrid SMTP password. Development uses weak local passwords; production uses strong credentials managed by the security team.

Directory layout:

project/
  ansible.cfg
  inventory/
    development/hosts
    staging/hosts
    production/hosts
  group_vars/
    all/
      vars.yml         # shared variable structure
    development/
      vars.yml         # non-sensitive dev config
      vault.yml        # encrypted dev secrets (separate password)
    staging/
      vars.yml
      vault.yml        # encrypted staging secrets
    production/
      vars.yml
      vault.yml        # encrypted production secrets (most restricted)
  playbooks/
    deploy.yml
    db_setup.yml

Each environment’s vault.yml uses a separate vault ID and password, managed by the appropriate team:

# Create development vault (developer team password)
ansible-vault create --vault-id dev@prompt group_vars/development/vault.yml

# Create staging vault (QA team password)
ansible-vault create --vault-id staging@prompt group_vars/staging/vault.yml

# Create production vault (ops team password)
ansible-vault create --vault-id prod@prompt group_vars/production/vault.yml

Deployment pipeline (production only):

ansible-playbook -i inventory/production playbooks/deploy.yml \
  --vault-id prod@~/.vault_prod

Staging pipeline (QA engineer can run):

ansible-playbook -i inventory/staging playbooks/deploy.yml \
  --vault-id staging@~/.vault_staging

The production vault password is never shared with developers or QA. If the QA vault is compromised, production secrets are unaffected because they use a completely separate password.


Gotchas and Edge Cases

Forgetting the vault password is permanent. There is no recovery mechanism. The encrypted data is gone. Store vault passwords in a password manager and a CI/CD secret immediately after creation.

Avoid --ask-vault-pass in automation. It blocks pipelines and requires interactive input. Always use --vault-password-file in any non-interactive context.

ansible.cfg can set vault defaults. Add vault_password_file = ~/.vault_pass to your ansible.cfg to avoid specifying it on every command. Be aware that this affects everyone who uses the same ansible.cfg.

vault.yml files should never be in .gitignore. The entire point is to commit them encrypted. If you are tempted to gitignore a vault file, you have probably made it plain text by accident.

Encrypted files still show as changed in git when re-encrypted identically. AES-256 in CBC mode produces different ciphertext for the same plaintext each time. Do not ansible-vault encrypt unchanged files as it pollutes git history. Only edit and re-encrypt when secrets actually change.

Binary files can also be encrypted. SSL certificates, keystore files, and SSH keys in binary format can all be encrypted with ansible-vault encrypt. Ansible decrypts them to a temp file before passing the path to the relevant module.


Troubleshooting

“Decryption failed” error:

# Verify the password is correct by viewing the file
ansible-vault view group_vars/all/vault.yml

# Check that the vault ID matches if using vault IDs
ansible-vault view --vault-id prod@prompt group_vars/production/vault.yml

Playbook runs without prompting for vault password and uses wrong value:

Check if vault_password_file is set in ansible.cfg. If the file does not exist or is empty, Ansible silently skips vault decryption. Add --ask-vault-pass temporarily to confirm.

“File is not encrypted” error when editing:

The file is stored as plain text. Encrypt it first: ansible-vault encrypt filename.yml.

Variable shows as literal string {{ vault_db_password }} in task output:

The variable is referenced but the vault file was not loaded. Verify the vault file is in a path Ansible automatically loads (group_vars/, host_vars/) or explicitly include it with vars_files: in the playbook.

CI/CD pipeline shows vault password in logs:

Never pass the vault password as a command-line argument (--vault-password-file /dev/stdin <<< "$PASSWORD"). Always write to a file first. Confirm your CI provider masks the secret variable in log output.


Summary

  • Store all secrets in group_vars/*/vault.yml files encrypted with ansible-vault create
  • Use the vault_ prefix convention to distinguish encrypted variables from plain ones
  • Reference vault variables from plain vars.yml files using {{ vault_varname }} so playbooks stay readable
  • Use encrypt_string only for single inline secrets, not as your primary secrets pattern
  • Use vault IDs to maintain separate passwords for different environments or teams
  • Integrate with CI/CD using --vault-password-file and store the password in your secrets manager
  • Rotate vault passwords with ansible-vault rekey and update CI/CD secrets immediately
  • Never store vault passwords in files that are committed to git
  • AWX and Semaphore both have native vault credential support — prefer that over shell workarounds