Ansible is an agentless configuration management tool that runs over SSH. Installing the same packages on 100 servers, distributing config files, starting services — all with a single command. It needs Python, but that's already installed on target servers.

Installation

Related guides: What is DNS, settings · Domain names & WHOIS lookup · Hosting types guide · Nginx configuration · Plesk panel guide

# Ubuntu/Debian
sudo apt install -y ansible

# Or with pip
pipx install ansible
ansible --version

# Having an SSH key on target servers is all you need
ssh-copy-id root@web-1.example.com

Inventory

# inventory.ini
[web]
web-1.example.com
web-2.example.com
web-3.example.com ansible_host=1.2.3.4

[db]
db-1.example.com
db-2.example.com

[production:children]
web
db

[web:vars]
ansible_user=deploy
ansible_ssh_private_key_file=~/.ssh/deploy_key
# inventory.yml (alternative)
all:
  children:
    web:
      hosts:
        web-1.example.com:
        web-2.example.com:
      vars:
        ansible_user: deploy
    db:
      hosts:
        db-1.example.com:

Ad-Hoc Commands

# Ping all servers
ansible all -i inventory.ini -m ping

# Run a command
ansible web -i inventory.ini -m shell -a 'uptime'

# Install a package
ansible web -i inventory.ini -m apt -a 'name=htop state=present' --become

# Copy a file
ansible web -i inventory.ini -m copy -a 'src=./nginx.conf dest=/etc/nginx/nginx.conf'

Playbook — Basic Example

# site.yml
- name: Harden web servers
  hosts: web
  become: true

  vars:
    ssh_port: 22022

  tasks:
    - name: Apt update
      apt:
        update_cache: yes
        upgrade: safe
        cache_valid_time: 3600

    - name: Required packages
      apt:
        name:
          - fail2ban
          - ufw
          - nginx
          - htop
        state: present

    - name: Change SSH port
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?Port '
        line: 'Port {{ ssh_port }}'
      notify: restart sshd

    - name: UFW rules
      ufw:
        rule: allow
        port: '{{ item }}'
      loop: ['{{ ssh_port }}', '80', '443']

    - name: UFW enable
      ufw:
        state: enabled
        policy: deny

  handlers:
    - name: restart sshd
      service: { name: ssh, state: restarted }
# Run it
ansible-playbook -i inventory.ini site.yml

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

# Single host only
ansible-playbook -i inventory.ini site.yml --limit web-1.example.com

# Resume from a specific task
ansible-playbook -i inventory.ini site.yml --start-at-task='SSH port'
Note
Ansible tasks are idempotent — running the same playbook ten times only changes the system on the first run. That is the key to safety: you can apply the same playbook across dev, staging and prod.

Roles — Modular Structure

# Scaffold a role
ansible-galaxy init roles/nginx

# Structure
roles/nginx/
├── tasks/main.yml        # main tasks
├── handlers/main.yml     # service restarts etc.
├── templates/            # jinja2 templates
├── files/                # static files
├── vars/main.yml         # role variables
└── defaults/main.yml     # overridable defaults
# roles/nginx/tasks/main.yml
- name: Install Nginx
  apt: { name: nginx, state: present }

- name: Config template
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    validate: 'nginx -t -c %s'
  notify: reload nginx

- name: Service enabled
  service:
    name: nginx
    state: started
    enabled: yes

# roles/nginx/handlers/main.yml
- name: reload nginx
  service: { name: nginx, state: reloaded }

# Main playbook
- hosts: web
  become: true
  roles:
    - nginx

Jinja2 Templates

# roles/nginx/templates/nginx.conf.j2
worker_processes {{ ansible_processor_vcpus }};
worker_connections {{ worker_connections | default(1024) }};

{% for server in groups['backend'] %}
upstream backend {
    server {{ hostvars[server].ansible_host }}:3000;
}
{% endfor %}

server {
    listen 443 ssl;
    server_name {{ server_name }};
    ssl_certificate /etc/letsencrypt/live/{{ server_name }}/fullchain.pem;
}

Ansible Vault

# Encrypted secrets file
ansible-vault create secrets.yml
# editor opens, with content like:
#   db_password: supersecret
#   api_key: xyz

ansible-vault edit secrets.yml
ansible-vault encrypt existing.yml
ansible-vault decrypt encrypted.yml

# Use in a playbook
ansible-playbook site.yml --ask-vault-pass
ansible-playbook site.yml --vault-password-file=.vault_pass

Real World: Full Deploy Playbook

- hosts: web
  become: true
  vars_files:
    - secrets.yml

  tasks:
    - name: Create app user
      user:
        name: app
        home: /var/www/app
        shell: /bin/bash

    - name: Clone/update repo
      git:
        repo: 'git@github.com:user/app.git'
        dest: /var/www/app
        version: main
        accept_hostkey: yes
      become_user: app
      notify: restart app

    - name: NPM install
      npm:
        path: /var/www/app
        production: yes
        state: present
      become_user: app

    - name: Env file
      template:
        src: env.j2
        dest: /var/www/app/.env
        owner: app
        mode: '0600'

    - name: Run with PM2
      command: pm2 startOrReload ecosystem.config.js
      args: { chdir: /var/www/app }
      become_user: app

  handlers:
    - name: restart app
      command: pm2 restart ecosystem.config.js
      become_user: app

Modern Web Hosting and Server Infrastructure

A performant web hosting service rests on three infrastructure decisions: NVMe SSD disks (4-6× IOPS over SATA SSD), LiteSpeed Web Server or Nginx + LSCache (9× request capacity over Apache) and CloudLinux + Imunify360 isolation. The hosting provider's control panel (cPanel, Plesk, DirectAdmin), daily backup policy, data center location and support response time make a big difference too. Turkish locations give low latency to local visitors, while Hetzner Frankfurt or OVH Roubaix suit global traffic. As your site grows, transitioning from shared hosting to VPS to dedicated server scales CPU/RAM/disk to your needs.

Conclusion

Ansible is worth its weight in gold for any system with 5+ servers. It runs wherever SSH and Python exist, needs no agent and has a gentle learning curve. Terraform provisions infrastructure; Ansible configures that infrastructure. Together they form the two pillars of modern IaC.

Automate with Ansible

Reach out to KEYDAL for multi-server management, playbook design and migration automation. Contact us

WhatsApp