Back to Linux guides

Getting Started with Ansible: A Practical Guide

Ayooluwa Isaiah
Updated on March 17, 2025

Ansible is a powerful yet straightforward automation tool that can save you countless hours of repetitive system administration tasks.

In this guide, you'll learn the essentials of Ansible and complete a practical project by setting up a web server automatically.

By the end of this article, you'll have hands-on experience with Ansible's core concepts and a working web server deployment you can build upon for your own projects.

What is Ansible?

Ansible is an open-source automation platform that simplifies configuration management, application deployment, and task automation.

Unlike some other automation tools, Ansible doesn't require special software to be installed on the machines you want to manage. Instead, it communicates with your systems using SSH, making it significantly easier to get started.

At its core, Ansible works by sending instructions from a control machine (your computer) to target machines (servers you want to manage). These instructions, written in YAML, tell the target machines exactly what state they should be in—what software should be installed, how services should be configured, and so on.

Why use Ansible?

The beauty of Ansible lies in its simplicity. Ansible is agentless, meaning you don't need to install and maintain client software on your servers. It uses YAML syntax, which is human-readable and easy to learn even for beginners.

Ansible operations are idempotent, so you can run the same instructions multiple times without causing problems—it only makes changes when needed.

Additionally, Ansible comes with hundreds of built-in modules for managing various systems and services, making it versatile for different automation needs.

Setting up your environment

Before you can start automating, you'll need to install Ansible on your control machine (the computer from which you'll run your commands).

I'll focus on installation for Linux and macOS, as these are the most common platforms for Ansible controllers.

For Ubuntu/Debian systems:

 
sudo apt update
 
sudo apt install ansible

For macOS (using Homebrew):

 
brew install ansible

Verifying your installation

Once installed, verify that Ansible is working correctly by checking its version:

 
ansible --version

You should see output similar to this:

Output
ansible [core 2.16.14]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/ayo/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.13/site-packages/ansible
  ansible collection location = /home/ayo/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.13.2 (main, Feb  4 2025, 00:00:00) [GCC 14.2.1 20250110 (Red Hat 14.2.1-7)] (/usr/bin/python3)
  jinja version = 3.1.5
  libyaml = True

Understanding the inventory

The inventory is simply a list of systems that Ansible will manage. While Ansible supports dynamic inventories that can pull server information from cloud providers or other sources, we'll start with a basic static inventory file.

Creating a simple inventory file

Create a new file named inventory.ini in your working directory:

 
[webservers]
webserver1 ansible_host=192.168.1.10

[database]
db1 ansible_host=192.168.1.11

[all:vars]
ansible_user=your_ssh_user
ansible_ssh_private_key_file=/path/to/your/private_key

This inventory defines a group called webservers containing one server with IP 192.168.1.10, a group called database with one server, and variables that apply to all servers, specifying how to connect via SSH.

For beginners, it's perfectly fine to start with just one server. You can even use localhost for testing:

 
[webservers]
localhost ansible_connection=local

Testing connectivity

Let's verify that Ansible can communicate with your servers using the ping module:

 
ansible webservers -i inventory.ini -m ping

If successful, you should see:

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

If you see "pong" in the response, your connection is working correctly. If you encounter errors, check your SSH configuration and make sure you can manually SSH into the target servers.

Running ad-hoc commands

Before diving into playbooks, let's explore ad-hoc commands. These are one-liner tasks that you might need to perform quickly across multiple servers.

Basic command structure

Ad-hoc commands follow this structure:

 
ansible [group] -i [inventory_file] -m [module] -a "[module arguments]"

Let's try some useful ad-hoc commands, starting with checking system information:

 
ansible webservers -i inventory.ini -m gather_facts
Output
localhost | SUCCESS => {
    "ansible_facts": {
        . . .
    },
    "changed": false,
    "deprecations": [],
    "warnings": []
}

This command returns detailed information about your target servers, including operating system, memory, and network configuration.

Another command ad-hoc command is one that installs a package. On Ubuntu/Debian, use:

 
ansible webservers -i inventory.ini -m apt -a "name=nginx state=present" --become

