Complete Guide to Ansible Conditionals: Master Control Flow in Your Playbooks

Conditionals are essential for creating intelligent, adaptive Ansible playbooks that can make decisions based on facts, variables, and runtime conditions. This comprehensive guide covers all aspects of conditional execution in Ansible.

1. Basic When Clauses

The when statement is the foundation of conditionals in Ansible. It evaluates a Jinja2 expression and executes the task only if the condition is true.

---
- name: Basic Conditional Examples
  hosts: all
  vars:
    run_updates: true
    environment: production

  tasks:
    - name: Update system packages
      apt:
        update_cache: yes
        upgrade: dist
      when: run_updates

    - name: Install monitoring agent on production servers
      package:
        name: datadog-agent
        state: present
      when: environment == "production"

    - name: Skip this task on test servers
      debug:
        msg: "This only runs on non-test servers"
      when: environment != "test"

Key Points:

  • Boolean variables don’t need comparison operators (just use when: variable_name)
  • String comparisons use == or !=
  • Jinja2 expressions in when don’t need double curly braces
  • Tasks are skipped silently when conditions are false

2. OS and Distribution Checks

Ansible facts provide detailed information about target systems, making OS-specific tasks easy to implement.

---
- name: OS-Specific Package Installation
  hosts: all
  become: yes

  tasks:
    - name: Install Apache on Debian/Ubuntu
      apt:
        name: apache2
        state: present
      when: ansible_os_family == "Debian"

    - name: Install Apache on RedHat/CentOS
      yum:
        name: httpd
        state: present
      when: ansible_os_family == "RedHat"

    - name: Install Apache on Fedora
      dnf:
        name: httpd
        state: present
      when: ansible_distribution == "Fedora"

    - name: Ubuntu-specific configuration
      template:
        src: ubuntu_config.j2
        dest: /etc/app/config.conf
      when:
        - ansible_distribution == "Ubuntu"
        - ansible_distribution_version >= "20.04"

    - name: CentOS 7 specific task
      shell: systemctl enable legacy-service
      when:
        - ansible_distribution == "CentOS"
        - ansible_distribution_major_version == "7"

Common Ansible Facts for OS Checks:

  • ansible_os_family: Debian, RedHat, Windows, Darwin
  • ansible_distribution: Ubuntu, CentOS, Fedora, Debian
  • ansible_distribution_version: 20.04, 8.5, etc.
  • ansible_distribution_major_version: 20, 8, 7
  • ansible_system: Linux, Win32NT, Darwin
  • ansible_architecture: x86_64, aarch64, armv7l

3. Multiple Conditions (AND/OR Logic)

Combine multiple conditions using logical operators or list format for complex decision-making.

---
- name: Multiple Condition Examples
  hosts: all
  vars:
    enable_ssl: true
    environment: production
    cpu_cores: 8
    memory_gb: 16

  tasks:
    # AND condition - list format (all must be true)
    - name: Deploy high-performance config
      template:
        src: high_perf_config.j2
        dest: /etc/app/config.conf
      when:
        - cpu_cores >= 8
        - memory_gb >= 16
        - environment == "production"

    # AND condition - inline format
    - name: Enable SSL on production with certificate
      service:
        name: nginx-ssl
        state: started
      when: environment == "production" and enable_ssl

    # OR condition - using parentheses
    - name: Install on dev or staging
      package:
        name: debug-tools
        state: present
      when: environment == "development" or environment == "staging"

    # Complex logic with AND/OR
    - name: Complex conditional
      debug:
        msg: "Running complex condition"
      when: >
        (environment == "production" and enable_ssl) or
        (environment == "staging" and cpu_cores >= 4)

    # NOT logic
    - name: Run only if NOT production
      debug:
        msg: "Safe to test here"
      when: not environment == "production"

    # IN operator
    - name: Run on multiple environments
      debug:
        msg: "Running on allowed environment"
      when: environment in ["development", "staging", "qa"]

4. Variable Defined/Undefined Checks

Check whether variables exist before using them to prevent errors.

