Get Started in 5 Minutes
If you’re managing 3+ Linux servers and every update means SSH-ing into each one to run commands — I know that feeling. Ansible solves this in the simplest way possible: SSH into each machine, run the commands, done. No agent installation, no background daemons, no extra ports to open.
Install Ansible on the Control Node (CentOS Stream 9)
# Enable EPEL repository
sudo dnf install epel-release -y
# Install Ansible
sudo dnf install ansible -y
# Check version
ansible --version
Create Your First Inventory File
The inventory file is a list of servers you want to manage. Create a hosts.ini file in your project directory:
[webservers]
web1 ansible_host=192.168.1.10
web2 ansible_host=192.168.1.11
[dbservers]
db1 ansible_host=192.168.1.20
[all:vars]
ansible_user=admin
ansible_ssh_private_key_file=~/.ssh/id_rsa
Run Your First Command
# Ping all servers to check connectivity
ansible all -i hosts.ini -m ping
# Check uptime of the webservers group
ansible webservers -i hosts.ini -m command -a "uptime"
# Check disk usage on all machines
ansible all -i hosts.ini -m command -a "df -h /"
If you see "ping": "pong" returned from the machines — you’ve connected successfully and are ready to automate.
How Does Ansible Work?
When most people hear “automation tool,” they immediately think of installing additional services on every server. Ansible is different: it uses SSH — the same thing you use every day to connect to servers — as its only communication channel.
The architecture has 2 parts:
- Control Node: The machine where you install Ansible (your laptop or a jump server). You only need this one machine.
- Managed Nodes: The servers you want to manage. They only need SSH and Python 3 — available on most modern Linux distros.
When you run an Ansible command, it follows this sequence:
- Reads the inventory file to determine which machines to connect to
- SSH into each managed node (in parallel if using
-f) - Uploads a temporary Python module to
/tmp - Executes the module, collects results, and immediately removes the temp files
The entire process happens over SSH — nothing remains on the managed node after execution. This is why Ansible is called agentless.
Writing Real-World Playbooks
Ad-hoc commands are great for quick one-off tasks. When you need to execute multiple steps in sequence, you need a Playbook — a YAML file that describes “what to do, on which machines, and in what order.”
Example: Playbook to Install Nginx on Web Servers
---
- name: Install and configure Nginx
hosts: webservers
become: yes # run with sudo
vars:
nginx_port: 80
tasks:
- name: Install nginx
dnf:
name: nginx
state: present
- name: Start nginx and enable on boot
systemd:
name: nginx
state: started
enabled: yes
- name: Open port 80 on firewall
firewalld:
port: "{{ nginx_port }}/tcp"
permanent: yes
state: enabled
immediate: yes
- name: Create index.html page
copy:
content: "<h1>Server {{ inventory_hostname }} — Deployed by Ansible</h1>"
dest: /var/www/html/index.html
owner: nginx
group: nginx
mode: '0644'
handlers:
- name: Reload nginx
systemd:
name: nginx
state: reloaded
# Run the playbook
ansible-playbook -i hosts.ini deploy_nginx.yml
# Dry-run: check changes without applying them
ansible-playbook -i hosts.ini deploy_nginx.yml --check --diff
The --check --diff flags are extremely useful when you’re not sure what a playbook will change — they show you exactly which files and lines would be modified without actually touching the system.
Real-World Story: Migrating 5 Servers in One Week
When CentOS 8 reached EOL, I had to urgently migrate 5 servers to Rocky Linux in one week. My initial plan was to do it manually — SSH into each machine, follow an Excel checklist. By the third server, mistakes were already creeping in: forgot to re-enable SELinux after testing on one, installed the wrong PHP version on another, missed the JST timezone configuration step on a third…
That’s when I stopped and wrote an Ansible playbook for the entire migration process. It took about 2 hours to write and test — but the remaining 2 servers ran the playbook in 8 minutes each, perfectly consistent, zero mistakes.
That playbook was then reused many times when provisioning new servers — no need to remember each step, no checklist needed, just ansible-playbook provision.yml.
Lesson learned: Write a playbook even if you only have 2–3 servers. The initial writing cost is far less than the debugging cost when you make a mistake on step 4 doing it manually.
Organizing Code with Roles
When a playbook exceeds 50 tasks, it’s time to split it into Roles — a standard directory structure that enables reuse and sharing across projects.
# Initialize a new role
ansible-galaxy init roles/nginx
Directory structure after creation:
roles/nginx/
├── tasks/
│ └── main.yml # Main tasks
├── handlers/
│ └── main.yml # Handlers (restart/reload service)
├── templates/
│ └── nginx.conf.j2 # Jinja2 template with dynamic variables
├── vars/
│ └── main.yml # Fixed variables
└── defaults/
└── main.yml # Default variables (can be overridden)
Using roles in a playbook is much cleaner:
---
- name: Setup web server stack
hosts: webservers
become: yes
roles:
- common # timezone, hostname, sysctl
- nginx
- php-fpm
- firewall
Practical Tips
1. Protect Sensitive Information with Ansible Vault
# Encrypt files containing passwords/API keys
ansible-vault encrypt secrets.yml
# View the contents of an encrypted file
ansible-vault view secrets.yml
# Run playbook with vault
ansible-playbook site.yml --ask-vault-pass
# Or use a password file (suitable for CI/CD)
ansible-playbook site.yml --vault-password-file ~/.vault_pass
2. Speed Up Execution with Many Servers
# By default Ansible runs 5 hosts in parallel
# Increase to 20 if your infrastructure is large enough
ansible-playbook -i hosts.ini site.yml -f 20
# Or set it in ansible.cfg
[defaults]
forks = 20
3. Check Playbook Quality Before Using in Production
# Install ansible-lint
pip install ansible-lint
# Lint the entire project
ansible-lint
# Quick syntax check
ansible-playbook deploy.yml --syntax-check
4. Create an ansible.cfg File to Avoid Specifying -i Every Time
[defaults]
inventory = hosts.ini
remote_user = admin
private_key_file = ~/.ssh/id_rsa
host_key_checking = False
forks = 10
[privilege_escalation]
become = True
become_method = sudo
With this ansible.cfg file, the command becomes simply:
ansible-playbook deploy_nginx.yml
Ansible doesn’t require you to be a senior DevOps engineer to get started. Begin with a simple inventory file and a few ad-hoc commands to get comfortable — within a few days you’ll find yourself never wanting to manually SSH into individual machines again. That’s a good sign.

