Ansible Best Practices 2025: Project Structure, Security, and Performance
As Ansible continues to evolve, so do the best practices for using it effectively. This guide covers the essential best practices for Ansible in 2025, focusing on project structure, security, performance optimization, and maintainability. Whether you are new to Ansible or looking to improve existing automation, these practices will help you build robust and scalable infrastructure as code.
📑 Table of Contents
- Project Structure
- Key Principles
- Writing Idempotent Playbooks
- Good Practice
- Avoid These Patterns
- Role Design Best Practices
- Single Responsibility
- Use Defaults Wisely
- Document Your Roles
- Security Best Practices
- Use Ansible Vault for Secrets
- Vault File Structure
- Limit Privilege Escalation
- Performance Optimization
- ansible.cfg Tuning
- Limit Fact Gathering
- Use async for Long Tasks
- Testing Your Playbooks
- Syntax Check
- Dry Run (Check Mode)
- Use Molecule for Role Testing
- Use Collections
- Conclusion
Project Structure
A well-organized project structure is crucial for maintainability and collaboration:
ansible-project/
├── ansible.cfg
├── requirements.yml
├── inventory/
│ ├── production/
│ │ ├── hosts.yml
│ │ ├── group_vars/
│ │ │ ├── all.yml
│ │ │ ├── webservers.yml
│ │ │ └── databases.yml
│ │ └── host_vars/
│ │ └── server1.yml
│ └── staging/
│ ├── hosts.yml
│ └── group_vars/
│ └── all.yml
├── playbooks/
│ ├── site.yml
│ ├── webservers.yml
│ └── databases.yml
├── roles/
│ ├── common/
│ ├── nginx/
│ ├── postgresql/
│ └── security/
└── collections/
└── requirements.yml
Key Principles
- Separate environments – Keep production and staging inventories isolated
- Use group_vars – Avoid hardcoding values in playbooks
- Host-specific configs – Use host_vars for unique settings
- Version everything – Store in Git with meaningful commits
Writing Idempotent Playbooks
Idempotency means running a playbook multiple times produces the same result. This is critical for reliable automation.
Good Practice
- name: Ensure nginx is installed
ansible.builtin.package:
name: nginx
state: present
- name: Ensure nginx config exists
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
notify: Reload nginx
- name: Ensure nginx is running
ansible.builtin.service:
name: nginx
state: started
enabled: true
Avoid These Patterns
# BAD: Not idempotent - runs every time
- name: Add line to file
ansible.builtin.shell: echo "some config" >> /etc/myapp.conf
# GOOD: Idempotent - only changes if needed
- name: Ensure line exists in file
ansible.builtin.lineinfile:
path: /etc/myapp.conf
line: "some config"
state: present
Role Design Best Practices
Single Responsibility
Each role should do one thing well:
# Good role structure
roles/
├── nginx/ # Just web server
├── certbot/ # Just SSL certificates
├── firewall/ # Just firewall rules
└── users/ # Just user management
Use Defaults Wisely
# roles/nginx/defaults/main.yml
---
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_server_tokens: "off"
nginx_ssl_protocols: "TLSv1.2 TLSv1.3"
Document Your Roles
# roles/nginx/README.md
# Nginx Role
## Requirements
- RHEL 8+ or Ubuntu 20.04+
## Role Variables
| Variable | Default | Description |
|----------|---------|-------------|
| nginx_worker_processes | auto | Number of worker processes |
## Example Playbook
```yaml
- hosts: webservers
roles:
- nginx
```
Security Best Practices
Use Ansible Vault for Secrets
Never store passwords or API keys in plain text:
# Create encrypted file
ansible-vault create group_vars/all/vault.yml
# Edit encrypted file
ansible-vault edit group_vars/all/vault.yml
# Run playbook with vault
ansible-playbook site.yml --ask-vault-pass
# Or use password file
ansible-playbook site.yml --vault-password-file ~/.vault_pass
Vault File Structure
# group_vars/all/vault.yml (encrypted)
---
vault_db_password: "supersecret123"
vault_api_key: "abc123xyz"
# group_vars/all/vars.yml (plain text, references vault)
---
db_password: "{{ vault_db_password }}"
api_key: "{{ vault_api_key }}"
Limit Privilege Escalation
# Only use become when necessary
- name: Install packages (needs root)
ansible.builtin.package:
name: nginx
state: present
become: true
- name: Deploy app config (no root needed)
ansible.builtin.template:
src: app.conf.j2
dest: /home/appuser/app.conf
become: false
Performance Optimization
ansible.cfg Tuning
[defaults]
# Increase parallelism
forks = 20
# Faster SSH
host_key_checking = False
# Reduce output noise
callback_whitelist = profile_tasks
# Use smart gathering
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 3600
[ssh_connection]
# Enable pipelining (major speed boost)
pipelining = True
# SSH optimizations
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
Limit Fact Gathering
# Disable if not needed
- hosts: all
gather_facts: false
tasks:
- name: Simple task not needing facts
ansible.builtin.ping:
# Or gather only what you need
- hosts: all
gather_facts: true
gather_subset:
- network
- hardware
Use async for Long Tasks
- name: Run long backup job
ansible.builtin.command: /usr/local/bin/backup.sh
async: 3600
poll: 0
register: backup_job
- name: Check backup status
ansible.builtin.async_status:
jid: "{{ backup_job.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 60
delay: 60
Testing Your Playbooks
Syntax Check
# Always check syntax before running
ansible-playbook site.yml --syntax-check
Dry Run (Check Mode)
# See what would change without making changes
ansible-playbook site.yml --check --diff
Use Molecule for Role Testing
# Install molecule
pip install molecule molecule-docker
# Initialize role with molecule
molecule init role my_role
# Run tests
cd roles/my_role
molecule test
Use Collections
Collections are the modern way to distribute Ansible content:
# collections/requirements.yml
---
collections:
- name: ansible.posix
version: ">=1.5.0"
- name: community.general
version: ">=8.0.0"
- name: amazon.aws
version: ">=7.0.0"
# Install collections
ansible-galaxy collection install -r collections/requirements.yml
Conclusion
Following these best practices will make your Ansible automation more reliable, secure, and maintainable. The key principles are:
- Organize projects consistently
- Write idempotent playbooks
- Keep roles focused and documented
- Secure secrets with Vault
- Optimize for performance
- Test before deploying
As AI tools like Ansible Lightspeed become more prevalent, these fundamentals become even more important. Good structure and practices ensure that AI-generated code integrates smoothly into your existing automation.
Was this article helpful?
About Ramesh Sundararamaiah
Red Hat Certified Architect
Expert in Linux system administration, DevOps automation, and cloud infrastructure. Specializing in Red Hat Enterprise Linux, CentOS, Ubuntu, Docker, Ansible, and enterprise IT solutions.