---
- name: Variable Definition Checks
  hosts: all
  vars:
    database_host: "db.example.com"

  tasks:
    - name: Run only if variable is defined
      debug:
        msg: "Database host is {{ database_host }}"
      when: database_host is defined

    - name: Set default if variable is undefined
      set_fact:
        app_port: 8080
      when: app_port is not defined

    - name: Check if variable is defined and not empty
      debug:
        msg: "API key is configured"
      when:
        - api_key is defined
        - api_key | length > 0

    - name: Use default_value filter
      debug:
        msg: "Port is {{ custom_port | default(8080) }}"

    - name: Check if variable is None
      debug:
        msg: "Variable is explicitly set to None"
      when: some_var is none

    - name: Check if variable has a value (not None or undefined)
      debug:
        msg: "Variable has a value"
      when: some_var is defined and some_var is not none

5. String Testing and Pattern Matching

Perform sophisticated string matching using regex, search, and match tests.

---
- name: String Testing Examples
  hosts: all
  vars:
    hostname: "web-server-01.production.example.com"
    service_name: "nginx-1.20.1"
    log_level: "DEBUG"

  tasks:
    - name: Check if string starts with pattern (match)
      debug:
        msg: "This is a web server"
      when: hostname is match("web-.*")

    - name: Check if string contains pattern (search)
      debug:
        msg: "Running in production"
      when: hostname is search("production")

    - name: Regex test with groups
      debug:
        msg: "Nginx version detected"
      when: service_name is regex("nginx-[0-9]+\.[0-9]+\.[0-9]+")

    - name: Case-insensitive search
      debug:
        msg: "Debug logging enabled"
      when: log_level | lower == "debug"

    - name: Check string length
      debug:
        msg: "Long hostname detected"
      when: hostname | length > 20

    - name: Check if string is in list
      debug:
        msg: "Valid log level"
      when: log_level in ["DEBUG", "INFO", "WARN", "ERROR"]

    - name: String starts with
      debug:
        msg: "Production server"
      when: hostname.startswith("web-server")

    - name: String ends with
      debug:
        msg: "Example.com domain"
      when: hostname.endswith(".example.com")

    - name: Check if variable is a string
      debug:
        msg: "Variable is a string"
      when: hostname is string

6. List and Dictionary Tests

Conditional logic for collection data types.

---
- name: List and Dictionary Conditionals
  hosts: all
  vars:
    packages: ["nginx", "mysql", "redis"]
    server_config:
      ssl_enabled: true
      port: 443
      workers: 4
    empty_list: []

  tasks:
    - name: Check if variable is a list
      debug:
        msg: "Packages is a list"
      when: packages is iterable and packages is not string

    - name: Check if list is not empty
      debug:
        msg: "We have packages to install"
      when: packages | length > 0

    - name: Check if item in list
      debug:
        msg: "Nginx will be installed"
      when: "'nginx' in packages"

    - name: Check if list is empty
      debug:
        msg: "No items in list"
      when: empty_list | length == 0

    - name: Check if variable is a dictionary
      debug:
        msg: "server_config is a dictionary"
      when: server_config is mapping

    - name: Check if key exists in dictionary
      debug:
        msg: "SSL is configured"
      when: "'ssl_enabled' in server_config"

    - name: Check dictionary value
      debug:
        msg: "SSL is enabled"
      when:
        - server_config.ssl_enabled is defined
        - server_config.ssl_enabled

    - name: Count dictionary keys
      debug:
        msg: "Config has {{ server_config | length }} settings"
      when: server_config | length > 0

    - name: Check if all items in list match condition
      debug:
        msg: "All ports are open"
      when: server_config.port > 0

    - name: Subset check
      debug:
        msg: "Required packages present"
      when: "['nginx', 'mysql'] is subset(packages)"

7. Numeric Comparisons

Perform mathematical comparisons and numeric tests.

---
- name: Numeric Conditional Examples
  hosts: all
  vars:
    available_memory: 16384
    cpu_count: 8
    disk_usage_percent: 75
    app_version: "2.5.3"

  tasks:
    - name: Greater than comparison
      debug:
        msg: "High memory system"
      when: available_memory > 8192

    - name: Less than or equal
      debug:
        msg: "Disk usage acceptable"
      when: disk_usage_percent <= 80

    - name: Range check
      debug:
        msg: "CPU count in acceptable range"
      when: cpu_count >= 4 and cpu_count <= 16

    - name: Modulo operation
      debug:
        msg: "Even number of CPUs"
      when: cpu_count % 2 == 0

    - name: Check if number
      debug:
        msg: "Memory is numeric"
      when: available_memory is number

    - name: Check if integer
      debug:
        msg: "CPU count is integer"
      when: cpu_count is integer

    - name: Check if float
      debug:
        msg: "Value is float"
      when: some_value is float

    - name: Version comparison
      debug:
        msg: "App version is 2.5 or higher"
      when: app_version is version('2.5', '>=')

    - name: Version comparison with operator
      debug:
        msg: "Running latest major version"
      when: app_version is version('2.0', '>=') and app_version is version('3.0', '<')

    - name: Divisible by check
      debug:
        msg: "CPU count divisible by 4"
      when: cpu_count is divisibleby(4)