This installs Nginx on your webservers. The --become flag tells Ansible to use privilege escalation (sudo).

Finally, you can create a file on your target servers with:

 
ansible webservers -i inventory.ini -m copy -a "content='Hello from Ansible' dest=/tmp/ansible_test.txt"
Output
localhost | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "5332d55f8fbdb22d9875ff5650211ed9630b1d68",
    "dest": "/tmp/ansible_test.txt",
    "gid": 1000,
    "group": "ayo",
    "md5sum": "4ed8eb0db4371dcd7ca9d773455bdd82",
    "mode": "0644",
    "owner": "ayo",
    "secontext": "unconfined_u:object_r:user_home_t:s0",
    "size": 18,
    "src": "/home/ayo/.ansible/tmp/ansible-tmp-1742183730.0935683-2183216-224482429836629/source",
    "state": "file",
    "uid": 1000
}

This creates a file with the specified content in the /tmp directory.

Ad-hoc commands are great for quick tasks, but for repeatable operations, playbooks are the way to go so lets explore that next.

Writing your first playbook

Playbooks are Ansible's configuration, deployment, and orchestration language. They describe a policy you want your systems to enforce, using a simple, declarative format.

To get started, create a file named webserver.yml:

webserver.yaml
- name: Setup web server
  hosts: webservers
  become: yes
  tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600
    - name: Install packages
      apt:
        name:
          - nginx
          - ufw
        state: present
    - name: Ensure Nginx is running
      service:
        name: nginx
        state: started
        enabled: yes

This simple playbook targets the webservers group from your inventory, requests privilege escalation (sudo), updates the apt cache if it hasn't been updated in the last hour, installs Nginx, and makes sure Nginx is running and set to start on boot.

Once saved, execute your playbook with:

 
ansible-playbook -i inventory.ini webserver.yaml

You should see output showing the execution of each task:

Output
PLAY [Setup web server] ***********************************************

TASK [Gathering Facts] ***********************************************
ok: [webserver1]

TASK [Update apt cache] **********************************************
ok: [webserver1]

TASK [Install Nginx] ************************************************
changed: [webserver1]

TASK [Ensure Nginx is running] **************************************
changed: [webserver1]

PLAY RECAP **********************************************************
webserver1                  : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Congratulations! You've just automated the installation and configuration of a web server. If you run the playbook again, you'll notice most tasks report as "ok" rather than "changed"—this is Ansible's idempotence at work. It only makes changes when needed.

Expanding your web server project

Now let's enhance our web server by adding a custom homepage and configuring firewall rules.

First, let's create a templates directory and a template for our custom homepage:

 
mkdir templates

Create templates/index.html.j2:

templates/index.html.j2
<!DOCTYPE html>
<html>
<head>
   <title>Welcome to {{ ansible_hostname }}</title>
</head>
<body>
   <h1>Hello from {{ ansible_hostname }}</h1>
   <p>This server was configured automatically using Ansible on {{ ansible_date_time.date }}.</p>
   <p>Server details:</p>
   <ul>
       <li>Distribution: {{ ansible_distribution }} {{ ansible_distribution_version }}</li>
       <li>System: {{ ansible_system }}</li>
       <li>Architecture: {{ ansible_architecture }}</li>
   </ul>
</body>
</html>

Now, update your playbook to use this template:

webserver.yaml
- name: Setup web server
  hosts: webservers
  become: yes
  tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600
    - name: Install packages
      apt:
        name:
          - nginx
          - ufw
        state: present
- name: Create custom index page
template:
src: templates/index.html.j2
dest: /var/www/html/index.html
owner: www-data
group: www-data
mode: '0644'
notify: Reload Nginx
- name: Ensure Nginx is running service: name: nginx state: started enabled: yes
handlers:
- name: Reload Nginx
service:
name: nginx
state: reloaded

The template module uses Jinja2 templating to replace variables in your template with actual values. The variables with ansible_ prefix are gathered by Ansible automatically as facts about the system.

Also notice the introduction of a handler. Handlers are special tasks that only run when notified by another task, typically used for restarting services when their configuration changes.

Configuring firewall rules

