News
Faster Speeds: Bandwidth for vStack Servers in Kazakhstan Increased to 200 Mbps
BS
June 28 2025
Updated June 24 2025

Using Ansible to Automate Server and Database Setup on VPS

Ansible

Manual server setup on VPS is time-consuming, error-prone, and inconsistent. Ansible is an automation tool that lets you define server configurations as code, ensuring idempotency and repeatability. This guide will help you set up a VPS with Nginx, MySQL, and PHP using Ansible, minimizing manual work and improving reliability.

What is Ansible?

Ansible is a simple yet powerful open-source automation tool (acquired by Red Hat). Its key features:

  • Agentless:
    • Requires no special software (agents) on managed nodes (your VPS). Uses existing protocols (SSH for Linux/Unix, WinRM for Windows).
  • Push Model:
    • Management is initiated from the "control node" (Ansible control node), which connects to target nodes via SSH to execute tasks.
  • Readable and Writable:
    • Configurations are written in YAML (a simple markup language). Core concepts:
      • Inventory:
        • File(s) describing your servers (hosts), their IPs/names, and groups (e.g., `webservers`, `dbservers`).
      • Playbooks:
        • YAML files defining *automated processes* (plays) and *tasks* to execute on hosts.
      • Tasks:
        • Specific actions (Ansible modules) performed on hosts (install packages, copy files, restart services).
      • Modules:
        • Pre-built functional "building blocks" (over 3000 built-in!). Examples: `apt`/`yum` (package management), `copy`/`template` (file management), `service` (service management), `mysql_db` (MySQL database management).
      • Roles:
        • A way to structure and reuse sets of tasks, variables, files, and templates for specific purposes (e.g., `nginx` or `postgresql` roles).
      • Templates:
        • Files (typically Jinja2-based) that dynamically generate configurations based on variables (e.g., Nginx config with a domain from a variable).
      • Variables:
        • Used to parameterize playbooks and roles (software versions, paths, passwords, settings).

Advantages of Ansible

  • Low Entry Barrier:
    • Easier to start compared to Chef/Puppet due to YAML and no agents.
  • Idempotency:
    • Built into most modules, making repeated playbook runs safe.
  • Large Community and Ecosystem:
    • Rich documentation, thousands of modules, and pre-built roles on Ansible Galaxy.
  • Versatility:
    • Manages not only OS configurations but also clouds (AWS, Azure, GCP), network devices, containers (Kubernetes), and applications.
  • Security:
    • No agents reduce attack surface. Integrates with `ansible-vault` for encrypting secrets (passwords, keys).
  • CI/CD Integration:
    • Playbooks easily integrate with Jenkins, GitLab CI, and others.

Disadvantages and Limitations

  • Performance on Large Fleets:
    • Push model via SSH can bottleneck when managing thousands of servers simultaneously (mitigated by Tower/AWX or dynamic inventory).
  • Limited "Intelligence" on Target Nodes:
    • Complex data processing logic is harder than in agent-based systems (Chef/Puppet), though possible via `command`/`shell` or Jinja2 filters.
  • Debugging Complex Scenarios:
    • Debugging large playbooks or Jinja2 templates can be challenging.
  • Dependency on SSH and Python:
    • Requires working SSH and Python (2.7 or 3.5+) on target nodes for most modules.

Why Use Ansible for VPS?

  • Fast and Predictable Deployment:
    • Installing LAMP/LEMP stacks, Node.js, Docker, PostgreSQL, Redis, etc., becomes a single-step operation.
  • Environment Consistency:
    • Ensures all servers in a group (`production`, `staging`) are identically configured.
  • Reproducibility:
    • Easily recreate a server after failure or deploy a new one.
  • Version-Controlled Configuration:
    • All setup code is stored in Git.
  • Simplified Complex Tasks:
    • Automates firewall setup, backups, and monitoring.
  • Frees Admin Time:
    • Focus on infrastructure improvements, not routine tasks.

Alternatives to Ansible

  • Chef:
    • Powerful, mature tool. Requires an agent (Chef Client) and server (Chef Infra Server). More complex to start but highly flexible for complex, dynamic environments.
  • Puppet:
    • A pioneer. Agent-based (Puppet Agent), uses a declarative DSL. Strong for maintaining long-term consistency in large infrastructures. More complex than Ansible.
  • SaltStack (Salt Project):
    • Agent-based (salt-minion) or agentless (salt-ssh). Fast and flexible. Uses YAML and Jinja2 (like Ansible) but with a different state approach. Can be complex for basic tasks.
  • Terraform:
    • Not a direct competitor! Terraform focuses on cloud infrastructure orchestration (creating VPS, networks, load balancers). Ansible focuses on configuring existing servers. Often used together: Terraform provisions infrastructure, Ansible configures it.
  • Scripts (Bash/Python):
    • Simple for one-off tasks but lack idempotency, are hard to maintain, and don’t scale well.

