DevOpsil
Ansible
92%
Fresh

Ansible Vault: Encrypting Secrets in Your Automation

Dev PatelDev Patel18 min read

Why Encrypt Secrets

Ansible playbooks and variable files often contain database passwords, API keys, TLS certificates, OAuth tokens, SSH private keys, and other credentials. Committing these in plaintext to version control is a security incident waiting to happen. Even in private repositories, a single leaked credential can compromise your entire infrastructure. GitHub's own data shows that thousands of secrets are accidentally pushed to public repositories every day, and private repositories are only one misconfigured permission away from exposure.

Ansible Vault solves this by encrypting sensitive data with AES-256 symmetric encryption. The encrypted files remain in your repository alongside everything else, but they are unreadable without the vault password. You get the benefits of version control (history, diffs via ansible-vault diff, pull requests, blame, and bisect) without exposing secrets. The encryption is transparent to Ansible at runtime: when you run a playbook with the vault password, encrypted values are decrypted in memory and never written to disk in plaintext.

What Ansible Vault Is and What It Is Not

It is important to understand the boundaries of Ansible Vault:

Ansible Vault DoesAnsible Vault Does Not
Encrypt files and strings at restManage secret rotation automatically
Decrypt transparently during playbook runsProvide access control per secret
Support multiple passwords (vault IDs)Offer an API for applications to query
Integrate with version control workflowsReplace a full secrets manager (like HashiCorp Vault)
Use AES-256 encryptionEncrypt data in transit (SSH handles that)

For small to medium teams, Ansible Vault is often sufficient on its own. For larger organizations with compliance requirements, dynamic secret generation, or lease-based access, pair Ansible Vault with an external secrets manager.

Core Vault Commands

Creating an Encrypted File

ansible-vault create group_vars/production/vault.yml

This opens your default editor ($EDITOR). When you save and close, the file is encrypted on disk. The content on disk looks like this:

$ANSIBLE_VAULT;1.1;AES256
36653632333438303834303339633035623036363636346434353631306663653766643438363134
62636437336530616139303031346237393834303636376132316365626261653362316134623830
...

The header identifies the file as vault-encrypted and specifies the encryption version and algorithm.

Editing an Encrypted File

ansible-vault edit group_vars/production/vault.yml

Ansible decrypts the file into a temporary location (in a secure temp directory with restrictive permissions), opens your editor, and re-encrypts when you save. If you close without saving, the encrypted file remains unchanged.

Encrypting an Existing File

ansible-vault encrypt group_vars/production/secrets.yml

This encrypts the file in place. The plaintext version is overwritten with the encrypted content.

Decrypting a File (Permanent)

ansible-vault decrypt group_vars/production/vault.yml

This writes the plaintext back to disk permanently. Use this sparingly and never commit the decrypted file. A common use case is decrypting temporarily for a migration and re-encrypting with a new password.

Viewing Encrypted Content Without Editing

ansible-vault view group_vars/production/vault.yml

This prints the decrypted content to stdout without modifying the file. Useful for quick inspections.

Changing the Vault Password (Rekeying)

ansible-vault rekey group_vars/production/vault.yml

You will be prompted for the old password and then the new one. To rekey multiple files at once:

ansible-vault rekey group_vars/production/vault.yml group_vars/staging/vault.yml

To rekey using password files (essential for automation):

ansible-vault rekey \
  --old-vault-password-file ~/.vault_pass_old \
  --new-vault-password-file ~/.vault_pass_new \
  group_vars/production/vault.yml

Comparing Encrypted Files

ansible-vault diff group_vars/production/vault.yml

This is particularly useful when reviewing changes to encrypted files before committing.

Full Command Reference

CommandPurposeExample
createCreate a new encrypted fileansible-vault create secrets.yml
editEdit an encrypted file in placeansible-vault edit secrets.yml
encryptEncrypt an existing plaintext fileansible-vault encrypt secrets.yml
decryptPermanently decrypt a fileansible-vault decrypt secrets.yml
viewView contents without editingansible-vault view secrets.yml
rekeyChange the encryption passwordansible-vault rekey secrets.yml
encrypt_stringEncrypt a single string valueansible-vault encrypt_string 'secret'

