Ansible for Beginners: Automating Linux Server Setup — LinuxDistroFinder
HomeGuides › Ansible Linux Automation
Beginner

Ansible for Beginners: Automating Linux Server Setup

📖 12 min read🗓 Updated July 2026✍️ LinuxDistroFinder Team

Disclosure: some links on this page are affiliate links. If you sign up through them we may earn a commission at no extra cost to you. It helps keep LinuxDistroFinder free.

You've just spun up three fresh Ubuntu servers. You need to install Nginx, create a deploy user, harden SSH, open the right firewall ports, and deploy your app — on all three, identically. Doing it by hand takes 45 minutes and you'll make at least one mistake. Ansible does it in under two minutes, repeatably, every time.

Ansible is an open-source IT automation tool written in Python. It's agentless — you install nothing on the servers you manage. It communicates over plain SSH, reads human-readable YAML files called playbooks, and applies your desired state to any number of machines. That last part is the key: Ansible is idempotent, meaning running the same playbook twice produces the same result without breaking anything on the second run.

This guide walks you from zero to a working server-setup playbook. We'll cover installation, inventory files, ad-hoc commands, modules, roles, and best practices — with real terminal commands and real YAML throughout.

Core Concepts in Plain English

Before writing a single line of YAML, it helps to understand Ansible's four moving parts:

Concept What it is File / location
Control node The machine you run Ansible from (your laptop or a jump host) Needs Python 3.9+ and Ansible installed
Managed nodes The servers being configured — no Ansible installed here Needs SSH access and Python 3 on the remote
Inventory A list of managed nodes, optionally grouped inventory.ini or inventory/ directory
Playbook A YAML file describing what to do on which hosts site.yml, setup.yml, etc.
Module A built-in unit of work (install package, create user, copy file…) Called inside tasks in a playbook
Role A reusable, self-contained bundle of tasks, templates, and variables roles/rolename/ directory
Note: Ansible runs tasks sequentially by default — top to bottom in a playbook, one host at a time (or in configurable batches). There's no daemon, no database, no agent. The simplicity is the point.

Installing Ansible on Your Control Node

Ansible only needs to be on the machine you're running commands from. The most reliable way to install it in 2026 is via pipx, which keeps it isolated from your system Python. Alternatively, most distro package managers carry a recent-enough version.

Ubuntu / Debian

# Option A — official PPA (usually newer than universe)
sudo apt update
sudo apt install software-properties-common -y
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible -y

# Confirm version
ansible --version

Fedora / RHEL 9+ / AlmaLinux / Rocky

sudo dnf install ansible -y
ansible --version

Any distro — via pipx (recommended for latest stable)

sudo apt install pipx -y        # or dnf install pipx
pipx install ansible
pipx ensurepath
# Restart your shell, then:
ansible --version

As of mid-2026, the current stable release is Ansible 10.x (which bundles ansible-core 2.17). You want at least ansible-core 2.16 to get full support for the ansible.builtin.dnf5 module on RHEL 10 and AlmaLinux 10.

Tip: Don't install Ansible with pip install ansible directly into your system Python — it can conflict with distro packages. Use pipx or a virtualenv instead.

Setting Up SSH Key Authentication

Ansible uses SSH. Password-based SSH works but is painfully slow at scale — use key-based auth. If you haven't already:

# Generate a key pair on your control node (if you don't have one)
ssh-keygen -t ed25519 -C "ansible-control" -f ~/.ssh/ansible_key

# Copy the public key to each managed node
ssh-copy-id -i ~/.ssh/ansible_key.pub user@192.168.1.101
ssh-copy-id -i ~/.ssh/ansible_key.pub user@192.168.1.102
ssh-copy-id -i ~/.ssh/ansible_key.pub user@192.168.1.103

If you're on a VPS provider like Vultr or Hostinger, you can paste the public key during server creation so it's there from first boot — which means zero manual SSH setup before running Ansible.

Your First Inventory File

An inventory file tells Ansible which hosts exist and how to reach them. The simplest format is INI:

# inventory.ini
[webservers]
web1 ansible_host=192.168.1.101
web2 ansible_host=192.168.1.102

[dbservers]
db1 ansible_host=192.168.1.103

