Install and configure Ansible with automation best practices and security

Intermediate 45 min Apr 01, 2026 56 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', 'webserver']
  • name: Configure database servers
hosts: databases become: yes vars: postgresql_version: 15 tasks: - name: Placeholder for database configuration debug: msg: "Database configuration would go here" tags: ['database']

Set up Ansible collections

Install useful collections from Ansible Galaxy for extended functionality and cloud integrations.

ansible-galaxy collection install community.general
ansible-galaxy collection install ansible.posix
ansible-galaxy collection install community.crypto
ansible-galaxy collection list

Create requirements file

Document all required roles and collections for consistent deployment across environments.

---
collections:
  - name: community.general
    version: ">=5.0.0"
  - name: ansible.posix
    version: ">=1.4.0"
  - name: community.crypto
    version: ">=2.0.0"
    
roles:
  - name: geerlingguy.security
    src: https://github.com/geerlingguy/ansible-role-security
  - name: nginxinc.nginx
    src: https://github.com/nginxinc/ansible-role-nginx

Verify your setup

Test your complete Ansible configuration to ensure everything works correctly.

ansible --version
ansible all -m ping
ansible-playbook site.yml --check --diff
ansible-inventory --list
ansible-vault --help
ansible-doc -l | grep -i user

Common issues

Symptom Cause Fix
SSH connection refused Wrong SSH key or user Verify key with ssh -i ~/.ssh/ansible_ed25519 ansible@host
Permission denied (sudo) User lacks sudo privileges Add user to sudoers: echo "ansible ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/ansible
Vault decryption failed Wrong vault password Check password file permissions: chmod 600 ~/.ansible-vault-pass
Host key verification failed SSH host key changed Remove old key: ssh-keygen -R hostname
Playbook syntax error YAML indentation issues Validate syntax: ansible-playbook playbook.yml --syntax-check
Module not found Missing collection Install collection: ansible-galaxy collection install collection.name

Next steps

Automated install script

Run this to automate the entire setup

#ansible #ansible-playbook #configuration-management #ansible-vault #infrastructure-as-code

Need help?

Don't want to manage this yourself?

We handle infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.

Talk to an engineer