Install and configure Ansible with automation best practices and security

Intermediate 45 min Apr 01, 2026 1,218 views
Ubuntu 24.04 Ubuntu 22.04 Debian 12 AlmaLinux 9 Rocky Linux 9 Fedora 41

Learn to install Ansible on Linux, configure secure SSH authentication, create production-ready playbooks with Vault encryption, and implement roles for scalable infrastructure automation.

Prerequisites

  • Multiple Linux servers with SSH access
  • Basic understanding of YAML syntax
  • Root or sudo access on managed hosts

What this solves

Ansible automates server configuration, application deployment, and infrastructure management without requiring agents on target systems. This tutorial shows you how to set up a secure Ansible control node with SSH key authentication, encrypted secrets management using Ansible Vault, and reusable roles for consistent automation across your infrastructure.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you get the latest versions and security patches.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install Python and pip dependencies

Ansible requires Python 3.8+ and pip for package management. Install the required dependencies for your distribution.

sudo apt install -y python3 python3-pip python3-venv software-properties-common
sudo dnf install -y python3 python3-pip python3-virtualenv

Install Ansible using pip

Install Ansible using pip to get the latest stable version with all features. This method works consistently across all distributions.

python3 -m pip install --user ansible ansible-core
echo 'export PATH=$PATH:$HOME/.local/bin' >> ~/.bashrc
source ~/.bashrc

Verify Ansible installation

Check that Ansible is installed correctly and view the version information.

ansible --version
ansible-playbook --version

Generate SSH key for authentication

Create an SSH key pair for secure, passwordless authentication to managed hosts. Use ED25519 for better security and performance.

ssh-keygen -t ed25519 -C "ansible-control-$(whoami)@$(hostname)" -f ~/.ssh/ansible_ed25519
chmod 600 ~/.ssh/ansible_ed25519
chmod 644 ~/.ssh/ansible_ed25519.pub

Create Ansible directory structure

Set up a proper directory structure following Ansible best practices for organizing playbooks, roles, and inventory files.

mkdir -p ~/ansible/{playbooks,roles,inventory,group_vars,host_vars}
cd ~/ansible

Configure Ansible settings

Create a configuration file to set secure defaults and optimize Ansible behavior for your environment.

[defaults]
inventory = ./inventory/hosts.yml
remote_user = ansible
private_key_file = ~/.ssh/ansible_ed25519
host_key_checking = False
timeout = 30
forks = 10
log_path = ./ansible.log

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null
pipelining = True

Create inventory file

Define your managed hosts using YAML inventory format with groups for better organization and variable management.

all:
  children:
    webservers:
      hosts:
        web01:
          ansible_host: 203.0.113.10
        web02:
          ansible_host: 203.0.113.11
      vars:
        ansible_user: ansible
        http_port: 80
    databases:
      hosts:
        db01:
          ansible_host: 203.0.113.20
      vars:
        ansible_user: ansible
        db_port: 5432

Set up managed host user

Create a dedicated ansible user on each managed host with sudo privileges. Run these commands on each target server.

sudo useradd -m -s /bin/bash ansible
sudo mkdir /home/ansible/.ssh
sudo chmod 700 /home/ansible/.ssh
echo "ansible ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/ansible

Copy SSH key to managed hosts

Deploy your public key to each managed host for passwordless authentication. Replace the IP addresses with your actual server IPs.

ssh-copy-id -i ~/.ssh/ansible_ed25519.pub ansible@203.0.113.10
ssh-copy-id -i ~/.ssh/ansible_ed25519.pub ansible@203.0.113.11
ssh-copy-id -i ~/.ssh/ansible_ed25519.pub ansible@203.0.113.20

Test inventory connectivity

Verify that Ansible can connect to all managed hosts using the ping module.

ansible all -m ping
ansible webservers -m setup --limit web01

Create your first playbook

Create a basic system update playbook

Write a simple playbook that updates packages across all managed systems with proper error handling and notifications.

---
- name: Update system packages
  hosts: all
  become: yes
  vars:
    max_fail_percentage: 10
  
  tasks:
    - name: Update package cache (Ubuntu/Debian)
      apt:
        update_cache: yes
        cache_valid_time: 3600
      when: ansible_os_family == "Debian"
      
    - name: Update package cache (RHEL/CentOS)
      dnf:
        update_cache: yes
      when: ansible_os_family == "RedHat"
      
    - name: Upgrade all packages (Ubuntu/Debian)
      apt:
        upgrade: dist
        autoremove: yes
      when: ansible_os_family == "Debian"
      register: apt_upgrade
      
    - name: Upgrade all packages (RHEL/CentOS)
      dnf:
        name: "*"
        state: latest
      when: ansible_os_family == "RedHat"
      register: dnf_upgrade
      
    - name: Check if reboot required (Ubuntu/Debian)
      stat:
        path: /var/run/reboot-required
      register: reboot_required
      when: ansible_os_family == "Debian"
      
    - name: Notify when reboot needed
      debug:
        msg: "Reboot required on {{ inventory_hostname }}"
      when: reboot_required is defined and reboot_required.stat.exists

