Skip to main content
RunBook Academy

AnsibleXL · Patch and Reboot ManagementPatch and Reboot Management

The reboot module actual contract

Advanced⏱ ~26 min🧪 Lab requiredansible-playbookansible-doc

What you'll learn

  • Name every parameter of ansible.builtin.reboot and the default each carries
  • Explain how boot_time_command proves a boot occurred rather than inferring it from a lost connection
  • Budget the true worst-case duration of a reboot task from reboot_timeout
  • Use wait_for_connection for a reboot the module did not initiate

Prerequisites

Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11

Not yet marked complete on this device.

ansible.builtin.reboot is a two-word task that takes a production host out of service and waits for it to come back. It is worth knowing what every one of its parameters does before you write the two words.

The whole table below was read from ansible-doc ansible.builtin.reboot in a virtualenv pinned to ansible-core 2.21.3. Module defaults change between versions; the version you have installed is the only one whose defaults are true for you.

Read-only / Safethe module contract, read rather than assumed
$ ansible-doc ansible.builtin.reboot
   boot_time_command
      default: cat /proc/sys/kernel/random/boot_id
 connect_timeout
      default: null       (falls back to the connection plugin default)
 msg
      default: Reboot initiated by Ansible
 post_reboot_delay
      default: 0
 pre_reboot_delay
      default: 0
 reboot_command
      default: '[determined based on target OS]'
 reboot_timeout
      default: 600
 search_paths
      default: [/sbin, /bin, /usr/sbin, /usr/bin, /usr/local/sbin]
 test_command
      default: whoami

ATTRIBUTES:
 async: none          check_mode: full
 diff_mode: none      platform: posix

RETURN VALUES:
 elapsed   int   seconds waited for the system to be rebooted
 rebooted  bool  true if the machine was rebooted

The parameter that does the actual proving

boot_time_command is the interesting one, and it is the reason this module is better than a shell: reboot followed by a sleep.

Its default is cat /proc/sys/kernel/random/boot_id. That file holds a random UUID generated by the kernel at boot and stable for the lifetime of that boot. The module reads it before issuing the reboot, and polls for it after. When the value changes, a boot demonstrably happened.

That is a different claim from “the connection dropped and came back”. A connection drop can be a network blip, a restarted sshd, a saturated link, or a firewall reload. None of those are a reboot, and a module that inferred a reboot from a dropped connection would report success on all of them.

The documentation attaches a condition to changing it: “Setting this to a command that has different output each time it is run will cause the task to fail.” The command must be stable within a boot and different across boots. date fails. uptime fails. cat /proc/sys/kernel/random/boot_id is exactly right, which is why it is the default.

reboot_timeout is evaluated twice

The default is 600 seconds, and the sentence next to it is the one people miss:

This timeout is evaluated separately for both reboot verification and test command success so the maximum execution time for the module is twice this amount.

So reboot_timeout: 600 is a task that can occupy twenty minutes before it fails, not ten. The two phases are:

  1. Reboot verification — wait until boot_time_command returns a different value from the one recorded before the reboot.
  2. Test command success — wait until test_command (default whoami) runs successfully on the host.

Both get the full timeout independently.

The two delays, and how they differ

pre_reboot_delay and post_reboot_delay both default to 0 and are not symmetric.

pre_reboot_delay is passed as a parameter to the reboot command itself. The documentation records a platform-specific conversion: “On Linux, macOS and OpenBSD, this is converted to minutes and rounded down. If less than 60, it will be set to 0. On Solaris and FreeBSD, this will be seconds.”

That is a genuine trap. pre_reboot_delay: 30 on Linux is zero, not thirty seconds, because 30 seconds rounds down to 0 minutes. If you want a delay that a logged-in user could notice, the smallest meaningful value on Linux is 60.

post_reboot_delay is a controller-side wait after the reboot command succeeded, before validation begins. The documentation describes its purpose precisely: “This is useful if you want wait for something to settle despite your connection already working.”

That sentence names a real problem. sshd frequently accepts connections well before the rest of the boot has finished — before mounts are complete, before the network is fully configured, before services have started. test_command succeeding proves the host answers, not that it is ready. post_reboot_delay buys a fixed pause for the remainder to settle.