Setup, Deployment, and Usage - Step-by-Step Example (Installing Nginx + MySQL + PHP on Ubuntu VPS)

Setting Up the Control Node (Ansible Control Node)

OS: Linux (Ubuntu/Debian/CentOS) or macOS. Windows requires WSL.

Install Ansible:

# Ubuntu/Debian

sudo apt update && sudo apt install ansible -y

# CentOS/RHEL (EPEL)

sudo yum install epel-release -y

sudo yum install ansible -y

# Verify

ansible --version

- Set Up SSH Keys: Generate a key pair on the control node (`ssh-keygen`) and copy the public key to target VPS (`ssh-copy-id user@your_vps_ip`) for passwordless access.

Creating Inventory

Create `inventory.ini`:

[webservers]

web1.example.com ansible_host=192.168.1.100 # Replace with your VPS IP/DNS

# web2 ansible_host=192.168.1.101 # Add more servers if needed

[databases]

db1.example.com ansible_host=192.168.1.100 # For this example, same server. Usually separate.

[all:vars]

ansible_user=ubuntu # SSH user

ansible_python_interpreter=/usr/bin/python3 # Specify Python 3 on target nodes

Test Connection:

ansible all -i inventory.ini -m ping

# Should return `pong` for each host

 Creating a Playbook

Create `setup_lamp.yml`:

- name: Install and Configure LAMP Stack on Web Servers

hosts: webservers # Apply to webservers group only

become: yes # Run tasks with root privileges (sudo)

vars: # Variables for the playbook

mysql_root_password: "secure_root_pass" # IN PRODUCTION, USE ansible-vault!

mysql_db_name: "myapp"

mysql_db_user: "appuser"

mysql_db_password: "secure_user_pass" # ENCRYPT THIS TOO!

domain_name: "myapp.example.com"

tasks: # List of tasks

# 1. Update package index

- name: Update apt package index

ansible.builtin.apt:

update_cache: yes

# 2. Install required system packages

- name: Install required system packages

ansible.builtin.apt:

name:

- curl

- software-properties-common

- unzip

- git

state: present

# 3. Install and configure firewall (ufw), allow SSH and HTTP/HTTPS

- name: Install and configure UFW

ansible.builtin.apt:

name: ufw

state: present

notify: Reload UFW # Notify handler

- name: Allow SSH through firewall

ansible.builtin.ufw:

rule: allow

port: '22'

proto: tcp

- name: Allow HTTP through firewall

ansible.builtin.ufw:

rule: allow

port: '80'

proto: tcp

- name: Allow HTTPS through firewall

ansible.builtin.ufw:

rule: allow

port: '443'

proto: tcp

# 4. Install MySQL Server

- name: Install MySQL Server

ansible.builtin.apt:

name: mysql-server

state: present

- name: Ensure MySQL is running and enabled

ansible.builtin.service:

name: mysql

state: started

enabled: yes

# 5. Configure MySQL (secure setup, create DB and user)

- name: Set MySQL root password (Idempotent)

ansible.builtin.mysql_user:

login_user: root

login_password: "" # Initial empty password

name: root

password: "{{ mysql_root_password }}"

host: localhost

check_implicit_admin: yes

state: present

- name: Remove anonymous MySQL users

ansible.builtin.mysql_user:

login_user: root

login_password: "{{ mysql_root_password }}"

name: ''

host_all: yes

state: absent

- name: Remove MySQL test database

ansible.builtin.mysql_db:

login_user: root

login_password: "{{ mysql_root_password }}"

name: test

state: absent

- name: Create application database

ansible.builtin.mysql_db:

login_user: root

login_password: "{{ mysql_root_password }}"

name: "{{ mysql_db_name }}"

state: present

encoding: utf8mb4

collation: utf8mb4_unicode_ci

- name: Create application database user with privileges

ansible.builtin.mysql_user:

login_user: root

login_password: "{{ mysql_root_password }}"

name: "{{ mysql_db_user }}"

password: "{{ mysql_db_password }}"

host: '%' # Or 'localhost' if app is on same server

priv: "{{ mysql_db_name }}.*:ALL"

state: present

# 6. Install PHP and required modules

- name: Add ondrej/php PPA for newer PHP versions

ansible.builtin.apt_repository:

repo: "ppa:ondrej/php"

state: present

- name: Install PHP and common extensions

ansible.builtin.apt:

name:

- php8.1-fpm # Specify desired version (7.4, 8.0, 8.1, 8.2)

- php8.1-mysql

- php8.1-curl

- php8.1-gd

- php8.1-mbstring

- php8.1-xml

- php8.1-zip

state: present

update_cache: yes

- name: Ensure PHP-FPM is running and enabled

ansible.builtin.service:

name: php8.1-fpm

state: started

enabled: yes

# 7. Install Nginx