Encrypting Entire Files vs Inline Variables

Strategy 1: Encrypting Entire Files

The simplest and most common approach is to encrypt entire variable files. A proven pattern is to split variables into two files per environment group:

group_vars/
  all/
    vars.yml            # Shared non-sensitive variables
  production/
    vars.yml            # Non-sensitive production variables (plaintext)
    vault.yml           # Sensitive production variables (encrypted)
  staging/
    vars.yml            # Non-sensitive staging variables (plaintext)
    vault.yml           # Sensitive staging variables (encrypted)

In group_vars/production/vault.yml (before encryption):

vault_db_password: "s3cret_pr0duction_p@ss"
vault_db_replication_password: "r3pl_s3cret_p@ss"
vault_api_key: "ak_live_abc123def456"
vault_smtp_password: "smtp_s3cret"
vault_redis_password: "r3d1s_auth_t0ken"
vault_jwt_secret: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"
vault_tls_private_key: |
  -----BEGIN PRIVATE KEY-----
  MIIEvgIBADANBgkqhkiG9w0BAQE...
  -----END PRIVATE KEY-----
vault_oauth_client_secret: "oauth_cl13nt_s3cret_v4lue"
vault_backup_encryption_key: "b4ckup_3ncrypt10n_k3y"

In group_vars/production/vars.yml, reference vault variables with a clear naming convention:

# Database
db_host: db-primary.internal.example.com
db_port: 5432
db_name: myapp_production
db_user: myapp
db_password: "{{ vault_db_password }}"
db_replication_password: "{{ vault_db_replication_password }}"

# API
api_base_url: https://api.example.com
api_key: "{{ vault_api_key }}"

# Email
smtp_host: smtp.example.com
smtp_port: 587
smtp_user: noreply@example.com
smtp_password: "{{ vault_smtp_password }}"

# Redis
redis_host: redis.internal.example.com
redis_port: 6379
redis_password: "{{ vault_redis_password }}"

# Authentication
jwt_secret: "{{ vault_jwt_secret }}"
jwt_expiry: 3600

# TLS
tls_certificate: /etc/ssl/certs/server.crt
tls_private_key: "{{ vault_tls_private_key }}"

This pattern has several advantages. When someone reads vars.yml, they can see every variable the application needs and understand that db_password comes from the vault without needing to decrypt anything. The vault_ prefix convention makes it immediately obvious which values are secrets. You can grep for vault_ across your entire codebase to audit secret usage.

Strategy 2: Encrypting Inline Variables

If you prefer to keep everything in one file, encrypt individual values with encrypt_string:

# Encrypt a string with a name
ansible-vault encrypt_string 's3cret_pr0duction_p@ss' --name 'db_password'

Output:

db_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  62313365396662343061393464336163383764316534363531323563633839393539343932373233
  ...

Paste this directly into your variable file. The rest of the file remains plaintext while this specific value is encrypted.

To encrypt from stdin (avoids the secret appearing in shell history):

echo -n 's3cret_pr0duction_p@ss' | ansible-vault encrypt_string --stdin-name 'db_password'

To encrypt with a vault ID:

ansible-vault encrypt_string --vault-id prod@~/.vault_pass_prod 's3cret' --name 'db_password'

Reading from a file to avoid shell history entirely:

ansible-vault encrypt_string --stdin-name 'tls_key' < /path/to/private.key

Strategy Comparison

AspectEncrypted FilesInline Encryption
SimplicitySimpler to manageMore complex per variable
Grep-abilityCannot grep encrypted filesCan grep variable names in plaintext
Diff in PRsEntire file diff is opaqueOnly encrypted values are opaque
Editor workflowMust use ansible-vault editEdit normally, only values encrypted
Team preferenceMore common in practicePreferred by some for mixed files
RekeyingRekey one file per groupMust re-encrypt each string