Service impact possiblea reboot task with deliberate values
- name: Reboot to activate the new kernel
ansible.builtin.reboot:
  msg: 'Rebooting for the {{ patch_window_id }} maintenance window'
  reboot_timeout: 900
  post_reboot_delay: 30
  test_command: 'systemctl is-system-running --wait'
register: reboot_result

- name: Record how long the host took to return
ansible.builtin.debug:
  msg: >-
    {{ inventory_hostname }} rebooted={{ reboot_result.rebooted }}
    elapsed={{ reboot_result.elapsed }}s
    kernel_before={{ ansible_facts.kernel }}

post_reboot_delay: 30 is a fixed cost per host and it is the honest kind: thirty seconds of doing nothing, multiplied by the fleet, in exchange for not validating a host that is halfway through its boot.

test_command, and making it mean something

The default test_command is whoami. It proves the transport works and a command can be executed. That is the module’s job and it is a deliberately minimal bar.

It is not a health check. whoami succeeds on a host whose filesystems failed to mount, whose application did not start, and which came up on the wrong kernel.

Replacing it with something stronger — systemctl is-system-running --wait is the usual choice — moves part of the validation inside the module. That has one real advantage: it happens inside reboot_timeout, so a host that answers but never reaches a running system state fails the reboot task rather than passing it and failing something later.

search_paths and the ignored PATH

The module locates the shutdown command by searching search_paths, which defaults to /sbin, /bin, /usr/sbin, /usr/bin and /usr/local/sbin. The documentation is emphatic that only those paths are searched, and the notes repeat it: “PATH is ignored on the remote node when searching for the shutdown command.”

This is why a host with shutdown somewhere unusual fails a reboot task with a “cannot find shutdown” error even though which shutdown works perfectly when you SSH in and look. Your interactive PATH is not consulted.

The module’s own example covers the commonest real instance: a host with molly-guard installed, which interposes its own shutdown in /lib/molly-guard.

Service impact possiblea host whose shutdown lives somewhere else
- name: Reboot a host with molly-guard installed
ansible.builtin.reboot:
  search_paths:
    - /lib/molly-guard
    - /sbin
    - /usr/sbin

reboot_command is the alternative: give the module a complete command to run instead of composing one. It carries a consequence stated directly in the documentation — “This will cause pre_reboot_delay, post_reboot_delay, and msg to be ignored.”

Losing post_reboot_delay is the one that catches people, because it is the settle-time parameter and its absence is invisible until a validation step starts failing intermittently.

Reboots the module did not initiate

ansible.builtin.reboot handles the case where Ansible issues the reboot. Sometimes something else does: a dnf transaction that triggers a restart, a cloud API call, a firmware update, a colleague.

ansible.builtin.wait_for_connection is the module for waiting on a host to become reachable again, and its defaults are its own:

ParameterDefault
timeout600
connect_timeout5
sleep1
delay0
Read-only / Safewaiting for a host somebody else rebooted
- name: Trigger the firmware update, which reboots the host itself
ansible.builtin.command:
  argv: [/usr/local/sbin/apply-firmware, '--reboot']
register: firmware
changed_when: firmware.rc == 0
async: 30
poll: 0

- name: Wait for the host to go away and come back
ansible.builtin.wait_for_connection:
  delay: 60
  timeout: 900
  sleep: 5

Do not confuse wait_for_connection with wait_for. wait_for polls a port or a file and defaults to host: 127.0.0.1 — meaning that unless you override it, it polls a port on the host it is running on, and run from the controller with delegate_to it polls the controller. wait_for_connection uses the actual connection plugin and the ping module, which is the question you are asking after a reboot.

Knowledge check

Knowledge check · 4 questions

  1. Q1. How does ansible.builtin.reboot establish that a reboot actually happened?

  2. Q2. A patch play sets reboot_timeout: 900. What is the longest the reboot task can occupy before failing?

  3. Q3. Which statements about reboot module parameters are correct on ansible-core 2.21.3? Select all that apply.

  4. Q4. wait_for_connection without a delay can report success before the host has actually gone down, which is why a delay is needed when waiting on a reboot that Ansible did not initiate.

Passing score: 75%. Answers are checked in this browser.