--- # Ultimate LEMP WordPress installation with Redis, OPcache and Nginx Optimization - name: Install Ultimate LEMP stack with WordPress, Redis, OPcache and Nginx Performance (Ubuntu/Debian) hosts: all become: yes pre_tasks: - name: Gather OS facts setup: gather_subset: - '!all' - '!min' - 'distribution' - 'distribution_version' - 'os_family' - name: Load Debian family variables include_vars: "../vars/debian-family.yml" - name: Debug OS information debug: msg: | OS Family: {{ ansible_os_family }} Distribution: {{ ansible_distribution }} Version: {{ ansible_distribution_version }} Supported: Ubuntu/Debian family systems - name: Verify supported OS fail: msg: "This playbook only supports Ubuntu/Debian systems. Detected: {{ ansible_os_family }}" when: ansible_os_family != "Debian" # Environment Detection and Configuration - name: Detect if running in Docker stat: path: /.dockerenv register: docker_env tags: always - name: Check for deployment_environment override set_fact: environment_override: "{{ deployment_environment is defined }}" tags: always - name: Set deployment environment (auto-detect or override) set_fact: deployment_environment: >- {{ deployment_environment | default('docker' if docker_env.stat.exists else 'bare_metal') }} tags: always - name: Set WordPress site URL with smart defaults set_fact: wp_site_url: "{{ wp_site_url | default(_auto_wp_url) }}" vars: _auto_wp_url: >- {{ 'http://localhost:8080' if (deployment_environment == 'docker') else 'http://' + (ansible_default_ipv4.address | default('localhost')) }} tags: always - name: Debug deployment configuration debug: msg: | Environment: {{ deployment_environment }} ({{ 'overridden' if environment_override else 'auto-detected' }}) WordPress URL: {{ wp_site_url }} Docker detected: {{ docker_env.stat.exists }} tags: always vars: # Default PHP version (will be overridden by detection) php_version: "8.4" # WordPress Configuration wordpress_path: "{{ web_root }}" wordpress_url: "http://{{ ansible_default_ipv4.address | default('localhost') }}" # Database Configuration (use inventory variables or these defaults) default_mysql_root_password: "{{ mysql_root_password | default('secure_root_password_123') }}" default_wordpress_db_name: "{{ wordpress_db_name | default('wordpress_db') }}" default_wordpress_db_user: "{{ wordpress_db_user | default('wordpress_user') }}" default_wordpress_db_password: "{{ wordpress_db_password | default('secure_wp_password_123') }}" # WordPress Admin (use inventory variables directly) # These variables come from inventory/production.yml # wp_admin_user, wp_admin_password, wp_admin_email, wp_site_title tasks: # IMPORTANT: Pre-flight checks - create basic directory structure FIRST - name: Ensure /var/www exists file: path: /var/www state: directory mode: '0755' tags: [preflight] - name: Create web root directory file: path: /var/www/html state: directory owner: www-data group: www-data mode: '0755' tags: [preflight] - name: Create PHP-FPM log directory file: path: "/var/log/php{{ php_version }}-fpm" state: directory mode: '0755' tags: [preflight] # Package Installation - name: Update package cache apt: update_cache: yes cache_valid_time: 3600 tags: packages - name: Install essential packages apt: name: - gnupg - nginx state: present tags: [packages, nginx] - name: Detect available PHP version and set fallback shell: | if apt-cache show php8.4-fpm >/dev/null 2>&1; then echo "8.4" elif apt-cache show php8.3-fpm >/dev/null 2>&1; then echo "8.3" else echo "none" fi register: php_version_detect changed_when: false tags: packages - name: Set PHP version fact set_fact: php_version: "{{ php_version_detect.stdout }}" tags: packages - name: Install PHP with fallback (try 8.4, fallback to 8.3) apt: name: - "php{{ php_version }}-fpm" - "php{{ php_version }}-mysql" - "php{{ php_version }}-curl" - "php{{ php_version }}-gd" - "php{{ php_version }}-intl" - "php{{ php_version }}-mbstring" - "php{{ php_version }}-soap" - "php{{ php_version }}-xml" - "php{{ php_version }}-xmlrpc" - "php{{ php_version }}-zip" - "php{{ php_version }}-opcache" - "php{{ php_version }}-redis" state: present register: php_install_result tags: packages - name: Install remaining packages package: name: "{{ packages | reject('search', '^php') | list }}" state: present tags: packages - name: Display PHP version installed debug: msg: "Using PHP version {{ php_version }}" tags: packages - name: Set PHP FPM service name dynamically set_fact: php_fpm_service: "php{{ php_version }}-fpm" tags: packages # Service Management - name: Start and enable Nginx service: name: "{{ nginx_service }}" state: started enabled: yes tags: nginx - name: Start and enable MySQL/MariaDB service: name: "{{ mysql_service }}" state: started enabled: yes tags: mysql - name: Start and enable PHP-FPM service: name: "{{ php_fpm_service }}" state: started enabled: yes tags: php # Firewall Configuration - name: Configure firewall (UFW) ufw: rule: allow port: "{{ item }}" proto: tcp loop: - "22" - "80" - "443" tags: firewall # MySQL Security (Ubuntu 24.04 fix for auth_socket) - name: Set MySQL root password (using auth_socket first) mysql_user: name: root password: "{{ mysql_root_password }}" plugin: mysql_native_password login_unix_socket: /var/run/mysqld/mysqld.sock state: present become: true tags: mysql - name: Create MySQL configuration for root template: src: ../templates/my.cnf.j2 dest: /root/.my.cnf mode: '0600' tags: mysql - name: Remove anonymous MySQL users mysql_user: name: "" host_all: yes state: absent tags: mysql - name: Remove MySQL test database mysql_db: name: test state: absent tags: mysql # WordPress Database Setup - name: Create WordPress database mysql_db: name: "{{ wordpress_db_name }}" state: present tags: mysql - name: Create WordPress database user mysql_user: name: "{{ wordpress_db_user }}" password: "{{ wordpress_db_password }}" priv: "{{ wordpress_db_name }}.*:ALL" host: localhost state: present tags: mysql # WordPress Installation - name: Create web root directory file: path: "{{ wordpress_path }}" state: directory owner: www-data group: www-data mode: '0755' tags: wordpress - name: Remove default nginx index file file: path: "{{ wordpress_path }}/index.nginx-debian.html" state: absent tags: wordpress - name: Download WordPress get_url: url: https://wordpress.org/latest.tar.gz dest: /tmp/wordpress.tar.gz mode: '0644' tags: wordpress - name: Extract WordPress unarchive: src: /tmp/wordpress.tar.gz dest: /tmp/ remote_src: yes tags: wordpress - name: Copy WordPress files command: "cp -r /tmp/wordpress/. {{ wordpress_path }}/" args: creates: "{{ wordpress_path }}/wp-config-sample.php" tags: wordpress - name: Set WordPress file permissions file: path: "{{ wordpress_path }}" owner: "{{ nginx_user }}" group: "{{ nginx_user }}" recurse: yes tags: wordpress # SSL Configuration (optional) - name: Generate self-signed SSL certificate (if SSL enabled but no cert provided) command: > openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout {{ ssl_cert_key_path | default('/etc/ssl/private/nginx-selfsigned.key') }} -out {{ ssl_cert_path | default('/etc/ssl/certs/nginx-selfsigned.crt') }} -subj "/C=DE/ST=State/L=City/O=Organization/OU=OrgUnit/CN={{ domain_name }}" when: - ssl_enabled | default(false) - not (ssl_cert_path is defined and ssl_cert_key_path is defined) tags: ssl # Nginx Configuration - name: Create Nginx configuration for WordPress template: src: >- ../templates/{% if ssl_enabled | default(false) %}wordpress-ssl.nginx.j2{% else %}wordpress.nginx.j2{% endif %} dest: "{{ nginx_sites_available }}/wordpress" backup: yes notify: restart nginx tags: nginx - name: Enable WordPress site file: src: "{{ nginx_sites_available }}/wordpress" dest: "{{ nginx_sites_enabled }}/wordpress" state: link notify: restart nginx tags: nginx - name: Remove default Nginx site file: path: "{{ nginx_sites_enabled }}/default" state: absent notify: restart nginx tags: nginx # PHP Configuration - name: Configure PHP-FPM pool template: src: ../templates/www.conf.j2 dest: "{{ php_fpm_pool_path }}/www.conf" backup: yes notify: restart php-fpm tags: php - name: Configure PHP settings lineinfile: path: "{{ php_cli_config_path }}/php.ini" regexp: "{{ item.regexp }}" line: "{{ item.line }}" backup: yes loop: - { regexp: '^upload_max_filesize', line: 'upload_max_filesize = 64M' } - { regexp: '^post_max_size', line: 'post_max_size = 64M' } - { regexp: '^memory_limit', line: 'memory_limit = 256M' } - { regexp: '^max_execution_time', line: 'max_execution_time = 300' } - { regexp: '^disable_functions', line: 'disable_functions = ' } notify: restart php-fpm tags: php # WordPress Configuration - name: Configure WordPress template: src: ../templates/wp-config.php.j2 dest: "{{ wordpress_path }}/wp-config.php" owner: www-data group: www-data mode: '0600' tags: wordpress # WP-CLI Installation - name: Download WP-CLI get_url: url: https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar dest: /usr/local/bin/wp mode: '0755' tags: wp-cli - name: Verify WP-CLI installation command: wp --info environment: HOME: "{{ wordpress_path }}" register: wp_cli_info tags: wp-cli - name: Display WP-CLI info debug: var: wp_cli_info.stdout_lines tags: wp-cli # WordPress Core Installation - name: Install WordPress Core command: > wp core install --url="{{ wp_site_url }}" --title="{{ wp_site_title }}" --admin_user="{{ wp_admin_user }}" --admin_password="{{ wp_admin_password }}" --admin_email="{{ wp_admin_email }}" --path="{{ wordpress_path }}" --allow-root environment: HOME: "{{ wordpress_path }}" tags: wp-install - name: Set WordPress file ownership after installation file: path: "{{ wordpress_path }}" owner: "{{ nginx_user }}" group: "{{ nginx_user }}" recurse: yes tags: wp-install - name: Display WordPress installation success debug: msg: | WordPress installation completed! URL: {{ wp_site_url }} Admin User: {{ wp_admin_user }} Admin Password: {{ wp_admin_password }} Admin Email: {{ wp_admin_email }} tags: wp-install # ============================= # REDIS CACHE (ULTIMATE FEATURE) # ============================= - name: Install Redis server package: name: redis-server state: present when: enable_redis | default(false) tags: redis - name: Start and enable Redis service: name: redis-server state: started enabled: yes when: enable_redis | default(false) tags: redis - name: Configure Redis for WordPress lineinfile: path: /etc/redis/redis.conf regexp: "{{ item.regexp }}" line: "{{ item.line }}" backup: yes loop: - { regexp: '^# maxmemory', line: 'maxmemory 128mb' } - { regexp: '^# maxmemory-policy', line: 'maxmemory-policy allkeys-lru' } - { regexp: '^save 900 1', line: 'save 900 1' } when: enable_redis | default(false) notify: restart redis tags: redis - name: Install Redis Object Cache plugin command: > wp plugin install redis-cache --activate --path="{{ wordpress_path }}" --allow-root environment: HOME: "{{ wordpress_path }}" when: enable_redis | default(false) tags: redis - name: Enable Redis object cache command: > wp redis enable --path="{{ wordpress_path }}" --allow-root environment: HOME: "{{ wordpress_path }}" when: enable_redis | default(false) ignore_errors: yes tags: redis - name: Display Redis status debug: msg: "Redis caching {{ 'ENABLED' if enable_redis | default(false) else 'DISABLED' }}" tags: redis # ============================= # OPCACHE OPTIMIZATION (ULTIMATE FEATURE) # ============================= - name: Configure OPcache for WordPress lineinfile: path: "{{ php_cli_config_path }}/php.ini" regexp: "{{ item.regexp }}" line: "{{ item.line }}" backup: yes loop: - { regexp: '^;?opcache.enable=', line: 'opcache.enable=1' } - { regexp: '^;?opcache.memory_consumption=', line: 'opcache.memory_consumption=128' } - { regexp: '^;?opcache.interned_strings_buffer=', line: 'opcache.interned_strings_buffer=8' } - { regexp: '^;?opcache.max_accelerated_files=', line: 'opcache.max_accelerated_files=4000' } - { regexp: '^;?opcache.revalidate_freq=', line: 'opcache.revalidate_freq=2' } - { regexp: '^;?opcache.fast_shutdown=', line: 'opcache.fast_shutdown=1' } when: enable_opcache | default(false) notify: restart php-fpm tags: opcache - name: Configure OPcache for PHP-FPM lineinfile: path: "/etc/php/{{ php_version }}/fpm/php.ini" regexp: "{{ item.regexp }}" line: "{{ item.line }}" backup: yes loop: - { regexp: '^;?opcache.enable=', line: 'opcache.enable=1' } - { regexp: '^;?opcache.memory_consumption=', line: 'opcache.memory_consumption=128' } - { regexp: '^;?opcache.interned_strings_buffer=', line: 'opcache.interned_strings_buffer=8' } - { regexp: '^;?opcache.max_accelerated_files=', line: 'opcache.max_accelerated_files=4000' } - { regexp: '^;?opcache.revalidate_freq=', line: 'opcache.revalidate_freq=2' } - { regexp: '^;?opcache.fast_shutdown=', line: 'opcache.fast_shutdown=1' } when: enable_opcache | default(false) notify: restart php-fpm tags: opcache - name: Display OPcache status debug: msg: "OPcache optimization {{ 'ENABLED' if enable_opcache | default(false) else 'DISABLED' }}" tags: opcache # ============================= # NGINX PERFORMANCE OPTIMIZATION (ULTIMATE FEATURE) # ============================= - name: Backup original nginx.conf copy: src: /etc/nginx/nginx.conf dest: /etc/nginx/nginx.conf.backup remote_src: yes when: enable_nginx_optimization | default(false) tags: nginx-optimization # Clean up any existing ansible-managed blocks first - name: Remove existing ansible-managed blocks from nginx.conf blockinfile: path: /etc/nginx/nginx.conf marker: "# {mark} ANSIBLE MANAGED BLOCK - Ultimate Performance" state: absent when: enable_nginx_optimization | default(false) tags: nginx-optimization - name: Remove duplicate worker directives from wrong blocks lineinfile: path: /etc/nginx/nginx.conf regexp: "{{ item }}" state: absent loop: - '^\s*worker_processes\s+auto;.*# Added by Ansible' - '^\s*worker_rlimit_nofile\s+65535;.*# Added by Ansible' when: enable_nginx_optimization | default(false) tags: nginx-optimization # Configure Worker settings in main block (before events) - name: Configure Nginx worker settings in main block (before events) lineinfile: path: /etc/nginx/nginx.conf regexp: "{{ item.regexp }}" line: "{{ item.line }}" insertbefore: "events {" backup: yes loop: - { regexp: '^worker_processes', line: 'worker_processes auto;' } - { regexp: '^worker_rlimit_nofile', line: 'worker_rlimit_nofile 65535;' } when: enable_nginx_optimization | default(false) notify: restart nginx tags: nginx-optimization - name: Configure Nginx events block lineinfile: path: /etc/nginx/nginx.conf regexp: '^(\s*)worker_connections.*' line: '\1worker_connections 2048;' backrefs: yes backup: yes when: enable_nginx_optimization | default(false) notify: restart nginx tags: nginx-optimization # Configure HTTP block optimizations (only unique directives) - name: Configure optimized Nginx HTTP configuration blockinfile: path: /etc/nginx/nginx.conf marker: "# {mark} ANSIBLE MANAGED BLOCK - HTTP Performance" insertafter: "http {" block: | # Ultimate Performance optimizations (unique directives only) keepalive_requests 1000; # Enhanced Gzip compression gzip_vary on; gzip_min_length 1000; gzip_comp_level 6; gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml+rss application/atom+xml image/svg+xml; # Buffer sizes client_body_buffer_size 128k; client_max_body_size 64m; client_header_buffer_size 1k; large_client_header_buffers 4 4k; # Timeouts client_body_timeout 12; client_header_timeout 12; send_timeout 10; # Cache for file handles open_file_cache max=200000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on; backup: yes when: enable_nginx_optimization | default(false) notify: restart nginx tags: nginx-optimization - name: Create Nginx cache directory file: path: /var/cache/nginx/fastcgi state: directory owner: www-data group: www-data mode: '0755' when: enable_nginx_optimization | default(false) tags: nginx-optimization - name: Display Nginx optimization status debug: msg: >- Nginx performance optimization {{ 'ENABLED' if enable_nginx_optimization | default(false) else 'DISABLED' }} tags: nginx-optimization handlers: - name: restart nginx service: name: "{{ nginx_service }}" state: restarted - name: restart php-fpm service: name: "{{ php_fpm_service }}" state: restarted - name: restart mysql service: name: "{{ mysql_service }}" state: restarted - name: restart redis service: name: redis-server state: restarted