Most teams choose the split-file approach (Strategy 1) for its simplicity and clear separation.

Vault Password Management

Password Files

Typing the password every time gets tedious. Use a password file:

# Create a password file with a strong random password
openssl rand -base64 32 > ~/.vault_pass
chmod 600 ~/.vault_pass

# Use it with a playbook
ansible-playbook site.yml --vault-password-file ~/.vault_pass

Set it permanently in ansible.cfg:

[defaults]
vault_password_file = ~/.vault_pass

Never commit the password file. Add it to .gitignore:

# Vault password files
.vault_pass
*.vault_pass
.vault_pass_*
vault_password*

Password File as an Executable Script

The password file can be an executable script. Ansible runs it and uses stdout as the password. This is the recommended approach for teams because it integrates with your existing secret management:

#!/bin/bash
# .vault_pass.sh - fetch from macOS Keychain
security find-generic-password -a "ansible-vault" -s "production" -w
#!/bin/bash
# .vault_pass.sh - fetch from 1Password CLI
op item get "Ansible Vault Production" --field password
#!/bin/bash
# .vault_pass.sh - fetch from AWS Secrets Manager
aws secretsmanager get-secret-value \
  --secret-id ansible/vault-password \
  --query SecretString \
  --output text
#!/bin/bash
# .vault_pass.sh - fetch from HashiCorp Vault
vault kv get -field=password secret/ansible/vault
#!/bin/bash
# .vault_pass.sh - fetch from Azure Key Vault
az keyvault secret show \
  --vault-name my-keyvault \
  --name ansible-vault-password \
  --query value \
  --output tsv
#!/bin/bash
# .vault_pass.sh - fetch from GNOME Keyring (Linux)
secret-tool lookup service ansible-vault environment production

Make the script executable:

chmod +x .vault_pass.sh
ansible-playbook site.yml --vault-password-file .vault_pass.sh

Multi-Vault IDs

Large organizations often need different passwords for different environments. If a staging password leaks, production secrets must remain safe. Vault IDs let you use multiple passwords in a single playbook run.

Encrypting with a Vault ID

# Encrypt production secrets with the "prod" vault ID
ansible-vault encrypt --vault-id prod@~/.vault_pass_prod group_vars/production/vault.yml

# Encrypt staging secrets with the "staging" vault ID
ansible-vault encrypt --vault-id staging@~/.vault_pass_staging group_vars/staging/vault.yml

# Encrypt shared secrets with a "shared" vault ID
ansible-vault encrypt --vault-id shared@~/.vault_pass_shared group_vars/all/vault.yml

The encrypted file header now includes the vault ID:

$ANSIBLE_VAULT;1.2;AES256;prod

Running with Multiple Vault IDs

ansible-playbook site.yml \
  --vault-id prod@~/.vault_pass_prod \
  --vault-id staging@~/.vault_pass_staging \
  --vault-id shared@~/.vault_pass_shared

Ansible matches each encrypted value to the correct password using the vault ID label. This lets your production and staging teams have separate passwords while sharing the same playbook repository.

Vault IDs with Scripts

ansible-playbook site.yml \
  --vault-id prod@./get_vault_pass.sh \
  --vault-id staging@./get_vault_pass.sh

The script receives the vault ID as its first argument, so a single script can handle multiple environments:

#!/bin/bash
# get_vault_pass.sh - single script for all vault IDs
VAULT_ID="${1}"

case "${VAULT_ID}" in
  prod)
    aws secretsmanager get-secret-value \
      --secret-id ansible/vault-password-prod \
      --query SecretString --output text
    ;;
  staging)
    aws secretsmanager get-secret-value \
      --secret-id ansible/vault-password-staging \
      --query SecretString --output text
    ;;
  *)
    echo "Unknown vault ID: ${VAULT_ID}" >&2
    exit 1
    ;;
esac

Vault IDs in ansible.cfg

