AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible serial: Rolling Updates and Batch Deployment Guide

By Luca Berton · Published 2024-01-01 · Category: installation

Complete guide to Ansible serial keyword. Implement rolling updates, batch deployments, canary releases, and zero-downtime strategies with max_fail_percentage.

The serial keyword controls how many hosts Ansible updates at once. Without it, Ansible runs on all hosts simultaneously — fine for configuration, dangerous for deployments. With serial, you get rolling updates, canary releases, and zero-downtime deployments.

Default Behavior (No serial)

# Runs on ALL 100 webservers simultaneously
- hosts: webservers
  tasks:
    - name: Restart nginx
      ansible.builtin.systemd:
        name: nginx
        state: restarted
# If something goes wrong, ALL servers are down at once

See also: Ansible troubleshooting - Error sanity

Basic serial Usage

Fixed Batch Size

# Update 5 hosts at a time
- hosts: webservers
  serial: 5
  tasks:
    - name: Deploy application
      ansible.builtin.include_role:
        name: deploy

Ansible processes hosts in batches of 5. If you have 20 hosts, it runs 4 batches sequentially.

Percentage

# Update 25% of hosts at a time
- hosts: webservers
  serial: "25%"
  tasks:
    - name: Deploy application
      ansible.builtin.include_role:
        name: deploy

One at a Time

# Safest: one host at a time
- hosts: webservers
  serial: 1
  tasks:
    - name: Restart critical service
      ansible.builtin.systemd:
        name: myapp
        state: restarted

Increasing Batch Sizes (Canary Pattern)

Start small to catch problems early, then increase batch size:

# Canary deployment: 1 → 5 → remaining
- hosts: webservers
  serial:
    - 1          # First: test on 1 host (canary)
    - 5          # Then: 5 hosts at a time
    - "100%"     # Finally: all remaining hosts
  tasks:
    - name: Deploy new version
      ansible.builtin.include_role:
        name: deploy

- name: Health check ansible.builtin.uri: url: "http://localhost:{{ app_port }}/health" retries: 5 delay: 3 register: health until: health.status == 200

With 50 hosts: batch 1 = 1 host, batch 2 = 5 hosts, batch 3 = 44 remaining hosts.

Percentage Ramp-Up

- hosts: webservers
  serial:
    - "5%"      # Canary: ~5% of fleet
    - "25%"     # Quarter of fleet
    - "100%"    # Everything else

See also: ansible-inventory-grapher: Visualize Ansible Inventory as Graph (Guide)

max_fail_percentage

Stop the entire deployment if too many hosts fail:

- hosts: webservers
  serial: 5
  max_fail_percentage: 10    # Stop if >10% of hosts fail
  tasks:
    - name: Deploy and verify
      ansible.builtin.include_role:
        name: deploy

Zero Tolerance

# Stop immediately on ANY failure
- hosts: webservers
  serial: 5
  max_fail_percentage: 0
  tasks:
    - name: Critical deployment
      ansible.builtin.include_role:
        name: deploy

Real-World Patterns

Zero-Downtime Web Deployment

---
- name: Rolling deployment with LB management
  hosts: webservers
  serial: 1
  max_fail_percentage: 0

pre_tasks: - name: Remove from load balancer community.general.haproxy: state: disabled host: "{{ inventory_hostname }}" backend: web_backend delegate_to: "{{ haproxy_host }}"

- name: Wait for connections to drain ansible.builtin.wait_for: port: "{{ app_port }}" state: drained timeout: 60

tasks: - name: Pull latest code ansible.builtin.git: repo: "{{ app_repo }}" dest: "{{ app_dir }}" version: "{{ deploy_version }}"

- name: Install dependencies ansible.builtin.command: npm install --production args: chdir: "{{ app_dir }}"

- name: Restart application ansible.builtin.systemd: name: myapp state: restarted

post_tasks: - name: Wait for health check ansible.builtin.uri: url: "http://localhost:{{ app_port }}/health" status_code: 200 retries: 10 delay: 5 register: health until: health.status == 200

