Manually configuring servers one at a time works fine when you have one or two boxes. It stops working the moment you're managing five, ten, or fifty. Ansible is an open-source automation tool that solves this by letting you describe a server's desired configuration — packages, users, firewall rules, services — in a simple text file, then apply that configuration to any number of servers consistently, repeatably, and without logging into each one by hand.
This tutorial covers setting up Ansible for automated bare-metal server provisioning: installing the control tooling, defining your server inventory, writing your first playbooks, and structuring things so the setup scales cleanly as your dedicated server fleet grows.
Why Use Ansible for Bare-Metal Provisioning?
Unlike some automation tools, Ansible does not require installing an agent on every server it manages. It connects over standard SSH and runs tasks defined in playbooks — human-readable YAML files. This makes it a natural fit for bare-metal environments where you want infrastructure-as-code without adding extra software footprint to production servers.
The main benefits for dedicated server fleets:
Consistency – every server ends up in the exact same state, removing "it works on this one but not that one" problems.
Speed – provisioning a new server becomes a matter of running one command instead of an hour of manual setup.
Documentation as code – your playbook is the documentation of how a server is configured.
Repeatability – re-running a playbook is safe; Ansible only changes what needs changing.
From experience automating provisioning across mixed server fleets, the biggest practical win isn't raw speed — it's eliminating configuration drift. When every dedicated server, database node, and application host is built from the same version-controlled automation script, you stop debugging "why is this one server different" problems entirely.
This approach scales particularly well across multiple dedicated server deployments, where you may be provisioning identical web, database, or application server roles across several physical machines. eServers UK's bare metal servers ship with full root access on Intel, AMD, or Ampere hardware, which matters here — Ansible needs unrestricted root-level SSH access to apply system-level changes like firewall rules and package installs. It also pairs naturally with a fleet spread across regions — for example, servers in London and Manchester can be provisioned from a single playbook run rather than configured separately by hand.
Ansible vs. Manual Provisioning: Why It's Worth the Setup Time
Writing your first playbook takes longer than just SSHing in and running a few commands. The payoff shows up the second, third, and fiftieth time you provision a server:
| Manual Provisioning | Ansible Automation | |
|---|---|---|
| Consistency across servers | Depends on memory/notes | Guaranteed by the playbook |
| Time to provision a new server | 30–60+ minutes | A few minutes per run |
| Documentation | Often outdated or missing | The playbook is the documentation |
| Re-running safely | Risk of duplicate/conflicting changes | Idempotent — safe to re-run anytime |
| Onboarding new team members | Tribal knowledge | Readable YAML anyone can review |
For a single test server, manual setup is fine. For a fleet of dedicated servers running production workloads, this gap compounds quickly.
Prerequisites
A control node — your local machine or a separate management server where Ansible will be installed.
One or more target servers (the bare-metal servers you want to provision), reachable over SSH.
SSH key-based access already set up between the control node and target servers.
Basic familiarity with YAML syntax.
Step 1: Install Ansible on the Control Node
Ansible only needs to be installed on the control node, not on the servers it manages.
On Ubuntu or Debian:
sudo apt update
sudo apt install software-properties-common -y
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible -y
On AlmaLinux or Rocky Linux:
sudo dnf install epel-release -y
sudo dnf install ansible -y
Confirm the installation:
ansible --version
Step 2: Set Up SSH Key Access to Your Target Servers
Ansible connects to remote servers over SSH, so passwordless key-based authentication needs to be in place first.
Generate a key pair on the control node if you don't already have one:
ssh-keygen -t ed25519 -C "ansible-control"
Copy the public key to each target server:
ssh-copy-id root@your_server_ip
Repeat this for every server you plan to manage with Ansible.
Step 3: Create Your Inventory File
The inventory is where you list the servers Ansible will manage, grouped by role. Create a project directory and an inventory file:
mkdir ~/ansible-provisioning && cd ~/ansible-provisioning
nano inventory.ini
Add your servers, grouped logically:
[webservers]
web1 ansible_host=203.0.113.10
web2 ansible_host=203.0.113.11
[dbservers]
db1 ansible_host=203.0.113.20
[all:vars]
ansible_user=root
ansible_ssh_private_key_file=~/.ssh/id_ed25519
Test connectivity to all servers in the inventory:
ansible all -i inventory.ini -m ping
A successful response returns "pong" from each server.
Step 4: Write a Basic Provisioning Playbook
A playbook defines the tasks Ansible should run. Create a file for baseline server hardening and setup:
nano baseline.yml
---
- name: Baseline provisioning for new dedicated servers
hosts: all
become: true
tasks:
- name: Update all packages
apt:
update_cache: yes
upgrade: dist
when: ansible_facts['os_family'] == "Debian"
- name: Create a non-root sudo user
user:
name: deployuser
groups: sudo
shell: /bin/bash
create_home: yes
- name: Set up SSH key for the new user
authorized_key:
user: deployuser
state: present
key: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
- name: Install UFW firewall
apt:
name: ufw
state: present
when: ansible_facts['os_family'] == "Debian"
- name: Allow SSH through the firewall
ufw:
rule: allow
name: OpenSSH
- name: Enable the firewall
ufw:
state: enabled
policy: deny
This playbook updates packages, creates a dedicated non-root user, sets up SSH key access for that user, and enables a firewall — the same baseline hardening steps you'd otherwise do manually on every new server.
Step 5: Run the Playbook
Apply the playbook to every server in your inventory:
ansible-playbook -i inventory.ini baseline.yml
Ansible connects to each target, checks the current state against what the playbook describes, and only makes the changes needed to bring it into compliance. Running the same playbook again later will report "ok" for tasks that are already satisfied, rather than repeating them.
Step 6: Provision Role-Specific Software
Once baseline hardening is handled, you can create additional playbooks for specific server roles. For example, a web server playbook:
nano webserver.yml
---
- name: Provision web servers
hosts: webservers
become: true
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
- name: Ensure Nginx is running and enabled
service:
name: nginx
state: started
enabled: true
- name: Open HTTP and HTTPS ports
ufw:
rule: allow
port: "{{ item }}"
loop:
- "80"
- "443"
Run it against just the webservers group defined in your inventory:
ansible-playbook -i inventory.ini webserver.yml
Step 7: Organise Playbooks with Roles (For Larger Deployments)
As your infrastructure grows, a single large playbook becomes harder to maintain. Ansible roles let you break configuration into reusable, organised components:
ansible-galaxy init roles/webserver
This generates a standard directory structure (tasks/, handlers/, templates/, vars/, defaults/) that keeps large provisioning setups organised and easier to reuse across projects.
Step 8: Keep Secrets Out of Plain Text
Provisioning playbooks often need sensitive values like database passwords or API keys. Ansible includes Ansible Vault for encrypting these values so they are never stored in plain text:
ansible-vault create secrets.yml
You'll be prompted for a password to encrypt the file. Reference vault-encrypted variables in your playbooks as normal, and provide the vault password at runtime:
ansible-playbook -i inventory.ini baseline.yml --ask-vault-pass
Putting It Together
With this setup, provisioning a brand-new dedicated server becomes:
Add the server's IP to your inventory file
Confirm SSH connectivity with
ansible all -m pingRun your baseline playbook
Run any role-specific playbooks (web, database, application)
What used to take an hour of manual SSH sessions now takes a few minutes, with a written, version-controlled record of exactly how each server was configured and provisioned.
Conclusion
Ansible turns bare-metal server provisioning from a manual, error-prone process into a repeatable, version-controlled workflow. Whether you're managing three servers or thirty, defining your configuration as code means every server ends up consistent, every change is documented, and scaling your infrastructure no longer means scaling your manual workload.
If you're building out a fleet of dedicated servers and want hardware that's ready for automated provisioning from day one, get in touch with the eServers UK team. Our engineers can help you plan server specs and network setup across locations to fit your automation workflow.
Frequently Asked Questions
Does Ansible require installing software on the servers it manages?
No. Ansible is agentless — it connects over standard SSH and only needs Python present on the target server, which is already included by default on virtually every mainstream Linux distribution.
Is Ansible suitable for a small setup, or only large server fleets?
It scales in both directions. Even with two or three dedicated servers, having your configuration as a version-controlled playbook rather than tribal knowledge makes rebuilding or replacing a server far less risky.
What's the difference between a playbook and a role?
A playbook is a single YAML file describing tasks to run. A role is a structured, reusable collection of tasks, templates, and variables — useful once your playbooks grow large enough that organising them by server function (web, database, cache) becomes worthwhile.
Can Ansible manage servers running different operating systems in the same playbook?
Yes, using conditionals like when: ansible_facts['os_family'] == 'Debian', as shown in this guide. This lets a single playbook adapt its commands correctly across Ubuntu, Debian, AlmaLinux, and Rocky Linux targets.
Discover eServers Dedicated Server Locations
eServers provides reliable dedicated servers across multiple global regions. Whether you need low latency, regional compliance, or proximity to your audience, our wide geographic coverage ensures the perfect hosting environment for your project.