8. File and Path Tests

Check file existence, type, and permissions using stat module with conditionals.

---
- name: File and Path Conditionals
  hosts: all

  tasks:
    - name: Check if file exists
      stat:
        path: /etc/app/config.conf
      register: config_file

    - name: Create config if it doesn't exist
      template:
        src: config.conf.j2
        dest: /etc/app/config.conf
      when: not config_file.stat.exists

    - name: Check if path is a directory
      stat:
        path: /var/www/html
      register: web_dir

    - name: Create directory if not exists
      file:
        path: /var/www/html
        state: directory
      when: not web_dir.stat.exists or not web_dir.stat.isdir

    - name: Check file permissions
      stat:
        path: /etc/ssl/private/key.pem
      register: ssl_key

    - name: Fix permissions if needed
      file:
        path: /etc/ssl/private/key.pem
        mode: '0600'
      when:
        - ssl_key.stat.exists
        - ssl_key.stat.mode != '0600'

    - name: Check if file is a symlink
      stat:
        path: /usr/bin/python
      register: python_bin

    - name: Report symlink info
      debug:
        msg: "Python is a symlink to {{ python_bin.stat.lnk_source }}"
      when:
        - python_bin.stat.exists
        - python_bin.stat.islnk

    - name: Check file age
      stat:
        path: /var/log/app.log
      register: log_file

    - name: Rotate old log file
      command: mv /var/log/app.log /var/log/app.log.old
      when:
        - log_file.stat.exists
        - log_file.stat.mtime < (ansible_date_time.epoch | int - 86400)

    - name: Check file size
      stat:
        path: /var/cache/downloads/large_file.zip
      register: download_file

    - name: Remove if file too large
      file:
        path: /var/cache/downloads/large_file.zip
        state: absent
      when:
        - download_file.stat.exists
        - download_file.stat.size > 1073741824  # 1GB in bytes

9. Registered Variable Conditions

Use output from previous tasks to control subsequent task execution.

---
- name: Registered Variable Conditionals
  hosts: all

  tasks:
    - name: Check if service is running
      command: systemctl is-active nginx
      register: nginx_status
      failed_when: false
      changed_when: false

    - name: Start nginx if not running
      service:
        name: nginx
        state: started
      when: nginx_status.rc != 0

    - name: Run application test
      command: /opt/app/test.sh
      register: test_result
      ignore_errors: yes

    - name: Deploy rollback if test failed
      command: /opt/app/rollback.sh
      when: test_result.rc != 0

    - name: Get disk usage
      shell: df -h / | awk 'NR==2 {print $5}' | sed 's/%//'
      register: disk_usage
      changed_when: false

    - name: Send alert if disk usage high
      debug:
        msg: "WARNING: Disk usage is {{ disk_usage.stdout }}%"
      when: disk_usage.stdout | int > 80

    - name: Check for package
      command: dpkg -l | grep nginx
      register: package_check
      failed_when: false
      changed_when: false

    - name: Install package if missing
      apt:
        name: nginx
        state: present
      when: package_check.rc != 0

    - name: Query database
      command: mysql -u root -e "SELECT COUNT(*) FROM users;"
      register: user_count
      changed_when: false

    - name: Import sample data if empty
      command: mysql -u root < /tmp/sample_data.sql
      when: user_count.stdout_lines[1] | int == 0

    - name: Get service status JSON
      uri:
        url: http://localhost:8080/health
        return_content: yes
      register: health_check

    - name: Restart service if unhealthy
      service:
        name: myapp
        state: restarted
      when:
        - health_check.status == 200
        - health_check.json.status != "healthy"

10. failed_when and changed_when

Control when Ansible considers a task as failed or changed.

