Overview

In automated operations, Ansible Playbooks frequently handle sensitive information such as database passwords, API keys, and SSH private keys. If this data is stored in plaintext in code repositories, a single repository leak exposes all credentials. Ansible Vault, Ansible’s built-in encryption tool, protects sensitive data using the AES-256 symmetric encryption algorithm, ensuring that only authorized users can access it.

This article will systematically cover Ansible Vault usage, password management strategies, and CI/CD pipeline integration—from basic concepts to production practices.

Why You Need Ansible Vault

Risks of Plaintext Storage

In real-world operations, sensitive information is scattered across multiple locations:

LocationCommon Sensitive DataRisk Level
group_vars/all.ymlDatabase passwords, Redis passwordsHigh
host_vars/web01.ymlSSH connection passwords, BECOME passwordsHigh
Inventory filesansible_password, ansible_ssh_passHigh
Playbook variablesAPI tokens, third-party keysMedium
Jinja2 templatesCertificate private keys, JWT secretsHigh

Risks of plaintext storage include:

  1. Repository leaks: Git history permanently retains plaintext passwords; even if subsequently deleted, they can be recovered from history
  2. Compliance audit failures: Security standards like PCI-DSS and ISO 27001 require encrypted storage of sensitive data
  3. Team collaboration risks: Anyone with repository access can see all passwords
  4. Log leaks: Ansible execution logs may output variable values, causing passwords to appear in log files

Ansible Vault’s Position

Ansible Vault is not the only secret management solution, but it is the most direct choice within the Ansible ecosystem:

SolutionProsConsUse Case
Ansible VaultBuilt-in, zero dependencies, YAML-nativeSingle password encryption, no fine-grained permissionsSmall to medium teams, quick start
HashiCorp VaultDynamic secrets, lease management, audit logsRequires additional deployment and maintenanceLarge enterprises, high security requirements
AWS Secrets ManagerCloud-native, auto-rotationVendor lock-in, usage-based pricingAWS cloud environments
SOPS + ageMulti-key encryption, Git-friendlyRequires additional toolingMulti-person collaboration, GitOps scenarios

Refer to the Ansible Vault official documentation for the complete feature list.

Core Concepts and Encryption Mechanism

Encryption Principle

Ansible Vault uses the AES-256 symmetric encryption algorithm (in earlier versions, possibly AES-128). The encryption flow:

Plaintext YAML → AES-256 encryption (Vault password as key) → Encrypted YAML ($ANSIBLE_VAULT header)

Encrypted files begin with $ANSIBLE_VAULT;1.1;AES256, followed by base64-encoded ciphertext. For example:

$ANSIBLE_VAULT;1.1;AES256
66386439653236336462626566653033393664633164636136383765393730353066386230336230
363366323737366336663737333635303462653066333637356436653066333763350a6366373134
37633733336236393030303237373332643761306635316631393963363033663638366363313061
...

Encryptable File Types

Ansible Vault can encrypt any data file used by Ansible:

File TypeExampleEncryptable
Variable filesgroup_vars/all.ymlYes
Inventory filesinventory.iniYes
Playbook filessite.ymlYes (but not recommended)
Jinja2 templatesnginx.conf.j2Yes
Standalone key filesdb_creds.ymlYes
Certificate/key filesserver.keyYes
ansible.cfgansible.cfgNo

Note: ansible.cfg cannot be encrypted because Ansible must read this file at startup. If the configuration contains sensitive information, move it to an encrypted variable file and reference it from ansible.cfg.

Basic Operations

Creating Encrypted Files

# Interactive creation (will prompt for password)
ansible-vault create secrets.yml

# Create using a password file (recommended for automation)
ansible-vault create --vault-password-file=~/.vault_pass secrets.yml

Example file content after creation:

# secrets.yml (encrypted)
db_password: "prod-db-2026!"
admin_api_key: "sk_live_abc123def456"
ssh_private_key: |
  -----BEGIN RSA PRIVATE KEY-----
  MIIEowIBAAKCAQEA...
  -----END RSA PRIVATE KEY-----  

Encrypting Existing Files

# Encrypt a single file
ansible-vault encrypt existing_vars.yml

# Encrypt multiple files
ansible-vault encrypt group_vars/all.yml host_vars/web01.yml