Let's add firewall configuration to our playbook to allow HTTP traffic:

webserver.yaml
- name: Setup web server
  hosts: webservers
  become: yes
  tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600
    - name: Install packages
      apt:
        name:
          - nginx
          - ufw
        state: present
    - name: Create custom index page
      template:
        src: templates/index.html.j2
        dest: /var/www/html/index.html
        owner: www-data
        group: www-data
        mode: '0644'
      notify: Reload Nginx
- name: Allow HTTP traffic
ufw:
rule: allow
name: Nginx Full
state: enabled
- name: Enable UFW
ufw:
state: enabled
policy: deny
- name: Ensure Nginx is running service: name: nginx state: started enabled: yes handlers: - name: Reload Nginx service: name: nginx state: reloaded

This updated playbook installs both Nginx and UFW (Uncomplicated Firewall), creates a custom index page, configures the firewall to allow HTTP and HTTPS traffic, enables the firewall with a default deny policy, and ensures Nginx is running.

Testing your completed web server

Run your updated playbook:

 
ansible-playbook -i inventory.ini webserver.yml

After the playbook completes, open a web browser and navigate to your server's IP address. You should see your custom homepage with the server's information.

Screenshot From 2025-03-17 05-39-31.png

If everything works correctly, you've successfully used Ansible to install necessary packages, configure a web server, deploy custom content, and set up security rules.

Troubleshooting common issues

Even with a tool as straightforward as Ansible, you might encounter some issues. Here are some common problems and how to solve them:

Connection problems

If Ansible can't connect to your servers, check SSH connectivity by making sure you can manually SSH into the target servers. Verify hostnames and IP addresses in your inventory file, and ensure your SSH key or password authentication is working.

For more detailed connection debugging, add the -vvv flag:

 
ansible webservers -i inventory.ini -m ping -vvv

Permission errors

If tasks fail due to permission issues, check that the become: yes directive is included in your playbook. Ensure your user has sudo privileges on the target system. For specific files, check ownership and permissions.

Syntax mistakes

YAML is sensitive to indentation. If you get syntax errors, use a YAML linter or editor with YAML support. Check your indentation (spaces, not tabs) and verify that all colons, dashes, and quotes are correctly placed.

You can validate your playbook syntax without running it:

 
ansible-playbook -i inventory.ini webserver.yml --syntax-check

Final thoughts

Ansible makes automation accessible even to beginners. Through this guide, you've learned how to install and configure Ansible, create an inventory of managed systems, run one-off commands across multiple servers, write playbooks for repeatable configurations, and deploy a working web server with custom content and security settings.

The skills you've gained provide a foundation for more complex automation tasks. To further develop your Ansible skills, explore Ansible's official documentation and experiment with different modules from the module library.

Think about repetitive tasks in your workflow that could be automated. You might want to deploy your favorite application stack, set up regular backups, configure monitoring tools, or standardize security settings across servers.

By investing time in learning Ansible, you're taking a significant step toward more efficient and consistent system administration. Happy automating!

Author's avatar
Article by
Ayooluwa Isaiah
Ayo is a technical content manager at Better Stack. His passion is simplifying and communicating complex technical ideas effectively. His work was featured on several esteemed publications including LWN.net, Digital Ocean, and CSS-Tricks. When he's not writing or coding, he loves to travel, bike, and play tennis.
Got an article suggestion? Let us know
Next article
Mastering Ansible Inventory Files: A Beginner's Guide
Learn how to create, organize and optimize Ansible inventory files for effective infrastructure automation. Master host grouping, variables, and hierarchy for precise control
Licensed under CC-BY-NC-SA

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Make your mark

Join the writer's program

Are you a developer and love writing and sharing your knowledge with the world? Join our guest writing program and get paid for writing amazing technical guides. We'll get them to the right readers that will appreciate them.

Write for us
Writer of the month
Marin Bezhanov
Marin is a software engineer and architect with a broad range of experience working...
Build on top of Better Stack

Write a script, app or project on top of Better Stack and share it with the world. Make a public repository and share it with us at our email.

community@betterstack.com

or submit a pull request and help us build better products for everyone.

See the full list of amazing projects on github