Installing Ansible & Inventory
Ansible is a tool that lets you configure many servers at once from a single control machine, without installing any agent (a background program) on the servers themselves. It works over SSH (Secure Shell, the standard encrypted way to log in to a remote Linux server), so the only thing your target servers need is an SSH server and Python. This page sets up Ansible end to end: you install it, tell it which servers to manage with an inventory file, and prove the connection works before you ever write a playbook (an automation script, covered on its own page).
How Ansible connects to your servers
Ansible runs on one machine you control. That machine is called the control node. From there, Ansible reaches out over SSH to your managed nodes (the servers you want to configure). It copies tiny Python scripts to each server, runs them, collects the result, and deletes them. Nothing stays behind.
This “push” model matters because it keeps your servers clean. You do not install Ansible on every server. You install it once, on the control node (often your laptop, a small management server, or a CI/CD runner).
Run the control node on Linux or macOS. On Windows, use WSL (Windows Subsystem for Linux). Ansible’s control node is not officially supported running natively on Windows, though Windows servers can be managed as nodes.
Installing Ansible on Ubuntu
There are two common ways to install on Ubuntu 22.04 or 24.04 LTS: the system package manager apt, or Python’s package manager pip. Here is how to choose.
| Method | When to use | When NOT to use |
|---|---|---|
apt | You want a stable, system-wide install that updates with the OS. Simplest for beginners. | You need the very latest Ansible version, or a specific pinned version. |
pip | You want the newest release, multiple versions, or an isolated install in a virtual environment. | You want zero Python tooling and prefer the OS to manage upgrades. |
Option A — install with apt
This is the recommended path for most people. Adding the official PPA (Personal Package Archive, an extra apt source) gives you a newer Ansible than Ubuntu ships by default.
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install -y ansible
Verify it installed:
ansible --version
Output:
ansible [core 2.17.4]
config file = None
python version = 3.12.3 (main, Sep 11 2024, 14:17:37) [GCC 13.2.0]
jinja version = 3.1.4
executable location = /usr/bin/ansible
Option B — install with pip
Use this when you need the latest version or want it isolated from system Python.
sudo apt update
sudo apt install -y python3-pip python3-venv
python3 -m venv ~/ansible-venv
source ~/ansible-venv/bin/activate
pip install ansible
ansible --version
The python3 -m venv command creates a virtual environment (a self-contained Python folder), so this Ansible install never clashes with other Python tools. Remember to run source ~/ansible-venv/bin/activate each new terminal session before using Ansible.
The inventory file
An inventory is a plain text file that lists the servers Ansible manages. You can group servers (for example, all web servers in one group, all databases in another) so you can target a whole group with one command. Ansible reads /etc/ansible/hosts by default, but the modern best practice is to keep an inventory file inside your project directory and point to it explicitly.
Create a project folder and an inventory file:
mkdir -p ~/ansible-project
cd ~/ansible-project
Create inventory.ini with this content:
[web]
web1 ansible_host=203.0.113.10
web2 ansible_host=203.0.113.11
[db]
db1 ansible_host=203.0.113.20
[all:vars]
ansible_user=deploy
ansible_ssh_private_key_file=~/.ssh/id_ed25519
ansible_python_interpreter=/usr/bin/python3
What each piece means:
[web]and[db]are groups. The names inside (web1,db1) are friendly aliases.ansible_hostis the real IP address or DNS name Ansible connects to.[all:vars]sets variables for every host.allis a built-in group that contains every server.ansible_useris the Linux username Ansible logs in as over SSH (here,deploy).ansible_ssh_private_key_filepoints to your SSH private key, so Ansible logs in without a password.ansible_python_interpretertells Ansible which Python to use on the managed node. Setting it avoids guesswork on modern Ubuntu.
When to use groups
Group servers by role, not by location. A [web] group lets you run “install Nginx everywhere” in one command. If you also need geography, use child groups. Do NOT put every server in all with no groups — you lose the ability to target subsets, which is the whole point.
Set up SSH key access
Ansible needs to log in without typing a password each time. Generate an SSH key pair on the control node (if you do not have one) and copy the public key to each managed node.
ssh-keygen -t ed25519 -C "ansible-control"
ssh-copy-id [email protected]
ssh-copy-id [email protected]
ssh-copy-id [email protected]
ssh-keygen creates the key pair. ssh-copy-id installs your public key on the server so future logins are passwordless. The deploy user on each server should also have sudo rights if your playbooks install packages.
Never copy your private key to managed nodes — only the
.pub(public) key, whichssh-copy-idhandles for you. Treat~/.ssh/id_ed25519like a password: anyone who steals it can log in as you.
Verify connectivity with an ad-hoc command
An ad-hoc command is a one-off Ansible command you run directly in the terminal, without writing a playbook. The classic first test is the ping module, which checks that Ansible can reach a host and run Python there. (This is not a network ping — it is an end-to-end SSH-plus-Python check.)
cd ~/ansible-project
ansible all -m ping -i inventory.ini
Here all targets every host, -m ping selects the ping module, and -i inventory.ini points to your inventory.
Output:
web1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
web2 | SUCCESS => {
"changed": false,
"ping": "pong"
}
db1 | SUCCESS => {
"changed": false,
"ping": "pong"
}
A "pong" reply from every host means SSH, the user, the key, and Python all work. You can also target a single group:
ansible web -m ping -i inventory.ini
If a host fails, the error usually points to the cause — UNREACHABLE means SSH or the IP is wrong, while Permission denied means the wrong user or key. To save typing -i inventory.ini every time, create an ansible.cfg in the project folder:
[defaults]
inventory = ./inventory.ini
host_key_checking = False
With that file present, ansible all -m ping works on its own.
Best Practices
- Keep your inventory file in the project’s Git repository, but never commit private SSH keys or secrets to it.
- Use SSH keys, not passwords, and create a dedicated
deployuser instead of logging in asroot. - Group hosts by role (
web,db,cache) so you can target subsets cleanly. - Pin
ansible_python_interpreterto/usr/bin/python3to avoid interpreter-discovery surprises. - Always run
ansible all -m pingafter editing your inventory, before running real changes. - Add a project-local
ansible.cfgso teammates get the same inventory and settings automatically. - Prefer the apt PPA for stable installs; use a pip virtual environment only when you need a newer or pinned version.