Introduction
ansible.builtin.lineinfile manages individual lines in text files — add a line, replace a line matching a pattern, or remove lines. It's the go-to module for small, targeted file edits without rewriting the entire file with template or copy.
Parameters
| Parameter | Default | Description |
|---|---|---|
path | (required) | File to modify |
line | — | Line content to add/ensure |
regexp | — | Regex to find existing line |
state | present | present to add/ensure, absent to remove |
insertafter | EOF | Insert after matching line/EOF/BOF |
insertbefore | — | Insert before matching line/BOF |
backrefs | false | Use regex backreferences in line |
create | false | Create file if it doesn't exist |
backup | false | Create backup before editing |
mode | — | File permissions |
owner | — | File owner |
validate | — | Validation command |
firstmatch | false | Replace only the first match |
Add a Line
# Add line at end of file (default)
- name: Add DNS server
ansible.builtin.lineinfile:
path: /etc/resolv.conf
line: "nameserver 8.8.8.8"
# Add line and create file if missing
- name: Ensure config exists
ansible.builtin.lineinfile:
path: /etc/myapp/config
line: "max_connections=100"
create: true
mode: "0644"
Ensure a Line Exists (Idempotent)
# If line exists → no change. If missing → adds it.
- name: Ensure NOPASSWD for deploy user
ansible.builtin.lineinfile:
path: /etc/sudoers
line: "deploy ALL=(ALL) NOPASSWD: ALL"
validate: "visudo -cf %s"
# Ensure SSH setting
- name: Disable root login
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?PermitRootLogin" # Match commented or uncommented
line: "PermitRootLogin no"
notify: Restart sshd
Replace a Line (regexp + line)
When you specify both regexp and line:
- If a line matches
regexp→ replace it withline - If no line matches → add
lineat end of file
# Change SSH port
- name: Set SSH port
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?Port "
line: "Port 2222"
notify: Restart sshd
# Change timezone
- name: Set timezone
ansible.builtin.lineinfile:
path: /etc/environment
regexp: "^TZ="
line: "TZ=UTC"
# Update config value
- name: Set max connections
ansible.builtin.lineinfile:
path: /etc/postgresql/16/main/postgresql.conf
regexp: "^#?max_connections"
line: "max_connections = 200"
notify: Restart postgresql
Remove a Line
# Remove by exact line
- name: Remove old DNS
ansible.builtin.lineinfile:
path: /etc/resolv.conf
line: "nameserver 10.0.0.1"
state: absent
# Remove by pattern
- name: Remove all comments starting with #TEMP
ansible.builtin.lineinfile:
path: /etc/myapp.conf
regexp: "^#TEMP"
state: absent
Insert After/Before
# Insert after a specific line
- name: Add setting after [mysqld] section
ansible.builtin.lineinfile:
path: /etc/mysql/my.cnf
insertafter: "^\\[mysqld\\]"
line: "max_connections = 500"
# Insert before a specific line
- name: Add comment before setting
ansible.builtin.lineinfile:
path: /etc/nginx/nginx.conf
insertbefore: "^http {"
line: "# Managed by Ansible"
# Insert at beginning of file
- name: Add shebang
ansible.builtin.lineinfile:
path: /opt/scripts/deploy.sh
insertbefore: BOF
line: "#!/bin/bash"
Backreferences
Capture groups from regexp and use them in line:
# Change a value while keeping the key
- name: Update listen port
ansible.builtin.lineinfile:
path: /etc/nginx/nginx.conf
regexp: "^(\\s*listen\\s+)\\d+;"
line: "\\1{{ nginx_port }};"
backrefs: true
# Uncomment a line (remove leading #)
- name: Enable setting
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#(PasswordAuthentication.*)"
line: "\\1"
backrefs: true
⚠️ With backrefs: true, if the regex doesn't match, the line is NOT added (unlike default behavior).
Practical Examples
SSH Hardening
---
- name: Harden SSH config
hosts: all
become: true
tasks:
- name: Disable root login
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?PermitRootLogin"
line: "PermitRootLogin no"
- name: Disable password auth
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?PasswordAuthentication"
line: "PasswordAuthentication no"
- name: Set max auth tries
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?MaxAuthTries"
line: "MaxAuthTries 3"
- name: Disable empty passwords
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?PermitEmptyPasswords"
line: "PermitEmptyPasswords no"
notify: Restart sshd
handlers:
- name: Restart sshd
ansible.builtin.service:
name: sshd
state: restarted
Manage /etc/hosts
- name: Add host entries
ansible.builtin.lineinfile:
path: /etc/hosts
regexp: ".*{{ item.name }}$"
line: "{{ item.ip }} {{ item.name }}"
loop:
- { ip: "10.0.1.10", name: "db-master" }
- { ip: "10.0.1.11", name: "db-replica" }
- { ip: "10.0.1.20", name: "cache-01" }
Environment Variables
- name: Set environment variables
ansible.builtin.lineinfile:
path: /etc/environment
regexp: "^{{ item.key }}="
line: "{{ item.key }}={{ item.value }}"
loop:
- { key: LANG, value: "en_US.UTF-8" }
- { key: APP_ENV, value: production }
- { key: JAVA_HOME, value: /usr/lib/jvm/java-17 }
Kernel Parameters (sysctl.conf)
- name: Set kernel parameters
ansible.builtin.lineinfile:
path: /etc/sysctl.conf
regexp: "^#?{{ item.key }}"
line: "{{ item.key }} = {{ item.value }}"
loop:
- { key: "net.ipv4.ip_forward", value: "1" }
- { key: "vm.swappiness", value: "10" }
- { key: "net.core.somaxconn", value: "65535" }
notify: Reload sysctl
lineinfile vs replace vs template
| Module | Use When |
|---|---|
lineinfile | Managing one line (add/replace/remove) |
replace | Replacing text within a line or multiple lines matching a pattern |
template | Managing the entire file with Jinja2 |
blockinfile | Managing a block of lines (multi-line insert) |
# lineinfile — one line
- lineinfile:
path: /etc/config
regexp: "^port="
line: "port=8080"
# replace — part of a line or multiple matches
- replace:
path: /etc/config
regexp: "port=\\d+"
replace: "port=8080"
# blockinfile — multiple lines
- blockinfile:
path: /etc/config
block: |
# App settings
port=8080
host=0.0.0.0
# template — entire file
- template:
src: config.j2
dest: /etc/config
Troubleshooting
Line Added Multiple Times
Your regexp doesn't match the line you're adding. The module checks regexp for existing matches — if it doesn't find one, it adds line:
# WRONG — regexp won't match the line being added
- lineinfile:
path: /etc/config
regexp: "^port" # Matches "port=..."
line: "listen_port=8080" # Doesn't start with "port"!
# Result: adds "listen_port=8080" every run
# RIGHT — regexp should match what you're adding
- lineinfile:
path: /etc/config
regexp: "^listen_port="
line: "listen_port=8080"
Trailing Whitespace Issues
# Be specific with regexp to avoid whitespace mismatches
regexp: "^\\s*PermitRootLogin\\s+"
Related Articles
- Ansible copy Module
- Ansible template Module
- Ansible blockinfile Module
- Ansible replace Module
- Ansible file Module
Conclusion
lineinfile is perfect for small, surgical file edits — one setting at a time. Use regexp + line to find-and-replace, state: absent to remove, and insertafter/insertbefore for precise placement. For whole-file management, use template. For multi-line blocks, use blockinfile. Always use validate for critical files (sudoers, sshd_config) and backup: true in production.