Lineinfile and Blockinfile Module — Surgical Edits
copy and template both replace an entire file’s content. That’s fine when you own the whole file — but plenty of real config files, like /etc/sysctl.conf or /etc/ssh/sshd_config, have many settings you don’t want to touch at all, and you just need to ensure one specific line or section is correct. Replacing the whole thing would mean reproducing every other line yourself, fragile and risky. lineinfile and blockinfile edit a file surgically instead, leaving everything else untouched.
Why Not Just Use template For Everything?
Because template requires knowing — and reproducing exactly — the entire content of a file. For a system config file with dozens of settings you have no reason to manage, that’s both unnecessary work and a real risk of silently discarding legitimate content you didn’t know was there. lineinfile/blockinfile solve a genuinely different problem: managing just the one piece you actually care about.
lineinfile: Ensuring A Specific Line Exists
- name: Ensure a specific line exists
ansible.builtin.lineinfile:
path: /etc/sysctl.conf
line: "net.ipv4.ip_forward = 1"Without regexp, lineinfile checks whether this exact line already exists anywhere in the file — if so, it does nothing; if not, it appends it to the end. Simple, but limited: a similar-but-not-identical existing line (different spacing, a different value already set) wouldn’t be recognized as “the same setting,” and you’d end up with two conflicting lines instead of one corrected one.
The regexp Parameter: Finding The Line To Replace
- name: Ensure ip_forward is set correctly, replacing any existing setting
ansible.builtin.lineinfile:
path: /etc/sysctl.conf
regexp: '^net\.ipv4\.ip_forward'
line: "net.ipv4.ip_forward = 1"Now: if a line matching the pattern already exists — regardless of its current value — it gets replaced with the exact line: content. If nothing matches, the new line is appended, same as before. This correctly handles the case of an existing-but-wrong setting, updating it in place rather than creating a duplicate.
blockinfile: Managing A Whole Block Of Lines
For managing several related lines as one logical unit, rather than one lineinfile call at a time:
- name: Ensure a block of SSH settings exists
ansible.builtin.blockinfile:
path: /etc/ssh/sshd_config
block: |
Match User deploy
PasswordAuthentication no
PubkeyAuthentication yesAnsible automatically wraps this content with its own marker comments in the actual file, letting it find, update, or remove exactly that managed section on later runs, without needing a complex multi-line regex to identify it.
A Realistic Example: Adding One Setting To An Existing Config
Combined with a handler, using state: reloaded from the previous chapter — reinforcing that lesson directly:
- name: Ensure a custom nginx worker_processes setting
ansible.builtin.lineinfile:
path: /etc/nginx/nginx.conf
regexp: '^worker_processes'
line: "worker_processes auto;"
notify: Reload nginx
handlers:
- name: Reload nginx
ansible.builtin.systemd:
name: nginx
state: reloadedA Different Idempotency Model
Worth being explicit about, since it’s a genuinely different mental model than copy/template. Those two compare the entire rendered or copied content against what’s already at the destination. lineinfile/blockinfile compare only their own specific line or block — the rest of the file is completely irrelevant to their change-detection. A lineinfile task reports changed only if the one line it’s responsible for actually needed to change, regardless of anything else happening elsewhere in that same file.
A Gotcha: A Loose regexp Can Match More Than Intended
An under-specified pattern can match lines you never meant to touch. regexp: 'port', with no anchoring, could match port = 8080, but also admin_port = 9090, or even a comment line mentioning the word “port” — any of these could unintentionally be treated as the line to replace, with a genuinely surprising result depending on which one actually gets modified.
# Too loose
regexp: 'port'
# Anchored and specific
regexp: '^port\s*='The anchored version matches only a line that genuinely starts with port, followed by optional whitespace and an = — no ambiguity about which line it’s targeting. Write regexp patterns as specific and anchored as you reasonably can, every time.
Best Practices
- Use
lineinfile/blockinfilefor surgical edits to files you don’t fully own or control, rather than replacing the whole thing withtemplate. - Always anchor
regexppatterns as specifically as possible (^for start of line, escaped literal characters) to avoid accidentally matching more than the one setting you actually intend to manage. - Use
blockinfile, not several separatelineinfilecalls, for more than a couple of related lines managed together as one logical unit. - Remember the idempotency here is narrowly scoped to just the managed line or block — a completely different comparison than
copy/template’s whole-file check.