Introduction
GRUB (GRand Unified Bootloader) controls which kernel boots, with what parameters, and how the boot menu behaves. Ansible automates GRUB configuration — set kernel command-line parameters, change default boot entries, configure timeouts, add password protection, and manage multi-kernel environments. Every change requires regenerating grub.cfg, which Ansible handles automatically.
How GRUB Config Works
/etc/default/grub ← Your settings (Ansible edits this)
│
▼
update-grub / grub2-mkconfig ← Regenerates grub.cfg
│
▼
/boot/grub/grub.cfg ← Actual boot config (never edit directly)
Set Kernel Parameters
---
- name: Configure GRUB kernel parameters
hosts: all
become: true
vars:
grub_cmdline_defaults:
- quiet
- splash
grub_cmdline_extras:
- transparent_hugepage=never
- elevator=noop
- intel_iommu=on
tasks:
- name: Set GRUB_CMDLINE_LINUX_DEFAULT
ansible.builtin.lineinfile:
path: /etc/default/grub
regexp: '^GRUB_CMDLINE_LINUX_DEFAULT='
line: 'GRUB_CMDLINE_LINUX_DEFAULT="{{ (grub_cmdline_defaults + grub_cmdline_extras) | join(" ") }}"'
notify: update grub
handlers:
- name: update grub
ansible.builtin.command: >
{{ 'update-grub' if ansible_os_family == 'Debian' else 'grub2-mkconfig -o /boot/grub2/grub.cfg' }}
changed_when: true
Add/Remove Individual Parameters
- name: Add kernel parameter
ansible.builtin.replace:
path: /etc/default/grub
regexp: '(GRUB_CMDLINE_LINUX_DEFAULT="[^"]*)'
replace: '\1 transparent_hugepage=never'
when: >
'transparent_hugepage=never' not in
lookup('file', '/etc/default/grub')
notify: update grub
- name: Remove kernel parameter
ansible.builtin.replace:
path: /etc/default/grub
regexp: '\s*transparent_hugepage=\S*'
replace: ''
notify: update grub
Configure Boot Timeout
- name: Set GRUB timeout
ansible.builtin.lineinfile:
path: /etc/default/grub
regexp: '^GRUB_TIMEOUT='
line: 'GRUB_TIMEOUT=5'
notify: update grub
- name: Set timeout style (hidden for fast boot)
ansible.builtin.lineinfile:
path: /etc/default/grub
regexp: '^GRUB_TIMEOUT_STYLE='
line: 'GRUB_TIMEOUT_STYLE=hidden'
notify: update grub
Default Boot Entry
- name: Set default kernel
ansible.builtin.lineinfile:
path: /etc/default/grub
regexp: '^GRUB_DEFAULT='
line: 'GRUB_DEFAULT=0'
notify: update grub
# Boot previous selection
- name: Save last booted entry
ansible.builtin.lineinfile:
path: /etc/default/grub
regexp: '^GRUB_DEFAULT='
line: 'GRUB_DEFAULT=saved'
notify: update grub
- name: Enable savedefault
ansible.builtin.lineinfile:
path: /etc/default/grub
regexp: '^GRUB_SAVEDEFAULT='
line: 'GRUB_SAVEDEFAULT=true'
notify: update grub
Security Parameters
- name: Configure security-focused kernel parameters
ansible.builtin.lineinfile:
path: /etc/default/grub
regexp: '^GRUB_CMDLINE_LINUX='
line: 'GRUB_CMDLINE_LINUX="audit=1 apparmor=1 security=apparmor page_poison=1 slab_nomerge vsyscall=none"'
notify: update grub
GRUB Password Protection
- name: Generate GRUB password hash
ansible.builtin.command: >
grub-mkpasswd-pbkdf2 --iteration-count 10000
args:
stdin: "{{ vault_grub_password }}\n{{ vault_grub_password }}"
register: grub_password_hash
changed_when: false
no_log: true
- name: Deploy GRUB password config
ansible.builtin.copy:
dest: /etc/grub.d/01_password
content: |
#!/bin/sh
cat <<EOF
set superusers="admin"
password_pbkdf2 admin {{ grub_password_hash.stdout | regex_search('grub\.pbkdf2\.sha512\..*') }}
EOF
mode: '0755'
notify: update grub
Disable Console Output (Headless Servers)
- name: Configure serial console for headless servers
ansible.builtin.blockinfile:
path: /etc/default/grub
block: |
GRUB_TERMINAL="serial console"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"
notify: update grub
- name: Add console to kernel cmdline
ansible.builtin.lineinfile:
path: /etc/default/grub
regexp: '^GRUB_CMDLINE_LINUX='
line: 'GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200n8"'
notify: update grub
Multi-Kernel Management
- name: List installed kernels
ansible.builtin.shell: >
grep -oP 'menuentry .+?(?=\x27)' /boot/grub/grub.cfg || true
register: kernel_list
changed_when: false
- name: Display available kernels
ansible.builtin.debug:
var: kernel_list.stdout_lines
- name: Pin specific kernel version
ansible.builtin.lineinfile:
path: /etc/default/grub
regexp: '^GRUB_DEFAULT='
line: 'GRUB_DEFAULT="Advanced options for Ubuntu>Ubuntu, with Linux 6.8.0-45-generic"'
notify: update grub
Complete GRUB Configuration Role
# roles/grub/defaults/main.yml
grub_timeout: 5
grub_timeout_style: hidden
grub_default: 0
grub_cmdline_linux_default:
- quiet
- splash
grub_cmdline_linux: []
grub_disable_recovery: true
grub_terminal: console
# roles/grub/tasks/main.yml
- name: Configure GRUB settings
ansible.builtin.template:
src: grub.j2
dest: /etc/default/grub
mode: '0644'
backup: true
notify: update grub
- name: Update GRUB
ansible.builtin.meta: flush_handlers
# roles/grub/templates/grub.j2
# Managed by Ansible — do not edit manually
GRUB_DEFAULT={{ grub_default }}
GRUB_TIMEOUT={{ grub_timeout }}
GRUB_TIMEOUT_STYLE={{ grub_timeout_style }}
GRUB_DISTRIBUTOR=`lsb_release -i -s 2>/dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="{{ grub_cmdline_linux_default | join(' ') }}"
GRUB_CMDLINE_LINUX="{{ grub_cmdline_linux | join(' ') }}"
GRUB_DISABLE_RECOVERY={{ grub_disable_recovery | lower }}
GRUB_TERMINAL={{ grub_terminal }}
Troubleshooting
System Won't Boot After Change
GRUB changes can make systems unbootable. Always:
- name: Backup current grub.cfg
ansible.builtin.copy:
src: /boot/grub/grub.cfg
dest: /boot/grub/grub.cfg.bak
remote_src: true
mode: '0600'
Verify Parameters Applied
- name: Check current kernel cmdline
ansible.builtin.command: cat /proc/cmdline
register: current_cmdline
changed_when: false
- name: Show active parameters
ansible.builtin.debug:
var: current_cmdline.stdout
Related Articles
Conclusion
GRUB configuration with Ansible follows a simple pattern: edit /etc/default/grub with lineinfile, replace, or template, then regenerate grub.cfg with a handler. Always backup before changes — GRUB errors can make systems unbootable. Use lineinfile for individual parameter tweaks and template for full configuration management across your fleet.