[defaults]
vault_identity_list = prod@~/.vault_pass_prod, staging@~/.vault_pass_staging, shared@~/.vault_pass_shared

Using Vault in Playbooks

Vault-encrypted variables work transparently in playbooks. No special syntax is needed when referencing them:

---
- name: Deploy application
  hosts: webservers
  become: true

  tasks:
    - name: Configure database connection
      template:
        src: database.yml.j2
        dest: /opt/myapp/config/database.yml
        owner: deploy
        group: deploy
        mode: "0600"
      no_log: true

    - name: Set application environment variables
      copy:
        content: |
          DB_HOST={{ db_host }}
          DB_PORT={{ db_port }}
          DB_NAME={{ db_name }}
          DB_USER={{ db_user }}
          DB_PASSWORD={{ db_password }}
          REDIS_URL=redis://:{{ redis_password }}@{{ redis_host }}:{{ redis_port }}/0
          JWT_SECRET={{ jwt_secret }}
          SMTP_PASSWORD={{ smtp_password }}
        dest: /opt/myapp/.env
        owner: deploy
        group: deploy
        mode: "0600"
      no_log: true
      notify: Restart Application

    - name: Deploy TLS certificate
      copy:
        content: "{{ tls_private_key }}"
        dest: /etc/ssl/private/server.key
        owner: root
        group: ssl-cert
        mode: "0640"
      no_log: true
      notify: Reload Nginx

    - name: Configure application secrets in systemd override
      copy:
        content: |
          [Service]
          Environment="SECRET_KEY={{ vault_secret_key }}"
          Environment="API_KEY={{ vault_api_key }}"
        dest: /etc/systemd/system/myapp.service.d/secrets.conf
        owner: root
        group: root
        mode: "0600"
      no_log: true
      notify:
        - Reload systemd
        - Restart Application

  handlers:
    - name: Restart Application
      service:
        name: myapp
        state: restarted

    - name: Reload Nginx
      service:
        name: nginx
        state: reloaded

    - name: Reload systemd
      systemd:
        daemon_reload: true

The Jinja2 template database.yml.j2:

production:
  adapter: postgresql
  host: {{ db_host }}
  port: {{ db_port }}
  database: {{ db_name }}
  username: {{ db_user }}
  password: {{ db_password }}
  pool: 25
  timeout: 5000
  ssl_mode: require

Run the playbook:

ansible-playbook deploy.yml --ask-vault-pass
# or
ansible-playbook deploy.yml --vault-password-file ~/.vault_pass

Integrating Vault with CI/CD Pipelines

In CI/CD pipelines, vault passwords should come from the CI system's secret management, not from files committed to the repository. The vault password should be a CI/CD secret that is injected at runtime.

GitHub Actions

# .github/workflows/deploy.yml
name: Deploy Application
on:
  push:
    branches: [main]
  workflow_dispatch:
    inputs:
      environment:
        description: "Target environment"
        required: true
        type: choice
        options: [staging, production]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment || 'staging' }}
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install Ansible
        run: pip install ansible

      - name: Configure SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          echo "${{ secrets.SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts

      - name: Run Ansible Playbook
        env:
          ANSIBLE_VAULT_PASSWORD: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
          ANSIBLE_HOST_KEY_CHECKING: "False"
        run: |
          echo "$ANSIBLE_VAULT_PASSWORD" > .vault_pass
          chmod 600 .vault_pass
          ansible-playbook -i inventory/ site.yml \
            --vault-password-file .vault_pass \
            --limit ${{ inputs.environment || 'staging' }}
          rm -f .vault_pass

GitLab CI

# .gitlab-ci.yml
stages:
  - lint
  - deploy

variables:
  ANSIBLE_HOST_KEY_CHECKING: "False"

.ansible_setup: &ansible_setup
  before_script:
    - pip install ansible
    - echo "$VAULT_PASSWORD" > .vault_pass
    - chmod 600 .vault_pass
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
    - chmod 600 ~/.ssh/deploy_key
  after_script:
    - rm -f .vault_pass

