Skip to content

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 yes

Ansible 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: reloaded

A 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/blockinfile for surgical edits to files you don’t fully own or control, rather than replacing the whole thing with template.
  • Always anchor regexp patterns 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 separate lineinfile calls, 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.
Last updated on