---
- name: failed_when and changed_when Examples
  hosts: all

  tasks:
    # Custom failure conditions
    - name: Check application status
      command: /opt/app/status.sh
      register: app_status
      failed_when:
        - app_status.rc != 0
        - "'WARNING' not in app_status.stderr"

    - name: Validate configuration
      command: nginx -t
      register: nginx_test
      failed_when: "'test failed' in nginx_test.stderr"

    - name: Check disk space
      shell: df -h / | awk 'NR==2 {print $5}' | sed 's/%//'
      register: disk_check
      failed_when: disk_check.stdout | int > 90
      changed_when: false

    # Custom changed conditions
    - name: Ensure line in config
      lineinfile:
        path: /etc/app/config.conf
        line: "max_connections = 1000"
      register: config_update
      changed_when: "'changed' in config_update.msg"

    - name: Run idempotent script
      command: /opt/scripts/setup.sh
      register: setup_result
      changed_when: "'Configuration updated' in setup_result.stdout"

    - name: Check and report only
      command: systemctl status nginx
      register: service_status
      changed_when: false
      failed_when: false

    # Complex logic
    - name: Database migration
      command: /opt/app/migrate.sh
      register: migration
      changed_when: "'migrations applied' in migration.stdout"
      failed_when:
        - migration.rc != 0
        - "'already up to date' not in migration.stdout"

    - name: API health check
      uri:
        url: http://localhost:8080/health
        status_code: [200, 503]
      register: api_health
      failed_when:
        - api_health.status == 503
        - "'maintenance' not in api_health.content"
      changed_when: false

    # Never fail
    - name: Best effort cleanup
      command: rm -f /tmp/old_cache/*
      failed_when: false

    # Always changed
    - name: Force handler notification
      command: /bin/true
      changed_when: true
      notify: Restart Application

  handlers:
    - name: Restart Application
      service:
        name: myapp
        state: restarted

11. Blocks with rescue and always

Group tasks together with error handling using blocks, similar to try/catch/finally.

---
- name: Block, Rescue, and Always Examples
  hosts: all

  tasks:
    - name: Deploy application with error handling
      block:
        - name: Stop application
          service:
            name: myapp
            state: stopped

        - name: Backup current version
          command: cp -r /opt/app /opt/app.backup

        - name: Deploy new version
          unarchive:
            src: /tmp/myapp-v2.tar.gz
            dest: /opt/app

        - name: Run database migrations
          command: /opt/app/migrate.sh

        - name: Start application
          service:
            name: myapp
            state: started

        - name: Health check
          uri:
            url: http://localhost:8080/health
            status_code: 200

      rescue:
        - name: Log deployment failure
          debug:
            msg: "Deployment failed, rolling back..."

        - name: Restore backup
          command: rm -rf /opt/app && mv /opt/app.backup /opt/app

        - name: Start old version
          service:
            name: myapp
            state: started

        - name: Send alert
          debug:
            msg: "ALERT: Deployment failed, rolled back to previous version"

      always:
        - name: Remove temporary files
          file:
            path: /tmp/myapp-v2.tar.gz
            state: absent

        - name: Log deployment attempt
          lineinfile:
            path: /var/log/deployments.log
            line: "{{ ansible_date_time.iso8601 }} - Deployment attempt completed"
            create: yes

    # Conditional blocks
    - name: Production-only deployment block
      block:
        - name: Enable maintenance mode
          command: /opt/app/maintenance.sh on

        - name: Deploy to production
          command: /opt/app/deploy.sh production

        - name: Disable maintenance mode
          command: /opt/app/maintenance.sh off

      when: environment == "production"

    # Nested blocks
    - name: Complex deployment with nested error handling
      block:
        - name: Pre-deployment checks
          block:
            - name: Check disk space
              command: df -h /opt
              register: disk_space

            - name: Fail if insufficient space
              fail:
                msg: "Insufficient disk space"
              when: disk_space.stdout is search("9[0-9]%|100%")

          rescue:
            - name: Clean old deployments
              command: /opt/scripts/cleanup.sh

        - name: Actual deployment
          command: /opt/app/deploy.sh

      rescue:
        - name: Emergency rollback
          command: /opt/app/rollback.sh

12. Complex Conditional Logic

Real-world examples combining multiple conditional techniques.

---
- name: Complex Real-World Conditionals
  hosts: all
  vars:
    environment: production
    enable_monitoring: true

  tasks:
    # Multi-tier application deployment
    - name: Determine server role
      set_fact:
        server_role: >-
          {%- if 'webservers' in group_names -%}
            web
          {%- elif 'dbservers' in group_names -%}
            database
          {%- elif 'cacheservers' in group_names -%}
            cache
          {%- else -%}
            unknown
          {%- endif -%}

    - name: Install role-specific packages
      package:
        name: "{{ item }}"
        state: present
      loop: "{{ packages[server_role] }}"
      when:
        - server_role in packages
        - packages[server_role] is defined
        - packages[server_role] | length > 0
      vars:
        packages:
          web: ["nginx", "php-fpm"]
          database: ["mysql-server"]
          cache: ["redis-server"]

    # Conditional based on multiple factors
    - name: Configure application
      template:
        src: "app_{{ server_role }}_{{ environment }}.conf.j2"
        dest: /etc/app/config.conf
      when:
        - server_role != "unknown"
        - environment in ["development", "staging", "production"]
      notify: Restart Application

    # Maintenance window check
    - name: Check if in maintenance window
      set_fact:
        in_maintenance_window: >-
          {{
            (ansible_date_time.hour | int >= 2 and ansible_date_time.hour | int < 4)
            or
            (ansible_date_time.weekday | int == 6)
          }}

    - name: Run heavy maintenance tasks
      command: /opt/scripts/reindex_database.sh
      when:
        - in_maintenance_window
        - environment == "production"
        - "'dbservers' in group_names"

    # Feature flag system
    - name: Load feature flags
      set_fact:
        features:
          new_ui: "{{ lookup('env', 'FEATURE_NEW_UI') | default('false') | bool }}"
          beta_api: "{{ lookup('env', 'FEATURE_BETA_API') | default('false') | bool }}"

    - name: Deploy new UI
      copy:
        src: new_ui/
        dest: /var/www/html/
      when:
        - features.new_ui
        - server_role == "web"

    # Complex validation
    - name: Validate SSL certificate
      block:
        - name: Get certificate info
          command: openssl x509 -in /etc/ssl/certs/server.crt -noout -enddate
          register: cert_info
          changed_when: false

        - name: Parse expiry date
          set_fact:
            cert_expiry: "{{ cert_info.stdout | regex_replace('notAfter=(.+)', '\1') }}"

        - name: Check if certificate expiring soon
          set_fact:
            cert_expiring: >-
              {{
                (cert_expiry | to_datetime('%b %d %H:%M:%S %Y %Z')).epoch
                <
                (ansible_date_time.epoch | int + 2592000)
              }}

        - name: Renew certificate if needed
          command: /opt/scripts/renew_cert.sh
          when: cert_expiring

      when:
        - environment == "production"
        - enable_ssl is defined
        - enable_ssl

  handlers:
    - name: Restart Application
      service:
        name: "{{ 'nginx' if server_role == 'web' else 'myapp' }}"
        state: restarted
      when: server_role in ['web', 'database', 'cache']

13. Best Practices

1. Readability and Maintainability

# Bad - Hard to read
- name: Complex condition
  debug: msg="test"
  when: (var1 == "prod" and var2 > 10 and var3 is defined) or (var1 == "dev" and var4 | length > 0)

# Good - Use YAML list format for AND conditions
- name: Complex condition - readable
  debug:
    msg: "Production with high load"
  when:
    - environment == "production"
    - cpu_usage > 10
    - monitoring_enabled is defined

# Good - Use variables for complex logic
- name: Set deployment flag
  set_fact:
    should_deploy: >-
      {{
        (environment == 'production' and cpu_usage > 10 and monitoring_enabled is defined)
        or
        (environment == 'development' and debug_mode)
      }}

- name: Deploy application
  command: /opt/app/deploy.sh
  when: should_deploy

2. Performance Optimization

# Bad - Runs on every host even if not needed
- name: Install on specific host
  package:
    name: special-package
    state: present
  when: inventory_hostname == "server-01"

# Good - Use limit or delegate_to
- name: Install on specific host
  package:
    name: special-package
    state: present
  delegate_to: server-01
  run_once: true

# Good - Skip gathering facts if not needed
- name: Simple deployment
  hosts: all
  gather_facts: no
  tasks:
    - name: Copy file
      copy:
        src: app.conf
        dest: /etc/app/

3. Error Handling

# Bad - Silent failures
- name: Optional task
  command: /opt/scripts/optional.sh
  when: some_condition
  ignore_errors: yes

# Good - Explicit error handling
- name: Optional task with logging
  command: /opt/scripts/optional.sh
  register: optional_result
  failed_when:
    - optional_result.rc != 0
    - optional_result.rc != 2  # 2 means "already done"
  when: some_condition

- name: Log if failed
  debug:
    msg: "Optional task failed but continuing: {{ optional_result.stderr }}"
  when:
    - optional_result is defined
    - optional_result.rc != 0

4. Security Considerations

# Bad - Exposing sensitive data in when clause
- name: Check password
  debug:
    msg: "Password check"
  when: user_password == "secret123"

# Good - Use no_log and proper validation
- name: Validate credentials
  command: /opt/scripts/validate_creds.sh
  register: cred_check
  no_log: true
  failed_when: cred_check.rc != 0

- name: Proceed with authenticated task
  command: /opt/scripts/deploy.sh
  when: cred_check.rc == 0
  no_log: true

5. Testing Conditionals

# Use assert module to test conditions
- name: Validate environment before deployment
  assert:
    that:
      - environment in ["development", "staging", "production"]
      - ansible_distribution in ["Ubuntu", "CentOS", "RedHat"]
      - ansible_memory_mb.real.total >= 2048
    fail_msg: "Pre-deployment validation failed"
    success_msg: "Pre-deployment validation passed"

# Use debug with verbosity for testing
- name: Debug conditional logic
  debug:
    msg: "Would deploy: {{ should_deploy }}, Reason: {{ deploy_reason }}"
    verbosity: 1
  vars:
    should_deploy: "{{ environment == 'production' and health_check.status == 200 }}"
    deploy_reason: >-
      Environment: {{ environment }},
      Health: {{ health_check.status | default('unknown') }}

6. Common Pitfalls to Avoid

# Pitfall 1: Using when with include/import
# Bad - when applies to include, not tasks inside
- import_tasks: database.yml
  when: setup_database

# Good - Use when inside the included file or use include_tasks
- include_tasks: database.yml
  when: setup_database

# Pitfall 2: String/Boolean confusion
# Bad - String comparison
- name: Check boolean
  debug: msg="enabled"
  when: feature_enabled == "true"

# Good - Boolean comparison
- name: Check boolean
  debug: msg="enabled"
  when: feature_enabled | bool

# Pitfall 3: Undefined variable errors
# Bad - Will fail if var is undefined
- name: Use variable
  debug: msg="{{ my_var }}"
  when: my_var != ""

# Good - Check if defined first
- name: Use variable safely
  debug: msg="{{ my_var }}"
  when:
    - my_var is defined
    - my_var | length > 0

# Pitfall 4: Changed status in conditionals
# Bad - Task always runs after command
- name: Run command
  command: /bin/true
  register: result

- name: Run if changed
  debug: msg="changed"
  when: result is changed  # command always shows changed

# Good - Use changed_when to control
- name: Run command
  command: /bin/true
  register: result
  changed_when: false

- name: Run if changed
  debug: msg="changed"
  when: result is changed

Conclusion

Mastering Ansible conditionals enables you to create intelligent, adaptive playbooks that can handle complex scenarios across diverse infrastructure. Key takeaways:

  • Start Simple: Use basic when clauses and gradually build complexity
  • Use Facts: Leverage ansible_facts for OS, hardware, and network information
  • Test Thoroughly: Use --check mode and assert to validate conditions
  • Handle Errors: Use block/rescue/always for robust error handling
  • Keep It Readable: Break complex conditions into variables and use YAML list format
  • Be Explicit: Check for defined variables and use failed_when/changed_when appropriately

With these conditional techniques, your Ansible playbooks can intelligently adapt to different environments, handle edge cases gracefully, and provide robust automation across your infrastructure.

Next Steps: Learn about Ansible Variables and Precedence to master data flow in your playbooks.

Was this article helpful?

RS

About the Author: Ramesh Sundararamaiah

Red Hat Certified Architect

Ramesh is a Red Hat Certified Architect with extensive experience in enterprise Linux environments. He specializes in system administration, DevOps automation, and cloud infrastructure. Ramesh has helped organizations implement robust Linux solutions and optimize their IT operations for performance and reliability.

Expertise: Red Hat Enterprise Linux, CentOS, Ubuntu, Docker, Ansible, System Administration, DevOps

Add Comment