- name: Re-enable in load balancer community.general.haproxy: state: enabled host: "{{ inventory_hostname }}" backend: web_backend delegate_to: "{{ haproxy_host }}"

- name: Wait for traffic to flow ansible.builtin.pause: seconds: 10

Database Migration with Rolling App Restart

---
# Play 1: Migrate database (once)
- name: Run database migration
  hosts: webservers[0]    # Only first host
  tasks:
    - name: Run migration
      ansible.builtin.command: "{{ app_dir }}/manage.py migrate"
      register: migrate
      changed_when: "'Applying' in migrate.stdout"

# Play 2: Rolling restart of app servers - name: Rolling restart hosts: webservers serial: 2 max_fail_percentage: 0 tasks: - name: Restart application ansible.builtin.systemd: name: myapp state: restarted

- name: Wait for ready ansible.builtin.wait_for: port: "{{ app_port }}" delay: 5 timeout: 30

Canary with Automatic Rollback

- name: Canary deployment
  hosts: webservers
  serial:
    - 1
    - "100%"
  max_fail_percentage: 0

tasks: - name: Deploy new version ansible.builtin.include_role: name: deploy

- name: Run smoke tests ansible.builtin.uri: url: "http://localhost:{{ app_port }}{{ item }}" status_code: 200 loop: - /health - /api/status - /

rescue: - name: Rollback on failure ansible.builtin.include_role: name: deploy vars: deploy_version: "{{ previous_version }}"

- name: Notify team of rollback ansible.builtin.uri: url: "{{ slack_webhook }}" method: POST body: '{"text": "⚠️ Rollback on {{ inventory_hostname }}: {{ ansible_failed_result.msg }}"}' body_format: json delegate_to: localhost

See also: Ansible Playbook Structure: Anatomy, Best Practices & Examples (2026)

serial vs forks

| Setting | What It Controls | |---------|-----------------| | serial | Batch size — how many hosts per batch (sequential batches) | | forks | Parallelism — how many hosts run simultaneously within a batch |

# ansible.cfg
[defaults]
forks = 20    # Up to 20 hosts in parallel
# With serial: 10 and forks: 20
# → 10 hosts per batch, all 10 run in parallel (forks > serial)
# → Next batch of 10 only starts after first batch completes

# With serial: 50 and forks: 20 # → 50 hosts per batch, but only 20 run in parallel at a time

FAQ

What does serial do in Ansible?

The serial keyword limits how many hosts are processed in each batch. Without it, Ansible runs on all hosts in the play simultaneously. With serial: 5, Ansible processes hosts in groups of 5, completing each batch before starting the next.

How do I do a rolling update with Ansible?

Set serial: 1 (or a small number) on your play to update hosts one at a time. Combine with load balancer management (drain → update → health check → re-enable) for zero-downtime deployments.

What is the difference between serial and forks?

serial controls batch size (how many hosts per round). forks controls parallelism within each batch (how many run simultaneously). serial affects execution order; forks affects speed.

How do I implement a canary deployment?

Use a list for serial: serial: [1, 5, "100%"]. This deploys to 1 host first (canary), then 5, then all remaining. Combined with max_fail_percentage: 0, any failure stops the rollout before affecting more hosts.

What happens when a host fails with serial?

If a host fails and max_fail_percentage is exceeded, Ansible stops processing remaining batches. Hosts already in the current batch continue, but no new batches start.

Conclusion

serial: 1 — Safest, one at a time (use for critical services) • serial: N — Fixed batch size (balance speed vs safety) • serial: [1, 5, "100%"] — Canary pattern (catch problems early) • max_fail_percentage: 0 — Stop on any failure • Always pair with health checks in post_tasks

Related Articles

Ansible delegate_to: Run Tasks on Different HostsAnsible block, rescue, always: Error HandlingAnsible wait_for: Wait for ConditionsAnsible Playbook Structure & Best Practices

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home