Create group variables

Define variables specific to server groups for consistent configuration management.

---
# Web server specific variables
nginx_version: latest
http_port: 80
https_port: 443
max_clients: 200

# Security settings
firewall_allowed_ports:
  - 22
  - 80
  - 443
  
# Backup settings
backup_enabled: true
backup_retention_days: 7

Run your first playbook

Execute the playbook with verbose output to see detailed information about each task execution.

ansible-playbook playbooks/system-update.yml --check --diff
ansible-playbook playbooks/system-update.yml -v

Implement Ansible Vault for secrets

Create encrypted variables file

Use Ansible Vault to securely store sensitive information like passwords, API keys, and certificates.

ansible-vault create group_vars/all/vault.yml

Enter a strong vault password when prompted, then add your secrets:

---
vault_database_password: "SecureDbPass2024!"
vault_api_key: "abc123def456ghi789"
vault_ssl_private_key: |
  -----BEGIN PRIVATE KEY-----
  MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...
  -----END PRIVATE KEY-----

Create vault password file

Store your vault password securely for automated playbook execution. Protect this file with strict permissions.

echo "YourVaultPasswordHere2024" > ~/.ansible-vault-pass
chmod 600 ~/.ansible-vault-pass
Never use chmod 777. It gives every user on the system full access to your vault password. Always use chmod 600 for sensitive files like passwords and private keys.

Update Ansible configuration for vault

Configure Ansible to automatically use your vault password file for decryption.

[defaults]
inventory = ./inventory/hosts.yml
remote_user = ansible
private_key_file = ~/.ssh/ansible_ed25519
host_key_checking = False
timeout = 30
forks = 10
log_path = ./ansible.log
vault_password_file = ~/.ansible-vault-pass

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null
pipelining = True

Create playbook using vault variables

Demonstrate using encrypted variables in a playbook for secure database configuration.

---
- name: Deploy application with secure credentials
  hosts: webservers
  become: yes
  vars:
    app_database_password: "{{ vault_database_password }}"
    app_api_key: "{{ vault_api_key }}"
    
  tasks:
    - name: Create application config directory
      file:
        path: /etc/myapp
        state: directory
        owner: root
        group: root
        mode: '0755'
        
    - name: Deploy application configuration
      template:
        src: app.conf.j2
        dest: /etc/myapp/app.conf
        owner: root
        group: myapp
        mode: '0640'
      notify: restart myapp
      
  handlers:
    - name: restart myapp
      systemd:
        name: myapp
        state: restarted

Create reusable roles

Generate role structure

Create a standardized role structure for installing and configuring NGINX with security best practices.

cd ~/ansible
ansible-galaxy init roles/nginx

Define role tasks

Create the main tasks for the NGINX role with proper error handling and idempotency.

---
- name: Install NGINX (Ubuntu/Debian)
  apt:
    name: nginx
    state: "{{ nginx_version | default('present') }}"
    update_cache: yes
  when: ansible_os_family == "Debian"
  notify: restart nginx
  
- name: Install NGINX (RHEL/CentOS)
  dnf:
    name: nginx
    state: "{{ nginx_version | default('present') }}"
  when: ansible_os_family == "RedHat"
  notify: restart nginx
  
- name: Create nginx directories
  file:
    path: "{{ item }}"
    state: directory
    owner: root
    group: root
    mode: '0755'
  loop:
    - /etc/nginx/sites-available
    - /etc/nginx/sites-enabled
    - /var/log/nginx
    - /var/www/html
    
- name: Deploy NGINX main configuration
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'
    backup: yes
  notify: restart nginx
  
- name: Deploy default site configuration
  template:
    src: default.conf.j2
    dest: /etc/nginx/sites-available/default
    owner: root
    group: root
    mode: '0644'
  notify: restart nginx
  
- name: Enable default site
  file:
    src: /etc/nginx/sites-available/default
    dest: /etc/nginx/sites-enabled/default
    state: link
  notify: restart nginx
  
- name: Start and enable NGINX
  systemd:
    name: nginx
    state: started
    enabled: yes
    daemon_reload: yes

Create role handlers

Define handlers for service management and configuration validation.

---
- name: restart nginx
  systemd:
    name: nginx
    state: restarted
  listen: restart nginx
  
- name: reload nginx
  systemd:
    name: nginx
    state: reloaded
  listen: reload nginx

Define role variables

Set default variables that can be overridden by users of the role.

---
# NGINX version
nginx_version: present

# Worker processes
nginx_worker_processes: auto
nginx_worker_connections: 1024

# Security headers
nginx_security_headers:
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff
  X-XSS-Protection: "1; mode=block"
  Referrer-Policy: strict-origin-when-cross-origin
  
# Rate limiting
nginx_rate_limit: "10r/s"

