How to Install Ansible on Ubuntu 26.04 and Write Your First Playbook

By 

Published on

8 min read

Ansible installation and first playbook on Ubuntu 26.04

When you manage more than a handful of servers, running the same commands on each one over SSH stops scaling. Ansible solves that by describing the desired state of your infrastructure in YAML files, then applying that state across many hosts in parallel. There is no agent to install on the targets and no central server to maintain; Ansible only needs SSH access and Python on the managed hosts.

This guide explains how to install Ansible on Ubuntu 26.04, set up an inventory of remote hosts, run ad hoc commands, and write a small playbook that installs and starts Nginx on every host in a group.

Quick Reference

TaskCommand
Install Ansible (apt)sudo apt install ansible
Show versionansible --version
Ping all hostsansible all -m ping
Run an ad hoc commandansible web -a "uptime"
Run a playbookansible-playbook site.yml
Check an established playbookansible-playbook --check --diff site.yml
Limit to one hostansible-playbook site.yml --limit web1
List inventory hostsansible-inventory --list
Edit a vault fileansible-vault edit secrets.yml
Install a collectionansible-galaxy collection install community.general

Prerequisites

Before installing Ansible, make sure you have:

  • A control node running Ubuntu 26.04 with a user with sudo privileges . This is the machine where you run the ansible commands.
  • One or more managed hosts you can reach over SSH from the control node. Ansible runs on the managed hosts using the Python interpreter that ships with Ubuntu, so no extra setup is required there.
  • SSH key-based authentication from the control node to each managed host. Password authentication works but is slower and limits automation.

Step 1: Install Ansible

Ansible is available in the Ubuntu 26.04 repositories. The ansible package installs ansible-core plus a curated set of community collections:

Terminal
sudo apt update
sudo apt install ansible

Confirm the install and the Python version Ansible is using:

Terminal
ansible --version

The output shows the Ansible version, the Python interpreter path, and the location of the default config file:

output
ansible [core 2.20.x]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  python version = 3.14.x

Ubuntu 26.04 provides the Ansible 13 community package with ansible-core 2.20. The exact patch versions may be newer after regular package updates.

If you need a release that is newer than the Ubuntu package, install Ansible in an isolated environment with pipx. This avoids changing packages in the system Python environment:

Terminal
sudo apt install pipx
pipx ensurepath
pipx install --include-deps ansible

Start a new shell after pipx ensurepath if the ansible command is not immediately available. The Ansible PPA does not currently publish an Ubuntu 26.04 package, so do not use it for this release.

Step 2: Create an Inventory File

The inventory is the list of hosts Ansible can talk to. Create a working directory for your project and a small inventory:

Terminal
mkdir -p ~/ansible-demo
cd ~/ansible-demo
nano inventory.ini

Add the following content. Replace the IP addresses with your own:

ini
[web]
web1 ansible_host=192.168.1.21
web2 ansible_host=192.168.1.22

[db]
db1 ansible_host=192.168.1.31