# Specify output file (does not overwrite the original)
ansible-vault encrypt --output=encrypted.yml plaintext.yml

Viewing, Editing, and Rekeying

# View encrypted file content (does not leave plaintext on disk)
ansible-vault view secrets.yml

# Edit encrypted file (auto decrypt → edit → re-encrypt on save)
ansible-vault edit secrets.yml

# Change the encryption password (rekey)
ansible-vault rekey secrets.yml

# Permanently decrypt a file (removes encryption, use with caution!)
ansible-vault decrypt secrets.yml

Common Command Reference

CommandFunctionSafety Level
ansible-vault createCreate and encrypt a new fileSafe
ansible-vault encryptEncrypt an existing fileSafe
ansible-vault editEdit an encrypted fileSafe (temporary in-memory decryption)
ansible-vault viewView encrypted contentSafe (no disk paging)
ansible-vault rekeyChange encryption passwordSafe
ansible-vault decryptPermanently decryptDangerous (plaintext on disk)

Password Management Strategies

Password File Approach

Create a file containing only the password string:

# Create password file
echo "MyVaultPassword2026!" > ~/.vault_pass

# Set strict permissions (mandatory!)
chmod 600 ~/.vault_pass

Configure the default password file path in ansible.cfg:

[defaults]
vault_password_file = ~/.vault_pass

Afterward, Ansible commands will automatically use the configured password file:

# Automatically uses the password file configured in ansible.cfg
ansible-playbook site.yml

Multiple Passwords (Vault ID) Approach

In production environments, different environments (development, staging, production) should use different Vault passwords. Ansible 2.4+ introduced the Vault ID concept:

# Create encrypted files with Vault IDs
ansible-vault create --vault-id dev@~/.vault_dev_pass dev_secrets.yml
ansible-vault create --vault-id prod@~/.vault_prod_pass prod_secrets.yml

Specify multiple Vault IDs when running Playbooks:

ansible-playbook site.yml \
  --vault-id dev@~/.vault_dev_pass \
  --vault-id prod@~/.vault_prod_pass

Ansible automatically selects the correct password for decryption based on the Vault ID used during file encryption.

Configure multiple Vault IDs in ansible.cfg:

[defaults]
vault_identity_list = dev@~/.vault_dev_pass, prod@~/.vault_prod_pass, staging@~/.vault_staging_pass

Vault ID Naming Conventions

Recommended practice is to use environment names as Vault ID identifiers:

Vault IDPurposePassword File
devDevelopment environment~/.vault_dev_pass
stagingStaging environment~/.vault_staging_pass
prodProduction environment~/.vault_prod_pass
dbDatabase-specific password~/.vault_db_pass
networkNetwork device passwords~/.vault_network_pass

Best practice: Production environment password files should be stored in restricted locations (such as password managers or HSMs), not in user home directories.

Using Encrypted Variables in Playbooks

Method 1: vars_files Reference

# site.yml
---
- name: Deploy Application
  hosts: prod_servers
  vars_files:
    - secrets.yml  # Automatically decrypts and loads variables
  tasks:
    - name: Configure database connection
      template:
        src: db.conf.j2
        dest: /etc/app/db.conf
      # {{ db_password }} is automatically decrypted and filled at runtime

Method 2: Dynamic Loading with include_vars

---
- name: Load secrets based on environment
  hosts: all
  tasks:
    - name: Include encrypted vars for current environment
      include_vars:
        file: "{{ env }}_secrets.yml"
      no_log: true  # Prevent variable content from appearing in logs

Method 3: Command-Line Input

# Interactive input with --ask-vault-pass
ansible-playbook site.yml --ask-vault-pass

# Specify password file with --vault-password-file
ansible-playbook site.yml --vault-password-file=~/.vault_pass

# Specify labeled password with --vault-id
ansible-playbook site.yml --vault-id prod@~/.vault_prod_pass

Preventing Log Leaks

Even with Vault encryption, Ansible may output variable values to logs during execution. Use the no_log attribute to prevent leaks:

---
- name: Create database user
  community.mysql.mysql_user:
    name: "{{ db_user }}"
    password: "{{ db_password }}"
    priv: "*.*:ALL"
    state: present
  no_log: true  # Critical! Prevents password from appearing in logs