lint:
  stage: lint
  image: python:3.12
  script:
    - pip install ansible-lint
    - ansible-lint site.yml

deploy_staging:
  stage: deploy
  image: python:3.12
  <<: *ansible_setup
  script:
    - ansible-playbook -i inventory/ site.yml --vault-password-file .vault_pass --limit staging
  environment:
    name: staging
  only:
    - main

deploy_production:
  stage: deploy
  image: python:3.12
  <<: *ansible_setup
  script:
    - ansible-playbook -i inventory/ site.yml --vault-password-file .vault_pass --limit production
  environment:
    name: production
  when: manual
  only:
    - main

Jenkins Pipeline

pipeline {
    agent any
    environment {
        VAULT_PASSWORD = credentials('ansible-vault-password')
    }
    stages {
        stage('Deploy') {
            steps {
                sh '''
                    echo "$VAULT_PASSWORD" > .vault_pass
                    chmod 600 .vault_pass
                    ansible-playbook -i inventory/ site.yml \
                        --vault-password-file .vault_pass \
                        --limit ${ENVIRONMENT}
                    rm -f .vault_pass
                '''
            }
        }
    }
    post {
        always {
            sh 'rm -f .vault_pass'
        }
    }
}

Using Environment Variables Directly

Ansible supports ANSIBLE_VAULT_PASSWORD_FILE as an environment variable:

export ANSIBLE_VAULT_PASSWORD_FILE=~/.vault_pass
ansible-playbook site.yml

Or use a script that reads from an environment variable:

#!/bin/bash
# vault_pass_from_env.sh
echo "$ANSIBLE_VAULT_PASSWORD"
chmod +x vault_pass_from_env.sh
export ANSIBLE_VAULT_PASSWORD="my-secret-password"
export ANSIBLE_VAULT_PASSWORD_FILE=./vault_pass_from_env.sh
ansible-playbook site.yml

Lookup Plugins for External Secret Managers

For organizations that centralize secrets outside of Ansible, lookup plugins pull values at runtime without ever writing them to Ansible variable files. This is the recommended approach for large-scale deployments where secrets must have audit trails, automatic rotation, and fine-grained access control.

HashiCorp Vault

Install the collection:

ansible-galaxy collection install community.hashi_vault
pip install hvac

Use the lookup in your playbook:

- name: Retrieve database password from HashiCorp Vault
  set_fact:
    db_password: "{{ lookup('community.hashi_vault.hashi_vault',
      'secret/data/production/database:password',
      url='https://vault.example.com:8200',
      token=lookup('env', 'VAULT_TOKEN')) }}"
  no_log: true

With AppRole authentication (recommended for CI/CD):

- name: Authenticate with AppRole and retrieve secrets
  set_fact:
    db_creds: "{{ lookup('community.hashi_vault.hashi_vault',
      'secret/data/production/database',
      auth_method='approle',
      role_id=lookup('env', 'VAULT_ROLE_ID'),
      secret_id=lookup('env', 'VAULT_SECRET_ID'),
      url='https://vault.example.com:8200') }}"
  no_log: true

- name: Use the credentials
  template:
    src: database.yml.j2
    dest: /opt/myapp/config/database.yml
  vars:
    db_user: "{{ db_creds.data.username }}"
    db_password: "{{ db_creds.data.password }}"
  no_log: true

With environment-based authentication:

# Export these before running:
# export VAULT_ADDR=https://vault.example.com:8200
# export VAULT_TOKEN=s.xxxxxxxxxxxxxxxx

- name: Get all database credentials
  set_fact:
    db_creds: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/data/production/database') }}"
  no_log: true

AWS Systems Manager Parameter Store

ansible-galaxy collection install amazon.aws
pip install boto3
- name: Get parameter from SSM
  set_fact:
    api_key: "{{ lookup('amazon.aws.ssm_parameter', '/production/api_key', region='us-east-1') }}"
  no_log: true

