Managing multiple servers manually is time-consuming, error-prone, and simply does not scale. Ansible solves this problem by letting you define your infrastructure as code and apply configurations across hundreds of servers with a single command. This step-by-step guide will take you from installation to managing a multi-server environment.
Prerequisites
Before starting, ensure you have:
- A control node running Linux (Ubuntu 22.04+ or AlmaLinux 9+ recommended)
- At least 2-3 target servers (can be VMs for practice)
- SSH access to all target servers
- Python 3.9+ on all systems
- Root or sudo access on the control node
Step 1: Install Ansible on the Control Node
Install Ansible using your distribution's package manager or pip:
# Ubuntu/Debian
sudo apt update
sudo apt install -y ansible
# AlmaLinux/RHEL
sudo dnf install -y ansible-core
# Via pip (any distribution)
pip3 install ansible
# Verify installation
ansible --version
Step 2: Set Up SSH Key Authentication
Ansible communicates with managed nodes over SSH. Set up key-based authentication for passwordless access:
# Generate SSH key pair (if you don't have one)
ssh-keygen -t ed25519 -C "ansible-controller"
# Copy public key to each managed node
ssh-copy-id user@webserver1.example.com
ssh-copy-id user@webserver2.example.com
ssh-copy-id user@dbserver1.example.com
# Test connectivity
ssh user@webserver1.example.com "hostname"
Step 3: Create Your Inventory File
The inventory defines your managed servers and how they are organized:
# /etc/ansible/hosts or ./inventory/hosts.ini
[webservers]
web1 ansible_host=192.168.1.10 ansible_user=admin
web2 ansible_host=192.168.1.11 ansible_user=admin
[dbservers]
db1 ansible_host=192.168.1.20 ansible_user=admin
db2 ansible_host=192.168.1.21 ansible_user=admin
[monitoring]
monitor1 ansible_host=192.168.1.30 ansible_user=admin
[production:children]
webservers
dbservers
monitoring
[production:vars]
ansible_python_interpreter=/usr/bin/python3
ansible_become=yes
Step 4: Configure ansible.cfg
Create a project-level configuration file:
# ansible.cfg in your project root
[defaults]
inventory = ./inventory/hosts.ini
remote_user = admin
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
[privilege_escalation]
become = True
become_method = sudo
become_ask_pass = False
Step 5: Test Connectivity
# Ping all hosts
ansible all -m ping
# Ping specific group
ansible webservers -m ping
# Gather facts from all hosts
ansible all -m setup --tree /tmp/facts
Step 6: Create Your First Playbook
Create a playbook that configures all servers with common settings:
# playbooks/common.yml
---
- name: Common Server Configuration
hosts: all
become: yes
vars:
common_packages:
- vim
- htop
- curl
- wget
- unzip
- git
- fail2ban
timezone: "Europe/Amsterdam"
ntp_servers:
- 0.pool.ntp.org
- 1.pool.ntp.org
tasks:
- name: Update package cache
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install common packages
package:
name: "{{ common_packages }}"
state: present
- name: Set timezone
timezone:
name: "{{ timezone }}"
- name: Configure NTP
template:
src: templates/ntp.conf.j2
dest: /etc/ntp.conf
notify: Restart NTP
- name: Enable and start fail2ban
systemd:
name: fail2ban
state: started
enabled: yes
- name: Set up automatic security updates
apt:
name: unattended-upgrades
state: present
when: ansible_os_family == "Debian"
handlers:
- name: Restart NTP
systemd:
name: ntp
state: restarted
Step 7: Create Role-Specific Playbooks
# playbooks/webservers.yml
---
- name: Configure Web Servers
hosts: webservers
become: yes
tasks:
- name: Install NGINX
apt:
name: nginx
state: present
- name: Deploy NGINX config
template:
src: templates/nginx-site.conf.j2
dest: /etc/nginx/sites-available/default
notify: Reload NGINX
- name: Configure firewall for HTTP/HTTPS
ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- "80"
- "443"
handlers:
- name: Reload NGINX
systemd:
name: nginx
state: reloaded
Step 8: Run Your Playbooks
# Run common configuration on all servers
ansible-playbook playbooks/common.yml
# Run with verbose output for debugging
ansible-playbook playbooks/common.yml -vv
# Dry run (check mode)
ansible-playbook playbooks/common.yml --check --diff
# Limit to specific hosts
ansible-playbook playbooks/common.yml --limit web1
# Run with tags
ansible-playbook playbooks/common.yml --tags "packages,firewall"
Best Practices for Multi-Server Management
- Use roles — Organize related tasks into reusable roles
- Version control everything — Keep playbooks, inventory, and templates in Git
- Use Ansible Vault — Encrypt sensitive variables like passwords and API keys
- Test in staging first — Always run playbooks against non-production environments first
- Use check mode — Preview changes with
--check --diffbefore applying
With this setup, you can manage your entire server fleet from a single control node. As your infrastructure grows, Ansible scales with you.
Further Reading
- Ansible Automation: From Zero to Production — The complete Ansible reference
- Linux System Administration Handbook — Master the systems you'll automate
- SSH Mastery — Deep dive into the protocol Ansible relies on