Blog

Ansible Security: Best Practices for Secure Automation

Ansible security best practices: protect secrets with Ansible Vault, harden privileges and modules, and scan playbooks in CI for secure automation.

Bruno Baldo·Sep 21, 2026·Updated Sep 14, 2026·9 min read·Reviewed by Rainforest Technologies
Ansible is the quiet workhorse of modern infrastructure. It configures servers, deploys applications, patches fleets, and orchestrates releases, often with a handful of readable YAML files. That accessibility is exactly why Ansible security deserves deliberate attention: a playbook that touches hundreds of hosts with escalated privileges is one of the most powerful, and most dangerous, artifacts in your environment. A single leaked credential or an unvalidated variable passed to a shell command can turn convenient automation into a fast path for an attacker. The good news is that securing Ansible is largely about discipline and a set of well-understood Ansible security best practices. This deep dive walks through the areas that matter most: protecting secrets with Ansible Vault, keeping credentials out of playbooks and logs, applying least privilege with become, choosing safe modules, defending against template injection, hardening connections, managing your automation supply chain, and scanning everything in CI so secure Ansible playbooks become the default rather than the exception. It is part of our broader Infrastructure as Code security guide, which ties these tool-specific practices together. Secrets management with Ansible Vault Automation needs credentials: database passwords, API tokens, TLS keys, cloud access keys. The cardinal rule is simple. Sensitive values must never sit in plaintext in a repository. Ansible Vault is the built-in answer. It encrypts variables and files with a symmetric key so they can live safely alongside your playbooks. You can encrypt an entire file: ansible-vault encrypt group_vars/production/secrets.yml Or encrypt individual strings inline so most of a vars file stays readable in diffs: db_password: !vault |       $ANSIBLE_VAULT;1.1;AES256       66386439653...... A few habits make Vault genuinely effective rather than theater: Never commit the Vault password itself. Supply it at runtime with --vault-password-file pointing at a file outside the repo, or a script that fetches the key from your secrets platform. Keep the password out of shell history and CI logs. Separate encrypted from cleartext data. A common pattern is a readable vars.yml that references encrypted values held in a vault.yml, so reviewers can see structure without exposing secrets. Rotate keys and re-key when people leave. ansible-vault rekey re-encrypts content under a new password without changing the plaintext. Vault is excellent for static secrets, but for credentials that rotate frequently or are shared across many systems, integrate an external secret store. Ansible can pull secrets at runtime from managed vaults and cloud secret managers via lookup plugins, so nothing sensitive is ever written to disk: - name: Fetch API token from an external secret store at runtime   ansible.builtin.debug:     msg: "{{ lookup('community.hashi_vault.vault_kv2_get', 'apps/payments').secret.api_token }}"   no_log: true This keeps the source of truth in a system built for rotation, auditing, and access control, while Ansible simply requests what it needs when it runs. It also shrinks the number of long-lived secrets you have to manage manually, which is where drift and stale credentials usually creep in. A practical rule of thumb: use Vault for values that change rarely and belong with the code (feature flags, service configuration, seed credentials for a fresh environment), and use an external store for anything shared across teams, rotated on a schedule, or subject to compliance auditing. Mixing the two is normal and expected; the important thing is that no unencrypted secret ever reaches a commit, a log line, or a CI artifact. Keeping secrets out of playbooks, inventory, and logs Encryption only helps if secrets do not leak elsewhere. Two quiet failure modes cause most incidents. First, inventory and playbook sprawl. Connection passwords in a plaintext inventory file, tokens hardcoded in a task, or private keys checked into version control are all common. Keep credentials in Vault-encrypted vars or an external store, and add inventory secrets to .gitignore patterns so they cannot be committed by accident. Second, logs. Ansible echoes task results by default, and a task that handles a password can print it straight to the console or CI output. Use no_log: true on any task that touches sensitive data: - name: Create database user   community.postgresql.postgresql_user:     name: app     password: "{{ db_password }}"   no_log: true Be mindful that verbose modes and callbacks can still surface data, so avoid passing secrets through command/shell where they may appear in process listings or logs, and prefer modules that accept credentials as parameters. Privilege and become: least privilege by default Ansible's privilege escalation is powerful and easy to over-apply. Setting become: true at the play level so every task runs as root is convenient and risky. If any task is compromised or misbehaves, it does so with full root authority. Apply least privilege instead: Escalate only where needed. Put become: true on the individual tasks that require it, not the whole play. Escalate to the right user, not always root. Use become_user to run tasks as the specific service account that owns the resource. Scope sudo tightly. On managed hosts, grant the automation account sudo rights only for the specific commands it needs rather than blanket ALL access. - name: Restart the app service (scoped escalation)   ansible.builtin.systemd:     name: myapp     state: restarted   become: true   become_user: root The goal is that a runaway or hijacked task has the smallest possible blast radius. It also helps to separate the account Ansible connects as from the privileges it escalates to. Connect as an unprivileged automation user over SSH, then escalate only for the specific tasks that need it. That way a stolen SSH key does not automatically equal root on every managed host, and your sudo policy on those hosts becomes an auditable second line of defense that you control independently of the playbooks themselves. Module safety: prefer modules over shell Ansible ships hundreds of idempotent modules that understand the state they manage. Reach for them before dropping to shell, command, or raw. Those three run arbitrary commands, are not idempotent, and, most importantly, are the primary vector for command injection when they interpolate unvalidated variables: # Risky: an attacker-controlled username can inject commands - name: Add user (unsafe)   ansible.builtin.shell: "useradd {{ username }}" # Safer: the module validates and handles the value - name: Add user (safe)   ansible.builtin.user:     name: "{{ username }}"     state: present If a variable comes from an external source (an API, user input, a survey), treat it as untrusted. When you genuinely must use shell or command, validate inputs, use quote filters, and avoid building command strings by concatenation. Prefer command over shell where possible, since command does not run through a shell and therefore does not interpret pipes, redirects, and other metacharacters that widen the injection surface. Modules give you idempotence, better error handling, and a much smaller injection surface for free. Template (Jinja2) injection Ansible renders variables and templates through Jinja2, and templating untrusted input can lead to server-side template injection, where crafted values execute expressions rather than being treated as data. Never render values you do not control directly into templates or command strings. Keep externally supplied data as data: pass it as module parameters, apply the quote filter where relevant, and be cautious with constructs that evaluate strings. Review template: tasks that pull in variables originating from outside your controlled inventory. SSH and connection security Ansible reaches hosts primarily over SSH, so connection hygiene is core to Ansible security. Manage keys properly. Use dedicated SSH keys for automation, protect private keys, and prefer an SSH agent or a short-lived certificate authority over long-lived keys scattered across machines. Do not disable host key checking. It is tempting to set host_key_checking = False to silence prompts, but that removes protection against man-in-the-middle attacks. Instead, manage known_hosts so new hosts are trusted deliberately. Prefer key-based auth over passwords, and avoid embedding connection passwords in inventory. Supply chain: pin and vet collections and roles Modern Ansible pulls collections and roles from Galaxy and other sources. Each one is code that runs in your environment, so treat it as part of your software supply chain. Unpinned dependencies mean a compromised or changed upstream can silently alter what your automation does. Pin exact versions in requirements.yml and vet where content comes from: collections:   - name: community.postgresql     version: "3.4.0" roles:   - src: https://github.com/example/hardening-role     version: "v2.1.0" Install from that manifest (ansible-galaxy install -r requirements.yml), review updates before bumping versions, and favor well-maintained, reputable sources. Rebuilding from a pinned manifest also makes your runs reproducible, and it gives you a single place to audit exactly what third-party code is executing in your environment. When you do update a dependency, read the changelog and diff the change the same way you would review application code, because a role that manages system state can do anything the account running it can do. Idempotence and drift Idempotence is a security property, not just a correctness one. A playbook that produces the same result no matter how many times it runs lets you re-apply your desired state to correct configuration drift. When automation is the authoritative definition of a system, unexpected changes stand out and manual tampering gets overwritten on the next run. Favor modules and declarative state (state: present, state: absent) over imperative shell steps so your playbooks converge reliably. Linting and scanning playbooks in CI The most reliable way to keep secure Ansible playbooks secure is to check them automatically on every change. Two layers work together. ansible-lint catches risky patterns and anti-patterns: use of command where a module exists, missing no_log, deprecated syntax, and more. Run it in CI so problems fail the pipeline early: # In your CI pipeline - ansible-lint playbooks/ Alongside linting, run an IaC security scan that understands misconfigurations and exposed secrets across your automation and the infrastructure it defines. Linting enforces good style and known anti-patterns; security scanning looks for the risky outcomes, such as leaked credentials, over-permissive escalation, and insecure defaults, and grades them by severity. Together they turn security from a manual review step into an automated gate. If you are securing IaC beyond Ansible, the same CI-first approach applies to your other tools, covered in our Terraform security and Kubernetes security deep dives. How Rainforest helps Rainforest brings Ansible into the same security workflow as the rest of your codebase. Our IaC security scanning analyzes your automation and infrastructure definitions for misconfigurations, exposed secrets, and insecure defaults, then surfaces findings with the context and severity your team needs to act. Because it plugs into the pipeline as part of the broader application security testing platform, the same scan runs on every merge request, so risky playbooks are flagged before they ship, not after an incident. The result is automation you can trust: encrypted secrets, least-privilege escalation, safe modules, and a CI gate that keeps every change honest. Ready to see it on your own playbooks? Book a demo and we will walk through it with your stack.