- name: Get encrypted parameter
  set_fact:
    db_password: "{{ lookup('amazon.aws.ssm_parameter', '/production/db_password', decrypt=true, region='us-east-1') }}"
  no_log: true

- name: Get multiple parameters by path
  set_fact:
    app_secrets: "{{ lookup('amazon.aws.ssm_parameter', '/production/', bypath=true, decrypt=true, region='us-east-1') }}"
  no_log: true

AWS Secrets Manager

- name: Get secret from Secrets Manager
  set_fact:
    db_creds: "{{ lookup('amazon.aws.secretsmanager_secret', 'production/database', region='us-east-1') | from_json }}"
  no_log: true

- name: Use credentials
  template:
    src: database.yml.j2
    dest: /opt/myapp/config/database.yml
  vars:
    db_user: "{{ db_creds.username }}"
    db_password: "{{ db_creds.password }}"
    db_host: "{{ db_creds.host }}"
  no_log: true

Azure Key Vault

ansible-galaxy collection install azure.azcollection
pip install azure-keyvault-secrets azure-identity
- name: Get secret from Azure Key Vault
  set_fact:
    db_password: "{{ lookup('azure.azcollection.azure_keyvault_secret',
      'db-password',
      vault_url='https://my-keyvault.vault.azure.net') }}"
  no_log: true

External Secrets Comparison

FeatureAnsible VaultHashiCorp VaultAWS SSMAWS Secrets Manager
EncryptionAES-256 at restAES-256 + transitAWS KMSAWS KMS
Access controlPassword-basedPolicies + tokensIAM policiesIAM policies
RotationManual rekeyAutomatic leasesManualAutomatic rotation
Audit trailGit historyDetailed audit logCloudTrailCloudTrail
Dynamic secretsNoYes (databases, AWS, etc.)NoLimited
CostFreeFree (OSS) or paidFree (standard)$0.40/secret/month
Setup effortMinimalModerate to highLowLow

Automating Vault Password Rotation

Regular password rotation is a security best practice. Automate it with a script that rekeys all encrypted files:

#!/bin/bash
# rotate-vault-password.sh
set -euo pipefail

OLD_PASS_FILE="$HOME/.vault_pass"
NEW_PASS_FILE="$HOME/.vault_pass_new"

# Generate new password
openssl rand -base64 32 > "$NEW_PASS_FILE"
chmod 600 "$NEW_PASS_FILE"

# Find and rekey all vault-encrypted files
echo "Finding vault-encrypted files..."
VAULT_FILES=$(grep -rl '^\$ANSIBLE_VAULT' . --include="*.yml" --include="*.yaml" || true)

if [ -z "$VAULT_FILES" ]; then
  echo "No vault-encrypted files found."
  exit 0
fi

echo "Rekeying files..."
for file in $VAULT_FILES; do
  echo "  Rekeying: $file"
  ansible-vault rekey \
    --old-vault-password-file "$OLD_PASS_FILE" \
    --new-vault-password-file "$NEW_PASS_FILE" \
    "$file"
done

# Replace old password with new
cp "$NEW_PASS_FILE" "$OLD_PASS_FILE"
rm "$NEW_PASS_FILE"

echo "Vault password rotation complete."
echo "Remember to update the password in your CI/CD secrets and team password manager."

Best Practices for Managing Vault Passwords in Teams

Use a password manager for the vault password. Store the vault password in 1Password, Bitwarden, HashiCorp Vault, or AWS Secrets Manager. Write a vault password script that retrieves it:

#!/bin/bash
# Fetch from 1Password
op item get "Ansible Vault Production" --field password

Rotate vault passwords regularly. Set a rotation schedule (quarterly at minimum) and use the automated rotation script above. After rotation, update the password in all CI/CD systems and team password managers.

Separate vault passwords by environment. Production and staging must use different vault passwords. If a staging password leaks, production secrets remain safe. Use vault IDs to manage this cleanly.

