Skip to content
DevOps devops iac 6 min read

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.

MethodWhen to useWhen NOT to use
aptYou 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.
pipYou 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_host is the real IP address or DNS name Ansible connects to.
  • [all:vars] sets variables for every host. all is a built-in group that contains every server.
  • ansible_user is the Linux username Ansible logs in as over SSH (here, deploy).
  • ansible_ssh_private_key_file points to your SSH private key, so Ansible logs in without a password.
  • ansible_python_interpreter tells 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, which ssh-copy-id handles for you. Treat ~/.ssh/id_ed25519 like 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 deploy user instead of logging in as root.
  • Group hosts by role (web, db, cache) so you can target subsets cleanly.
  • Pin ansible_python_interpreter to /usr/bin/python3 to avoid interpreter-discovery surprises.
  • Always run ansible all -m ping after editing your inventory, before running real changes.
  • Add a project-local ansible.cfg so 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.
Last updated June 15, 2026
Was this helpful?