Frequently asked questions

Is Ansible secure?

Ansible can be very secure, but security depends on how you use it. Its agentless, SSH-based design and large library of idempotent modules give you a strong foundation. Risk comes from how you handle secrets, privilege escalation, untrusted input, and dependencies. Following practices like using Ansible Vault, scoping become, preferring modules over shell, and scanning playbooks in CI makes Ansible a secure automation tool.

What is Ansible Vault?

Ansible Vault is Ansible's built-in feature for encrypting sensitive data such as passwords, API tokens, and keys. It uses symmetric AES-256 encryption so you can safely store secrets in the same repository as your playbooks. You can encrypt entire files or individual variables, and you supply the Vault password at runtime rather than committing it.

How do I keep secrets out of Ansible playbooks?

Encrypt sensitive variables and files with Ansible Vault, or pull them at runtime from an external secret store using lookup plugins so nothing is written to disk. Never commit plaintext credentials or private keys, keep secret-bearing inventory files out of version control, and add no_log: true to tasks that handle sensitive data so they do not leak into logs.

Is it safe to use the shell/command modules?

They are safe only with care. shell, command, and raw run arbitrary commands, are not idempotent, and can allow command injection if they interpolate unvalidated variables. Prefer a purpose-built module whenever one exists. When you must use them, treat external input as untrusted, validate and quote values, and avoid building command strings by concatenation.

How do I scan Ansible playbooks for security issues?

Use two layers in CI. Run ansible-lint to catch risky patterns and anti-patterns, and run an IaC security scan to detect misconfigurations, exposed secrets, and over-permissive settings graded by severity. Rainforest's IaC security scanning integrates into your pipeline so every change to your playbooks is checked automatically before it ships.

Bruno Baldo

Written by

Bruno Baldo

CMO

Um pouco de marketing e um pouco de curiosidade e temos a receita pra criar um apaixonado por cyber!

Keep reading