Review encrypted file changes in pull requests. You cannot see the diff of an encrypted file in a standard PR. Set up a Git diff driver to enable plaintext diffs:

# .gitattributes
*vault*.yml diff=ansible-vault

# .git/config (or global .gitconfig)
[diff "ansible-vault"]
  textconv = ansible-vault view
  cachetextconv = false

This lets git diff and git log -p show plaintext diffs of encrypted files (assuming you have the vault password available). The diffs are generated locally and never stored in plaintext.

Minimize what you encrypt. Only encrypt actual secrets. Configuration values like port numbers, package names, hostnames, and feature flags do not need encryption. Encrypting everything makes debugging harder, slows down development, and creates unnecessary cognitive overhead. Use the split-file pattern (vars.yml + vault.yml) to keep the boundary clear.

Never echo or debug secrets in output. Use no_log: true on every task that handles sensitive data:

- name: Set database password
  lineinfile:
    path: /opt/myapp/.env
    regexp: "^DB_PASSWORD="
    line: "DB_PASSWORD={{ db_password }}"
  no_log: true

- name: Create database user
  community.postgresql.postgresql_user:
    name: myapp
    password: "{{ db_password }}"
    state: present
  no_log: true

Without no_log, the password appears in Ansible's stdout when the task runs, which is especially dangerous in CI/CD logs that are often accessible to many team members.

Use ansible-lint to catch unencrypted secrets. Configure ansible-lint to warn about potential plaintext secrets:

# .ansible-lint
warn_list:
  - no-log-password

Pre-commit hooks for vault files. Use a pre-commit hook to prevent accidentally committing decrypted vault files:

#!/bin/bash
# .git/hooks/pre-commit
# Prevent committing unencrypted vault files

FILES=$(git diff --cached --name-only | grep -E "vault\.yml$" || true)

for f in $FILES; do
  if ! head -1 "$f" | grep -q '^\$ANSIBLE_VAULT'; then
    echo "ERROR: $f appears to be an unencrypted vault file!"
    echo "Run: ansible-vault encrypt $f"
    exit 1
  fi
done

Or use the pre-commit framework:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/ansible-community/ansible-lint
    rev: v24.2.0
    hooks:
      - id: ansible-lint
  - repo: local
    hooks:
      - id: check-vault-encrypted
        name: Check vault files are encrypted
        entry: bash -c 'for f in "$@"; do if echo "$f" | grep -q "vault" && ! head -1 "$f" | grep -q "^\$ANSIBLE_VAULT"; then echo "ERROR: $f is not encrypted"; exit 1; fi; done'
        language: system
        files: "vault.*\\.yml$"

Troubleshooting Vault Issues

Common Errors and Solutions

"Decryption failed" -- Wrong vault password. Verify you are using the correct password file or vault ID.

# Test which vault ID a file uses
head -1 group_vars/production/vault.yml
# Output: $ANSIBLE_VAULT;1.2;AES256;prod

"ERROR! input is not vault encrypted data" -- The file is not encrypted or has been corrupted.

# Check if a file is vault-encrypted
file group_vars/production/vault.yml
head -1 group_vars/production/vault.yml

"Vault password client script had non-zero exit" -- Your password script is failing. Test it manually:

bash -x .vault_pass.sh

Performance with many encrypted files -- If you have many vault-encrypted files, consider consolidating secrets into fewer files. Each encrypted file requires a separate decryption operation.

"no vault secrets were found that could decrypt" -- You are using vault IDs, but the vault ID on the encrypted file does not match any provided vault IDs:

# Check the vault ID in the file header
head -1 encrypted_file.yml

# Provide the matching vault ID
ansible-playbook site.yml --vault-id prod@~/.vault_pass_prod
Share:
Dev Patel
Dev Patel

Cloud Cost Optimization Specialist

I find the money your cloud is wasting. FinOps practitioner, data-driven analyst, and the person your CFO wishes they'd hired sooner. Every dollar saved is a dollar earned.

Related Articles