[all:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3

The [web] and [db] sections define groups. Each entry is a host alias followed by connection variables. The [all:vars] block sets defaults for every host. Set ansible_user to the SSH user on the managed hosts (often ubuntu on cloud images or your own user on a private fleet).

Tell Ansible to use this inventory by default by creating a project-level config:

Terminal
nano ansible.cfg

Add:

ini
[defaults]
inventory = ./inventory.ini

Ansible keeps SSH host-key checking enabled by default. Before the first Ansible run, connect to each server with SSH, verify its fingerprint, and accept the key:

Terminal
ssh ubuntu@192.168.1.21

Repeat this for each managed host. Verifying host keys protects the control node from connecting to an impersonated server.

Step 3: Test the Connection

Use the ping module to confirm Ansible can reach every host in the inventory. Despite the name, this module does not use ICMP; it logs in over SSH and runs a tiny Python module on the target:

Terminal
ansible all -m ping

A successful run prints a green pong for each host:

output
web1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

If you see UNREACHABLE, check the SSH key, the user name in ansible_user, and basic connectivity with ssh ubuntu@192.168.1.21.

You can scope commands to a group instead of all. For example, list disk usage on every web host:

Terminal
ansible web -a "df -h /"

The -a flag runs an ad hoc command using the default command module. Use -m shell instead when the command relies on pipes, redirection, or shell expansion.

Step 4: Write Your First Playbook

A playbook is a YAML file that describes the desired state of your hosts. Create one that installs Nginx and makes sure the service is running on every web host:

Terminal
nano site.yml

Add the following content:

yaml
- name: Configure web servers
  hosts: web
  become: true
  tasks:
    - name: Install Nginx
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true

    - name: Ensure Nginx is running and enabled
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true

    - name: Deploy a custom landing page
      ansible.builtin.copy:
        content: "<h1>Managed by Ansible</h1>\n"
        dest: /var/www/html/index.html
        owner: root
        group: root
        mode: "0644"

The play targets the web group and uses become: true so each task runs as root through sudo. The three tasks install the nginx package, make sure the service is started and enabled at boot, and replace the default index page with a small marker.

Step 5: Run the Playbook

Check the YAML and playbook structure before changing the managed hosts:

Terminal
ansible-playbook --syntax-check site.yml

When the syntax check passes, run the playbook:

Terminal
ansible-playbook site.yml

The output ends with a per-host summary that looks like this:

output
PLAY RECAP *********************************************************************
web1 : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2 : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

The changed=3 count means Ansible applied three of the four tasks (the gather facts task counts as ok only). Run the playbook in check mode after the initial configuration:

Terminal
ansible-playbook --check --diff site.yml

The check should report changed=0 because the hosts already match the playbook. Check mode is a simulation, and some modules do not fully support it, so review its output before relying on it for production changes.

Open http://<web_host_ip> in a browser and you should see the custom landing page.

Step 6: Use ansible-vault for Secrets

Most playbooks reference passwords or API tokens. ansible-vault encrypts variable files at rest so you can keep them in version control:

Terminal
ansible-vault create secrets.yml

Set a strong password when prompted. Add encrypted variables to the file, for example:

yaml
db_password: "s3cret-value"
Warning
The example above uses a placeholder password. Replace it with a real strong value, do not commit unencrypted credentials, and store the vault password in a password manager or in a file referenced by --vault-password-file outside the repository.

Reference the vault file from a play with vars_files and run the playbook with --ask-vault-pass or --vault-password-file:

yaml
- name: Configure web servers
  hosts: web
  become: true
  vars_files:
    - secrets.yml

Run the playbook and enter the vault password when prompted:

Terminal
ansible-playbook site.yml --ask-vault-pass

Ansible Vault protects data at rest. Use no_log: true on tasks that may print decrypted secrets, and never commit a vault password file to version control.

Troubleshooting

UNREACHABLE errors on every host
Ansible cannot SSH into the targets. Run ssh ansible_user@host manually to confirm the key, the user, and the network path. Check that ansible_user in the inventory matches the user that owns the authorized SSH key.

sudo: a password is required
The remote user requires a password for sudo and you did not pass one. Run the play with --ask-become-pass or configure passwordless sudo for the deploy user on the managed hosts.

Tasks fail with Could not find python interpreter
The discovery process picked the wrong path. Set ansible_python_interpreter=/usr/bin/python3 in the inventory [all:vars] block to pin it to the Ubuntu default.

Host key verification failed
The server key is missing from ~/.ssh/known_hosts, or it changed since the previous connection. Connect with SSH and verify the fingerprint. If the server was rebuilt, remove only its stale entry with ssh-keygen -R 192.168.1.21, then reconnect and verify the new key.

Playbook reports changed on every run for the same task
A task is not idempotent. Re-check the module you used. For example, prefer ansible.builtin.copy or ansible.builtin.template over command: echo ... > file, because shell redirection always reports changed.

FAQ

What is the difference between Ansible and ansible-core?
ansible-core is the engine and the small set of built-in modules. The ansible package adds a curated bundle of community collections on top of ansible-core. Most users install ansible and pull additional collections with ansible-galaxy as needed.

Do I need to install anything on the managed hosts?
Only Python and an SSH server, both of which ship by default on Ubuntu. See How to Install Python on Ubuntu 26.04 if you need to confirm the interpreter is present.

Can I run Ansible against the local machine?
Yes. Add a localhost ansible_connection=local entry to the inventory, or run a playbook with ansible-playbook -i localhost, -c local site.yml. The trailing comma after localhost tells Ansible to treat the value as an inline inventory rather than a file path.

How do I structure a real project?
Split logic into roles under a roles/ directory, group host variables under group_vars/, and host-specific values under host_vars/. Keep site.yml short and import roles per group. The Ansible documentation calls this layout a “best-practices project”.

Next Steps

You now have a working Ansible setup on Ubuntu 26.04 and a first playbook that configures a group of web servers. From here you can break tasks into roles, parameterize plays with variables and templates, and add CI to run ansible-playbook --check on every push. You can also use the same inventory and roles to prepare hosts before you install Docker on Ubuntu 26.04 .

Linuxize Weekly Newsletter

A quick weekly roundup of new tutorials, news, and tips.

About the authors

Dejan Panovski

Dejan Panovski

Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 800+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.

View author page