You can also set no_log at the play level:

---
- name: Sensitive operations
  hosts: all
  no_log: true  # All tasks in this play will not log
  tasks:
    - name: Deploy with secrets
      ...

Project Structure Design

project/
├── ansible.cfg
├── inventory/
   ├── production/
      ├── hosts.ini
      ├── group_vars/
         ├── all/
            ├── common.yml          # Common variables (plaintext)
            └── vault.yml           # Encrypted variables
         └── webservers/
             ├── vars.yml            # Plaintext variables
             └── vault.yml           # Encrypted variables
      └── host_vars/
          └── web01/
              ├── vars.yml
              └── vault.yml
   └── staging/
       ├── hosts.ini
       └── group_vars/
           └── all/
               └── vault.yml
├── playbooks/
   ├── site.yml
   ├── deploy.yml
   └── db_setup.yml
├── roles/
   ├── nginx/
   ├── mysql/
   └── app/
├── files/
   └── ssl/
       └── server.key                   # Encrypted SSL private key
└── templates/
    └── db.conf.j2

Plaintext-Encrypted Separation Principle

Store plaintext and encrypted variables in separate files for easier management and auditing:

# group_vars/all/common.yml (plaintext, can be committed to Git)
---
app_name: "myapp"
app_port: 8080
db_host: "db.internal"
db_port: 3306
# group_vars/all/vault.yml (encrypted, can be committed to Git but content is unreadable)
$ANSIBLE_VAULT;1.1;AES256
6638643965323633646262656665303339366463316463613638376539373035...
# ansible.cfg
[defaults]
inventory = inventory/
vault_identity_list = dev@~/.vault_dev_pass, staging@~/.vault_staging_pass, prod@~/.vault_prod_pass
host_key_checking = False

CI/CD Integration

GitLab CI Integration

In CI/CD environments, Vault passwords cannot be entered interactively. Inject via CI variables:

# .gitlab-ci.yml
stages:
  - deploy

deploy_production:
  stage: deploy
  image: ansible/ansible-runner:latest
  variables:
    # Vault password injected via CI variable (set as Masked variable in GitLab UI)
    VAULT_PASS_PROD: $VAULT_PASS_PROD
  before_script:
    # Write CI variable to temporary password file
    - echo "$VAULT_PASS_PROD" > /tmp/vault_pass
    - chmod 600 /tmp/vault_pass
  script:
    - ansible-playbook -i inventory/production playbooks/deploy.yml
        --vault-id prod@/tmp/vault_pass
  after_script:
    # Clean up password file
    - rm -f /tmp/vault_pass
  only:
    - main

GitHub Actions Integration

# .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-core

      - name: Create vault password file
        run: |
          echo "${{ secrets.VAULT_PASS_PROD }}" > ~/.vault_pass
          chmod 600 ~/.vault_pass          

      - name: Run playbook
        run: |
          ansible-playbook -i inventory/production playbooks/deploy.yml \
            --vault-id prod@~/.vault_pass          

      - name: Cleanup
        if: always()
        run: rm -f ~/.vault_pass

Jenkins Pipeline Integration

pipeline {
    agent any
    environment {
        VAULT_PASS = credentials('ansible-vault-prod')
    }
    stages {
        stage('Deploy') {
            steps {
                sh '''
                    echo "${VAULT_PASS}" > /tmp/vault_pass
                    chmod 600 /tmp/vault_pass
                    ansible-playbook -i inventory/production playbooks/deploy.yml \
                        --vault-id prod@/tmp/vault_pass
                    rm -f /tmp/vault_pass
                '''
            }
        }
    }
}

Security tip: Password files in CI/CD should be cleaned up in after_script or post steps to ensure no residue even if the task fails.

Password Rotation

Regular Rotation Strategy

Security compliance typically requires regular password changes. Ansible Vault’s rekey command supports password rotation:

# Interactive rotation (enter old password → set new password)
ansible-vault rekey secrets.yml

# Rotation using password files
ansible-vault rekey \
  --vault-password-file=~/.vault_old_pass \
  --new-vault-password-file=~/.vault_new_pass \
  secrets.yml

# Rotation using Vault ID
ansible-vault rekey \
  --vault-id prod@~/.vault_prod_old \
  --new-vault-id prod@~/.vault_prod_new \
  prod_secrets.yml