[all:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/ansible_key
ansible_python_interpreter=/usr/bin/python3

Groups like [webservers] let you target subsets of your fleet. [all:vars] sets variables that apply to every host. You can also write inventories in YAML — useful when you have dozens of groups — but INI is easier to start with.

Test connectivity with a ping

# -i specifies the inventory file, 'all' targets every host
ansible -i inventory.ini all -m ping

You should see pong back from each host. If you see UNREACHABLE, double-check your SSH key path and that the user exists on the remote.

Ad-hoc Commands — Ansible Without a Playbook

Ad-hoc commands are one-liners for quick tasks. They use the -m flag to call a module and -a for arguments. They're great for exploring or doing emergency one-offs:

# Check uptime on all webservers
ansible -i inventory.ini webservers -m command -a "uptime"

# Install htop on all hosts (become = sudo)
ansible -i inventory.ini all -m ansible.builtin.apt -a "name=htop state=present" --become

# Restart nginx on webservers
ansible -i inventory.ini webservers -m ansible.builtin.service -a "name=nginx state=restarted" --become

# Copy a file to all hosts
ansible -i inventory.ini all -m ansible.builtin.copy -a "src=./motd.txt dest=/etc/motd" --become
Note: Always use the fully-qualified module name (e.g. ansible.builtin.apt) in production playbooks. It makes the collection source unambiguous and avoids surprises when community collections are also installed.

Writing Your First Playbook

A playbook is a YAML file with one or more plays. Each play targets a group of hosts and lists tasks to run on them. Here's a complete playbook that sets up a production-ready Nginx web server from a blank Ubuntu 24.04 VPS:

# setup-webserver.yml
---
- name: Configure web servers
  hosts: webservers
  become: true          # run tasks as root (via sudo)
  vars:
    app_user: deploy
    nginx_port: 80

  tasks:

    - name: Update apt cache
      ansible.builtin.apt:
        update_cache: true
        cache_valid_time: 3600   # only update if cache is older than 1 hour

    - name: Install required packages
      ansible.builtin.apt:
        name:
          - nginx
          - ufw
          - fail2ban
          - curl
          - git
        state: present

    - name: Create deploy user
      ansible.builtin.user:
        name: "{{ app_user }}"
        shell: /bin/bash
        create_home: true
        groups: www-data
        append: true

    - name: Allow SSH through UFW
      community.general.ufw:
        rule: allow
        name: OpenSSH

    - name: Allow HTTP through UFW
      community.general.ufw:
        rule: allow
        port: "{{ nginx_port }}"
        proto: tcp

    - name: Enable UFW
      community.general.ufw:
        state: enabled
        policy: deny

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

    - name: Ensure fail2ban is started and enabled
      ansible.builtin.service:
        name: fail2ban
        state: started
        enabled: true

    - name: Harden SSH — disable password auth
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?PasswordAuthentication'
        line: 'PasswordAuthentication no'
        backup: true
      notify: Restart sshd

  handlers:
    - name: Restart sshd
      ansible.builtin.service:
        name: ssh
        state: restarted

Run it with:

ansible-playbook -i inventory.ini setup-webserver.yml

Ansible prints a coloured summary — green for ok, yellow for changed, red for failed. A task that is already in the desired state shows ok and makes no change. That's idempotency in action.

What each section means

  • vars: — define reusable variables; reference them as {{ variable_name }}
  • tasks: — the ordered list of work to do
  • notify / handlers: — a handler runs once at the end of the play only if its task triggered a change; perfect for service restarts
  • become: true — escalates to root via sudo; can be set per-task or per-play

The Most Useful Built-in Modules

Ansible ships with hundreds of modules. These ten cover 90% of server setup tasks:

Module What it does Key args
ansible.builtin.apt Manage packages on Debian/Ubuntu name, state, update_cache
ansible.builtin.dnf Manage packages on Fedora/RHEL/Rocky name, state
ansible.builtin.copy Copy a file from control node to remote src, dest, mode, owner
ansible.builtin.template Copy a Jinja2 template and render variables src, dest
ansible.builtin.lineinfile Ensure a specific line exists in a file path, regexp, line
ansible.builtin.user Manage user accounts name, shell, groups, state
ansible.builtin.service Start/stop/enable systemd services name, state, enabled
ansible.builtin.git Clone or update a git repository repo, dest, version
ansible.builtin.file Manage files, directories, symlinks, permissions path, state, mode, owner
ansible.builtin.command Run a raw command (use sparingly — not idempotent) cmd, creates, removes
Tip: Prefer ansible.builtin.shell over command only when you need pipes or shell expansion. Both are "last resort" modules — if a dedicated module exists for the task, use it. Purpose-built modules are idempotent; raw commands rarely are.

Organising with Roles

Once your playbook grows past ~50 tasks, split it into roles. A role is a directory with a standard layout that Ansible recognises automatically:

# Create a role scaffold with ansible-galaxy
ansible-galaxy init roles/nginx
ansible-galaxy init roles/common
ansible-galaxy init roles/deploy_user

This creates a directory tree like:

roles/
  nginx/
    tasks/
      main.yml        # task list
    handlers/
      main.yml        # handlers
    templates/
      nginx.conf.j2   # Jinja2 templates
    files/            # static files to copy
    vars/
      main.yml        # role-scoped variables
    defaults/
      main.yml        # default variable values (lowest precedence)
    meta/
      main.yml        # role metadata and dependencies

Then your top-level playbook becomes beautifully concise:

# site.yml
---
- name: Configure all web servers
  hosts: webservers
  become: true
  roles:
    - common
    - deploy_user
    - nginx

- name: Configure database servers
  hosts: dbservers
  become: true
  roles:
    - common
    - postgres

Variables and Vault — Keeping Secrets Safe

Never hardcode passwords or API keys in playbooks. Use ansible-vault to encrypt sensitive variable files:

# Create an encrypted vars file
ansible-vault create group_vars/all/vault.yml

# Edit it later
ansible-vault edit group_vars/all/vault.yml

# Run a playbook that uses vault-encrypted vars
ansible-playbook -i inventory.ini site.yml --ask-vault-pass

# Or store the password in a file (don't commit this file!)
echo "mysecretpassword" > ~/.vault_pass
chmod 600 ~/.vault_pass
ansible-playbook -i inventory.ini site.yml --vault-password-file ~/.vault_pass

Inside your vault file, store secrets like database passwords or API tokens. Reference them in playbooks as normal variables — Ansible decrypts them at runtime.

Note: Add .vault_pass and any unencrypted secret files to your .gitignore immediately. Vault-encrypted files are safe to commit — the raw password file is not.

Common Patterns and Best Practices

01 Always run with --check first Safety

Dry-run mode (--check) shows what would change without making any changes. Add --diff to also see line-by-line diffs for file tasks. Run this before every production deployment.

02 Use group_vars and host_vars directories Organisation

Ansible automatically loads group_vars/webservers.yml for all hosts in the webservers group, and host_vars/web1.yml for the host named web1. This keeps your inventory clean and your variables contextual.

03 Tag your tasks Efficiency

Add tags: [nginx, packages] to tasks, then run only tagged tasks with --tags nginx. This lets you re-run just the nginx configuration without touching unrelated tasks — critical for fast iteration.

04 Pin your collections Reproducibility

Create a requirements.yml and install collections with ansible-galaxy collection install -r requirements.yml. Specify exact versions to avoid breakage when community collections release updates.

05 Lint before you commit Quality

Install ansible-lint via pipx and run it on your playbooks. It catches common mistakes like missing name: fields, deprecated module names, and tasks that aren't idempotent. Add it to your CI pipeline.

# Install ansible-lint
pipx install ansible-lint

# Lint a playbook
ansible-lint setup-webserver.yml

# Dry-run with diff before deploying
ansible-playbook -i inventory.ini site.yml --check --diff

Need Servers to Practice On?

🚀 Get Cloud VPS Instances to Practice Ansible On

The best way to learn Ansible is against real servers — not VMs on your laptop. Spin up 2–3 cheap VPS instances, practice provisioning them end-to-end, then destroy them. Vultr's $2.50/month instances are perfect for this.

Get $100 Free Credit on Vultr →

Also great: Hostinger VPS — KVM-based, NVMe storage, starts at ₹299/mo. Both work perfectly as Ansible managed nodes.

Frequently Asked Questions

Do I need to install anything on the servers I'm managing?

Almost nothing. Your managed nodes need SSH access enabled and Python 3 installed — both are present by default on Ubuntu 20.04+, Debian 11+, AlmaLinux 8+, and most modern server distros. Ansible itself only lives on the control node. This is what "agentless" means and it's a major advantage over tools like Puppet or Chef.

What's the difference between Ansible and Terraform?

Terraform is an infrastructure provisioning tool — it creates and destroys cloud resources (VMs, networks, DNS records, load balancers). Ansible is a configuration management tool — it configures software on machines that already exist. In practice, they complement each other: Terraform creates your servers, Ansible configures them. You can also use Ansible's cloud modules to provision infrastructure, but Terraform's state management is generally better for that job.

How is Ansible different from a shell script?

Shell scripts are imperative — you write every step explicitly and they're not idempotent (running adduser deploy twice errors on the second run). Ansible is declarative — you describe the desired state and Ansible figures out whether any change is needed. Ansible also handles error reporting, parallel execution, secrets management, and templating in ways that shell scripts require significant extra code to replicate.

What is ansible-core vs ansible (the full package)?

ansible-core is the minimal engine — the CLI tools, a small set of built-in modules, and the runtime. The ansible package bundles ansible-core plus a curated set of community collections (hundreds of extra modules for AWS, GCP, Docker, Kubernetes, networking gear, etc.). For most beginners, install ansible to get the collections pre-loaded. For a lean CI environment, install just ansible-core and add specific collections via requirements.yml.

Can I use Ansible with Windows servers?

Yes, but instead of SSH, Ansible uses WinRM (Windows Remote Management) or, from Ansible 2.18+, experimental SSH support on Windows. You need to enable WinRM on the target Windows machines and use the ansible.windows collection. It works but is noticeably more complex to set up than Linux automation. Linux is Ansible's native environment.

How do I handle different Linux distros in one playbook?

Use the ansible_os_family or ansible_distribution facts with a when: condition, or create separate task files and include them conditionally. A common pattern is to have tasks/install-debian.yml and tasks/install-redhat.yml and include the right one based on ansible_os_family == "Debian". Ansible's package module also abstracts over apt/dnf/yum for basic installs.

Is there a GUI for Ansible?

The official GUI is AWX (the open-source upstream of Red Hat Ansible Automation Platform). AWX provides a web interface for running playbooks, managing inventory, scheduling jobs, and controlling access. It runs as a containerised application. For small teams or personal use, most people stick with the CLI — it's faster and easier to script into CI/CD pipelines.

🐧
Chippy
Your Linux distro assistant