# SSL settings
nginx_ssl_protocols: "TLSv1.2 TLSv1.3"
nginx_ssl_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"

Create role templates

Create a Jinja2 template for the NGINX configuration with security hardening.

user www-data;
worker_processes {{ nginx_worker_processes }};
pid /run/nginx.pid;

events {
    worker_connections {{ nginx_worker_connections }};
    use epoll;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    # Security headers
{% for header, value in nginx_security_headers.items() %}
    add_header {{ header }} "{{ value }}" always;
{% endfor %}
    
    # Hide server version
    server_tokens off;
    
    # Rate limiting
    limit_req_zone $binary_remote_addr zone=global:10m rate={{ nginx_rate_limit }};
    
    # Logging
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
                    
    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log;
    
    # Performance
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
    
    include /etc/nginx/sites-enabled/*;
}

Create playbook using the role

Create a playbook that uses your custom role to deploy NGINX across web servers.

---
- name: Configure web servers
  hosts: webservers
  become: yes
  vars:
    nginx_worker_processes: 2
    nginx_rate_limit: "20r/s"
    
  roles:
    - role: nginx
      tags: ['nginx', 'webserver']
      
  post_tasks:
    - name: Ensure firewall allows HTTP traffic
      ufw:
        rule: allow
        port: "{{ item }}"
        proto: tcp
      loop:
        - '80'
        - '443'
      when: ansible_os_family == "Debian"
      
    - name: Open firewall ports (RHEL/CentOS)
      firewalld:
        port: "{{ item }}/tcp"
        permanent: yes
        state: enabled
        immediate: yes
      loop:
        - '80'
        - '443'
      when: ansible_os_family == "RedHat"

Run playbook with roles

Execute the playbook to deploy your role across the web servers with specific tags for selective execution.

ansible-playbook playbooks/webserver-setup.yml --check
ansible-playbook playbooks/webserver-setup.yml --tags nginx
ansible-playbook playbooks/webserver-setup.yml

Security hardening and best practices

Create security hardening role

Implement a comprehensive security role that can be applied to all managed hosts.

ansible-galaxy init roles/security-hardening

Implement security tasks

Create security hardening tasks including user management, SSH configuration, and system updates. You can integrate this with Fail2ban for additional protection.

---
- name: Update all packages to latest version
  package:
    name: "*"
    state: latest
  when: security_update_packages | default(true)
  
- name: Remove unnecessary packages
  package:
    name: "{{ security_packages_remove | default([]) }}"
    state: absent
    
- name: Install security packages
  package:
    name: "{{ security_packages_install }}"
    state: present
    
- name: Secure SSH configuration
  lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: "{{ item.regexp }}"
    line: "{{ item.line }}"
    backup: yes
  loop:
    - { regexp: '^#?PermitRootLogin', line: 'PermitRootLogin no' }
    - { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication no' }
    - { regexp: '^#?X11Forwarding', line: 'X11Forwarding no' }
    - { regexp: '^#?MaxAuthTries', line: 'MaxAuthTries 3' }
    - { regexp: '^#?ClientAliveInterval', line: 'ClientAliveInterval 300' }
    - { regexp: '^#?ClientAliveCountMax', line: 'ClientAliveCountMax 2' }
  notify: restart sshd
  
- name: Set proper permissions on sensitive files
  file:
    path: "{{ item.path }}"
    mode: "{{ item.mode }}"
    owner: "{{ item.owner | default('root') }}"
    group: "{{ item.group | default('root') }}"
  loop:
    - { path: '/etc/ssh/sshd_config', mode: '0644' }
    - { path: '/etc/passwd', mode: '0644' }
    - { path: '/etc/shadow', mode: '0640', group: 'shadow' }
    - { path: '/etc/gshadow', mode: '0640', group: 'shadow' }
    
- name: Configure automatic security updates
  template:
    src: 20auto-upgrades.j2
    dest: /etc/apt/apt.conf.d/20auto-upgrades
    mode: '0644'
  when: ansible_os_family == "Debian" and security_auto_updates | default(true)
  
- name: Enable and configure auditd
  systemd:
    name: auditd
    enabled: yes
    state: started
  when: security_enable_audit | default(true)

Create comprehensive site playbook

Combine multiple roles into a site-wide playbook for complete infrastructure deployment.

---
- name: Apply security hardening to all hosts
  hosts: all
  become: yes
  roles:
    - role: security-hardening
      tags: ['security', 'hardening']
      
- name: Configure web servers
  hosts: webservers
  become: yes
  roles:
    - role: nginx
      tags: ['nginx',

Automated install script

Run this to automate the entire setup

इसे खुद मैनेज नहीं करना चाहते?

हम उन businesses के लिए infrastructure संभालते हैं जो uptime पर निर्भर हैं। Fully managed, एक fixed contact के साथ जो आपके setup को जानता है।

आपको एक निश्चित contact मिलता है जो आपके setup को जानता है

Rotterdam 08:59 · एक message में पहुंचें, कोई ticket form नहीं