- name: Install Nginx

ansible.builtin.apt:

name: nginx

state: present

- name: Ensure Nginx is running and enabled

ansible.builtin.service:

name: nginx

state: started

enabled: yes

# 8. Configure Nginx virtual host (using Jinja2 template)

- name: Create Nginx config directory if needed

ansible.builtin.file:

path: /etc/nginx/sites-available

state: directory

- name: Deploy Nginx virtual host configuration from template

ansible.builtin.template:

src: templates/nginx_vhost.conf.j2 # Path to template on Control Node

dest: /etc/nginx/sites-available/{{ domain_name }}.conf

owner: root

group: root

mode: '0644'

notify: Reload Nginx # Notify handler on config change

- name: Enable the site by creating symlink

ansible.builtin.file:

src: /etc/nginx/sites-available/{{ domain_name }}.conf

dest: /etc/nginx/sites-enabled/{{ domain_name }}.conf

state: link

- name: Remove default Nginx site

ansible.builtin.file:

path: /etc/nginx/sites-enabled/default

state: absent

notify: Reload Nginx

# 9. Deploy application code (example)

- name: Create web root directory

ansible.builtin.file:

path: /var/www/{{ domain_name }}

state: directory

owner: "{{ ansible_user }}"

group: www-data

mode: '0775'

- name: Clone Git repository (example)

ansible.builtin.git:

repo: "https://github.com/yourusername/yourrepo.git"

dest: /var/www/{{ domain_name }}

version: "main" # Branch or tag

clone: yes

update: yes

when: false # Uncomment and configure as needed

handlers: # Handlers triggered by notify

- name: Reload UFW

ansible.builtin.service:

name: ufw

state: reloaded

- name: Reload Nginx

ansible.builtin.service:

name: nginx

state: reloaded # Graceful reload without dropping connections

Create Nginx Template (`templates/nginx_vhost.conf.j2`):

server {

listen 80;

listen [::]:80;

server_name {{ domain_name }};

root /var/www/{{ domain_name }}/public; # Path to app's public folder

index index.php index.html index.htm;

location / {

try_files $uri $uri/ /index.php?$query_string;

}

location ~ \.php$ {

include snippets/fastcgi-php.conf;

fastcgi_pass unix:/run/php/php8.1-fpm.sock; # Ensure PHP version matches!

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

include fastcgi_params;

}

location ~ /\.ht {

deny all;

}

# Additional settings (logs, gzip, etc.)

access_log /var/log/nginx/{{ domain_name }}-access.log;

error_log /var/log/nginx/{{ domain_name }}-error.log;
}

Run the Playbook:

ansible-playbook -i inventory.ini setup_lamp.yml

Ansible connects to VPS in the `webservers` group.

Executes tasks sequentially.

Outputs a detailed report (OK, CHANGED, FAILED) for each task on each host.

Usage and Next Steps

  • Testing:
    • Open your VPS IP or configure DNS for `domain_name` in a browser. You should see Nginx’s default page or your app.
  • Re-running:
    • Run the playbook again (`ansible-playbook ...`). Most tasks should return `ok` (idempotency), with changes applied only where needed.
  • Secret Management:
    • Replace plaintext passwords! Use `ansible-vault`:
      • ansible-vault encrypt_string 'secure_root_pass' --name 'mysql_root_password'
  • Copy encrypted output to `vars/vault.yml`, include it in the playbook with `vars_files`, and use `--ask-vault-pass` when running.
  • Structuring:
    • Split large playbooks into Roles (e.g., `common`, `mysql`, `php`, `nginx`, `app`). Use `ansible-galaxy init role_name` to create role structures.
  • Ansible Galaxy:
    • Use pre-built roles for common software (e.g., `geerlingguy.mysql`, `geerlingguy.nginx`). Install with `ansible-galaxy install role_name`.
  • Dynamic Inventory:
    • Use scripts (Python, Bash) or cloud provider plugins to auto-fetch host lists from clouds (AWS, DigitalOcean, etc.).
  • Testing Playbooks:
    • Use Molecule and Testinfra to write tests for your roles.

Ansible is a powerful, accessible, and elegant tool that transforms VPS management into a predictable, versioned, and scalable process. By describing infrastructure as code, it ensures consistency, repeatability, and reliability. Start with simple task automation, master core concepts (inventory, playbooks, tasks, templates, variables, roles), and gradually manage your entire infrastructure. Investing time in learning Ansible pays off by saving hours of manual work and preventing incidents.

Vote:
5 out of 5
Аverage rating : 5
Rated by: 1
1101 CT Amsterdam The Netherlands, Herikerbergweg 292
+31 20 262-58-98
700 300
ITGLOBAL.COM NL
700 300

You might also like...

We use cookies to make your experience on the Serverspace better. By continuing to browse our website, you agree to our
Use of Cookies and Privacy Policy.