Ansible
Automate management of multiple VPS with Ansible, installation, inventory, playbooks and practical examples for server configuration
Ansible enables management of dozens of VPS simultaneously using YAML playbooks. It is agentless, using only SSH, with nothing to install on target servers. Ideal for infrastructure automation, configuration management, and deployment workflows.
Installation on Control Machine
Ubuntu / Debian
apt install ansible -y
ansible --versionCentOS / Rocky / AlmaLinux
dnf install ansible -y
ansible --versionVia pip (Latest Version)
pip install ansible --break-system-packages
ansible --versionInventory: Define Your Servers
Create an inventory file (inventory.ini):
[webservers]
web1 ansible_host=192.168.1.10 ansible_user=root
web2 ansible_host=192.168.1.11 ansible_user=ubuntu ansible_become=yes
[dbservers]
db1 ansible_host=192.168.1.20 ansible_user=ubuntu ansible_become=yes
[all:vars]
ansible_ssh_private_key_file=~/.ssh/id_rsa
ansible_python_interpreter=/usr/bin/python3Variables Explained
ansible_host: Server IP or hostnameansible_user: SSH useransible_become=yes: Use sudo for commandsansible_ssh_private_key_file: Path to SSH key
Test Connectivity
Test all servers:
ansible all -i inventory.ini -m pingRun a command on all webservers:
ansible webservers -i inventory.ini -m command -a "uptime"First Playbook: Install Nginx
Create install-nginx.yml:
---
- name: Install and start Nginx
hosts: webservers
become: yes
tasks:
- name: Update apt cache
apt:
update_cache: yes
when: ansible_os_family == "Debian"
- name: Install Nginx
package:
name: nginx
state: present
- name: Start and enable Nginx
service:
name: nginx
state: started
enabled: yes
- name: Check Nginx status
command: systemctl status nginx
register: nginx_status
- name: Show status
debug:
var: nginx_status.stdout_linesRun the playbook:
ansible-playbook -i inventory.ini install-nginx.ymlCommon Ansible Modules
Package Management
- name: Install packages
package:
name: "{{ item }}"
state: present
loop:
- curl
- wget
- git
- htopCopy Files
- name: Copy config file
copy:
src: /local/path/nginx.conf
dest: /etc/nginx/nginx.conf
backup: yesUser Management
- name: Create user
user:
name: deploy
shell: /bin/bash
home: /home/deploy
createhome: yes
groups: sudo
- name: Set SSH key
authorized_key:
user: deploy
state: present
key: "{{ lookup('file', '/local/ssh/id_rsa.pub') }}"Services
- name: Start service
service:
name: nginx
state: started
enabled: yes
- name: Restart on config change
service:
name: nginx
state: restarted
when: config_changedRun Shell Commands
- name: Run custom script
shell: |
#!/bin/bash
echo "Deploying..."
./deploy.sh
args:
executable: /bin/bashTemplates with Jinja2
Create templates/nginx.conf.j2:
server {
listen {{ nginx_port }};
server_name {{ server_hostname }};
location / {
proxy_pass http://{{ backend_server }};
}
}Use in playbook:
- name: Deploy Nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
owner: root
group: root
mode: '0644'
vars:
nginx_port: 80
server_hostname: example.com
backend_server: 127.0.0.1:3000
notify: Restart Nginx
handlers:
- name: Restart Nginx
service:
name: nginx
state: restartedHandlers and Notifications
Handlers run only when notified:
- name: Update Nginx config
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify: Restart Nginx
handlers:
- name: Restart Nginx
service:
name: nginx
state: restartedVariables and Facts
Define Variables
---
- hosts: all
vars:
app_port: 3000
app_user: deploy
tasks:
- name: Create app directory
file:
path: /opt/{{ app_name }}
state: directory
owner: "{{ app_user }}"Register Variables
- name: Check if file exists
stat:
path: /etc/app.conf
register: config_file
- name: Only run if file exists
command: cat /etc/app.conf
when: config_file.stat.existsUse Facts
- name: Display system info
debug:
msg: |
Hostname: {{ ansible_hostname }}
OS: {{ ansible_os_family }}
IP: {{ ansible_default_ipv4.address }}
Memory: {{ ansible_memtotal_mb }} MBAnsible Vault: Encrypt Secrets
Create encrypted file:
ansible-vault create secrets.ymlEdit encrypted file:
ansible-vault edit secrets.ymlEncrypt existing file:
ansible-vault encrypt vars/db_password.ymlRun playbook with vault:
ansible-playbook -i inventory.ini playbook.yml --ask-vault-passOr use vault password file:
ansible-playbook -i inventory.ini playbook.yml --vault-password-file ~/.vault_passAnsible Galaxy: Reusable Roles
Search community roles:
ansible-galaxy search dockerInstall a role:
ansible-galaxy install geerlingguy.dockerUse in playbook:
---
- hosts: all
roles:
- geerlingguy.docker
- geerlingguy.nginxCreate requirements.yml:
---
roles:
- name: geerlingguy.docker
version: 7.0.0
- name: geerlingguy.nginx
version: 4.0.0Install all:
ansible-galaxy install -r requirements.ymlComplete Hardening Playbook Example
---
- name: Base server hardening
hosts: all
become: yes
vars:
ssh_port: 2222
disable_root_login: yes
firewall_allowed_ports:
- 22
- 80
- 443
tasks:
- name: Update all packages
apt:
update_cache: yes
upgrade: dist
when: ansible_os_family == "Debian"
- name: Install security tools
package:
name: "{{ item }}"
state: present
loop:
- fail2ban
- ufw
- curl
- git
- name: Configure SSH security
lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
state: present
loop:
- regexp: "^#?PermitRootLogin"
line: "PermitRootLogin no"
- regexp: "^#?PasswordAuthentication"
line: "PasswordAuthentication no"
- regexp: "^#?PubkeyAuthentication"
line: "PubkeyAuthentication yes"
notify: Restart SSH
- name: Configure UFW firewall
ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop: "{{ firewall_allowed_ports }}"
- name: Enable UFW
ufw:
state: enabled
policy: deny
direction: incoming
- name: Start fail2ban
service:
name: fail2ban
state: started
enabled: yes
- name: Create deploy user
user:
name: deploy
shell: /bin/bash
home: /home/deploy
createhome: yes
groups: sudo
handlers:
- name: Restart SSH
service:
name: sshd
state: restartedRun with check mode (dry-run):
ansible-playbook -i inventory.ini hardening.yml --check --diffApply for real:
ansible-playbook -i inventory.ini hardening.ymlDry Run and Diff
Always test before applying:
# Check mode (no changes)
ansible-playbook playbook.yml --check
# Show what would change
ansible-playbook playbook.yml --diff
# Both
ansible-playbook playbook.yml --check --diffRunning Playbooks Verbosely
# More verbose output
ansible-playbook playbook.yml -v
# Very verbose
ansible-playbook playbook.yml -vv
# Debug everything
ansible-playbook playbook.yml -vvvAnsible is the industry standard for managing fleets of VPS. With playbooks, you can apply security updates, deploy applications, or configure 50 servers in minutes instead of hours.
Always test playbooks with --check and --diff before running in production. A syntax error or logic bug can impact all servers in your inventory. Keep vault password safe and never commit it to version control.