Batch Rotation Script

#!/bin/bash
# rotate-vault-passwords.sh
# Batch rotate Vault passwords for all encrypted files

set -euo pipefail

OLD_PASS_FILE="$1"
NEW_PASS_FILE="$2"
VAULT_DIR="${3:-group_vars}"

if [ ! -f "$OLD_PASS_FILE" ] || [ ! -f "$NEW_PASS_FILE" ]; then
    echo "Usage: $0 <old_pass_file> <new_pass_file> [vault_dir]"
    exit 1
fi

# Find all encrypted files
find "$VAULT_DIR" -type f -name "*.yml" -exec grep -l '^\$ANSIBLE_VAULT' {} \; | while read -r file; do
    echo "Rekeying: $file"
    ansible-vault rekey \
        --vault-password-file="$OLD_PASS_FILE" \
        --new-vault-password-file="$NEW_PASS_FILE" \
        "$file"
done

echo "All vault files rekeyed successfully."

Rotation Automation

Combine with cron or CI/CD for regular automatic rotation:

# /etc/cron.d/vault-rotate
# Execute password rotation at 2 AM on the first day of each quarter
0 2 1 */3 * /opt/scripts/rotate-vault-passwords.sh \
    /etc/ansible/.vault_pass \
    /etc/ansible/.vault_pass_new \
    /etc/ansible/group_vars \
    && mv /etc/ansible/.vault_pass_new /etc/ansible/.vault_pass

Security Auditing

Encrypted File Scanning

Regularly scan repositories for plaintext sensitive data:

#!/bin/bash
# scan-plaintext-secrets.sh
# Scan for files that may contain plaintext passwords

PATTERNS=(
    "password.*=.*['\"].\{8,\}['\"]"
    "secret.*=.*['\"].\{8,\}['\"]"
    "api_key.*=.*['\"].\{20,\}['\"]"
    "token.*=.*['\"].\{20,\}['\"]"
    "private_key"
)

SCAN_DIR="${1:-.}"
ISSUES=0

for pattern in "${PATTERNS[@]}"; do
    while IFS= read -r file; do
        # Skip already encrypted files
        if head -1 "$file" | grep -q '^\$ANSIBLE_VAULT'; then
            continue
        fi
        # Skip .git directory
        if echo "$file" | grep -q '\.git/'; then
            continue
        fi
        echo "[WARNING] Potential plaintext secret in: $file"
        grep -n "$pattern" "$file" | head -5
        ISSUES=$((ISSUES + 1))
    done < <(grep -rl "$pattern" "$SCAN_DIR" 2>/dev/null)
done

if [ "$ISSUES" -gt 0 ]; then
    echo ""
    echo "Found $ISSUES files with potential plaintext secrets."
    echo "Consider encrypting them with: ansible-vault encrypt <file>"
    exit 1
else
    echo "No plaintext secrets found."
    exit 0
fi

Audit Logging

In team collaboration, record who accessed encrypted data and when:

# Record vault operation audit logs
vault_audit() {
    local action="$1"
    local file="$2"
    local user="$(whoami)"
    local timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
    local log_file="/var/log/ansible-vault-audit.log"

    echo "[$timestamp] user=$user action=$action file=$file" >> "$log_file"
}

# Usage
vault_audit "view" "group_vars/all/vault.yml"
ansible-vault view group_vars/all/vault.yml

Advanced Techniques

Encrypting Individual Variables

Sometimes you only need to encrypt a few sensitive fields rather than an entire file. Use the !vault tag to encrypt individual variables:

# group_vars/all.yml
---
db_host: "db.internal"
db_port: 3306
db_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  66386439653236336462626566653033393664633164636136383765393730353066386230336230
  363366323737366336663737333635303462653066333637356436653066333763350a6366373134
  ...
api_key: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  37323438366638343536373633336630333134356238393630353436393866636330303037343030
  ...
# Non-sensitive variables remain in plaintext
app_name: "myapp"
app_port: 8080

Generate encrypted variable values:

# Using the encrypt_string command
ansible-vault encrypt_string --vault-id prod@~/.vault_prod_pass 'my_secret_password'

Output can be pasted directly into a YAML file:

# Specify variable name
ansible-vault encrypt_string --vault-id prod@~/.vault_prod_pass \
  --name 'db_password' \
  'prod-db-2026!'

Integration with HashiCorp Vault

For scenarios requiring higher security levels, integrate with HashiCorp Vault:

# playbook.yml
---
- name: Get secrets from HashiCorp Vault
  hosts: localhost
  vars:
    vault_url: "http://vault:8200"
  tasks:
    - name: Read database credentials from Vault
      hashivault_read:
        url: "{{ vault_url }}"
        token: "{{ lookup('env', 'VAULT_TOKEN') }}"
        secret: "secret/data/database"
        key: "password"
      register: db_secret
      no_log: true

    - name: Use the secret
      debug:
        msg: "DB password retrieved from Vault (masked)"
      no_log: true

Encrypting an Entire Directory

#!/bin/bash
# encrypt-directory.sh
# Encrypt all YAML files in a directory

DIR="${1:-group_vars}"

find "$DIR" -type f \( -name "*.yml" -o -name "*.yaml" \) | while read -r file; do
    # Skip already encrypted files
    if head -1 "$file" | grep -q '^\$ANSIBLE_VAULT'; then
        echo "[SKIP] Already encrypted: $file"
        continue
    fi
    echo "[ENCRYPT] $file"
    ansible-vault encrypt --vault-id prod@~/.vault_prod_pass "$file"
done

Common Issues and Troubleshooting

Forgotten Vault Password

Ansible Vault uses symmetric encryption with no backdoor. If you forget the password, the data cannot be recovered.

Preventive measures:

  1. Store passwords in an enterprise password manager (e.g., 1Password, Bitwarden)
  2. Have multiple team members hold password copies
  3. Regularly back up encrypted files

Decryption Failures

# Example error message
ERROR! Attempting to decrypt but no valid secrets found

# Troubleshooting steps
# 1. Check if password file exists and is readable
ls -la ~/.vault_pass

# 2. Check password file content (ensure no extra newlines or spaces)
cat -A ~/.vault_pass

# 3. Check vault_password_file path in ansible.cfg
grep vault ansible.cfg

# 4. Check if Vault ID matches
ansible-vault view --vault-id prod@~/.vault_prod_pass secrets.yml

Git Diff for Encrypted Files

Encrypted files change entirely even when only one character is modified. This makes Git diff unable to show actual changes.

Solution—temporarily decrypt for viewing diffs:

# Configure in .gitattributes
echo "*.yml diff=ansible_vault" >> .gitattributes

# Configure diff tool in .git/config
git config diff.ansible_vault.textconv "ansible-vault view"

After configuration, git diff will automatically decrypt and show plaintext differences.

Common Error Reference

Error MessageCauseSolution
Attempting to decrypt but no valid secrets foundNo valid Vault password providedCheck password file or --vault-id parameter
Vault format unhandledFile format incompatibleCheck Ansible version, upgrade to latest
Decryption failedWrong password or corrupted fileTry correct password or restore from backup
value must be valid YAMLEncrypted file content corruptedRestore from Git history or recreate
ansible.cfg not foundConfiguration file accidentally encryptedansible.cfg cannot be encrypted; restore from backup

Summary

Ansible Vault provides a lightweight sensitive data protection solution for Ansible automated operations. Key points to focus on when using it:

  1. Plaintext-encrypted separation: Store sensitive and regular variables in separate files; only encrypt sensitive files for easier management and auditing
  2. Multi-password management: Use Vault IDs to set different passwords for different environments, reducing single-point leak risk
  3. CI/CD security: Inject passwords through Secret variables in pipelines; clean up temporary files immediately after use
  4. Regular rotation: Combine with cron or CI/CD for periodic password rotation to meet security compliance requirements
  5. Log protection: Set no_log: true on tasks handling sensitive variables to prevent password leaks in logs
  6. Backup and recovery: Encrypted files need backups too; passwords also need to be securely stored in enterprise password managers

Ansible Vault’s design philosophy is “good enough”—it doesn’t pursue the enterprise-grade key management capabilities of HashiCorp Vault, but instead solves the most common sensitive data protection problem in Ansible scenarios with minimal learning curve and zero additional dependencies. For small to medium teams and rapid-iteration projects, it is the most cost-effective choice.

References