Ansible lineinfile Module Cookbook: 25 Practical Examples
By Luca Berton · Published 2024-01-01 · Category: linux-administration
25 practical Ansible lineinfile examples. Add, modify, remove, and validate lines in configuration files.
lineinfile ensures a specific line exists (or doesn't exist) in a file. It's Ansible's surgical tool for in-place file edits — one line at a time.
Basic Operations
1. Add a Line
- name: Add DNS server
ansible.builtin.lineinfile:
path: /etc/resolv.conf
line: "nameserver 8.8.8.8"
state: present
2. Add a Line If Not Present
- name: Ensure PATH export exists
ansible.builtin.lineinfile:
path: /etc/profile
line: 'export PATH="/opt/bin:$PATH"'
state: present
# Idempotent — skips if line already exists
3. Remove a Line
- name: Remove old DNS entry
ansible.builtin.lineinfile:
path: /etc/resolv.conf
line: "nameserver 10.0.0.1"
state: absent
4. Replace a Line by Regex
- name: Set max open files
ansible.builtin.lineinfile:
path: /etc/security/limits.conf
regexp: '^\*\s+soft\s+nofile'
line: "* soft nofile 65536"
5. Uncomment a Line
- name: Enable IP forwarding
ansible.builtin.lineinfile:
path: /etc/sysctl.conf
regexp: '^#?\s*net\.ipv4\.ip_forward'
line: "net.ipv4.ip_forward = 1"
See also: Ansible lineinfile Module: Add, Replace, Remove Lines in Files (Complete Guide)
Placement Control
6. Insert After a Line
- name: Add config after [global] section
ansible.builtin.lineinfile:
path: /etc/samba/smb.conf
insertafter: '^\[global\]'
line: " server string = Managed by Ansible"
7. Insert Before a Line
- name: Add comment before setting
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
insertbefore: '^PermitRootLogin'
line: "# Security: Restrict root login"
8. Insert at Beginning of File
- name: Add managed-by header
ansible.builtin.lineinfile:
path: /etc/myapp/config.conf
insertbefore: BOF
line: "# Managed by Ansible — do not edit manually"
9. Insert at End of File
- name: Append to end
ansible.builtin.lineinfile:
path: /etc/hosts
insertafter: EOF
line: "10.0.1.50 app.internal"
SSH Configuration
10. Disable Root Login
- name: Disable SSH root login
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?\s*PermitRootLogin'
line: "PermitRootLogin no"
validate: 'sshd -t -f %s'
notify: restart sshd
11. Disable Password Authentication
- name: SSH key-only authentication
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?\s*PasswordAuthentication'
line: "PasswordAuthentication no"
validate: 'sshd -t -f %s'
notify: restart sshd
12. Change SSH Port
- name: Set SSH port
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?\s*Port\s'
line: "Port {{ ssh_port }}"
validate: 'sshd -t -f %s'
notify: restart sshd
See also: Ansible Development: Write Custom Modules, Plugins & Collections
System Configuration
13. Set Hostname in /etc/hosts
- name: Set hostname entry
ansible.builtin.lineinfile:
path: /etc/hosts
regexp: '^127\.0\.1\.1'
line: "127.0.1.1 {{ ansible_hostname }}.{{ domain }} {{ ansible_hostname }}"
14. Configure Sysctl Parameter
- name: Set kernel parameters
ansible.builtin.lineinfile:
path: /etc/sysctl.conf
regexp: '^{{ item.key }}\s*='
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
15. Set Timezone
- name: Configure timezone in profile
ansible.builtin.lineinfile:
path: /etc/environment
regexp: '^TZ='
line: "TZ={{ timezone }}"
Regex with Backreferences
16. Update a Value Preserving Format
- name: Update max connections (preserve comment)
ansible.builtin.lineinfile:
path: /etc/postgresql/16/main/postgresql.conf
regexp: '^(#?\s*max_connections\s*=\s*)\d+'
line: '\g<1>200'
backrefs: true
17. Update Version Number in Config
- name: Update app version
ansible.builtin.lineinfile:
path: /opt/app/VERSION
regexp: '^version:\s*.*'
line: "version: {{ new_version }}"
See also: Ansible on Debian 11 Bullseye: OpenSSH Hardening Complete Guide
Multiple Lines with Loop
18. Add Multiple Hosts Entries
- name: Add internal hosts
ansible.builtin.lineinfile:
path: /etc/hosts
regexp: '.*\s{{ item.name }}$'
line: "{{ item.ip }} {{ item.name }}"
loop:
- { ip: "10.0.1.10", name: "web01.internal" }
- { ip: "10.0.1.20", name: "db01.internal" }
- { ip: "10.0.1.30", name: "cache01.internal" }
File Creation
19. Create File If Missing
- name: Ensure config exists with default
ansible.builtin.lineinfile:
path: /etc/myapp/overrides.conf
line: "# Custom overrides"
create: true
owner: myapp
group: myapp
mode: '0644'
blockinfile: Multi-Line Blocks
20. Add a Block of Lines
- name: Add firewall rules block
ansible.builtin.blockinfile:
path: /etc/iptables/rules.v4
marker: "# {mark} ANSIBLE MANAGED - app rules"
block: |
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT
-A INPUT -p tcp --dport 8080 -s 10.0.0.0/8 -j ACCEPT
21. Add SSH Config Block
- name: Configure SSH for bastion
ansible.builtin.blockinfile:
path: /home/deploy/.ssh/config
marker: "# {mark} ANSIBLE MANAGED - bastion"
block: |
Host bastion
HostName bastion.example.com
User ubuntu
IdentityFile ~/.ssh/bastion_key
Host 10.0.*.*
ProxyJump bastion
User deploy
create: true
owner: deploy
group: deploy
mode: '0600'
22. Update a Block
# blockinfile replaces content between markers on re-run
- name: Update allowed IPs
ansible.builtin.blockinfile:
path: /etc/nginx/conf.d/allowed-ips.conf
marker: "# {mark} ANSIBLE MANAGED BLOCK"
block: |
{% for ip in allowed_ips %}
allow {{ ip }};
{% endfor %}
deny all;
replace Module: Regex Replace
23. Replace All Occurrences
- name: Update all references to old domain
ansible.builtin.replace:
path: /etc/myapp/config.yml
regexp: 'old\.example\.com'
replace: 'new.example.com'
24. Comment Out a Section
- name: Comment out debug settings
ansible.builtin.replace:
path: /etc/myapp/config.conf
regexp: '^(debug_.+=.+)$'
replace: '# \1'
Validation
25. Validate Before Writing
- name: Edit sudoers safely
ansible.builtin.lineinfile:
path: /etc/sudoers
regexp: '^deploy'
line: "deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl"
validate: 'visudo -cf %s'
lineinfile vs blockinfile vs replace vs template
| Module | Use When | |--------|----------| | lineinfile | Change a single line | | blockinfile | Add/update a multi-line block | | replace | Regex replace across entire file | | template | Generate entire file from scratch | | copy | Deploy a static file |
FAQ
Why does lineinfile keep adding duplicate lines?
You're missing the regexp parameter. Without it, lineinfile checks for exact line match. If the line has even slight whitespace differences, it adds a new one. Always use regexp to match the line you want to replace.
What does backrefs: true do?
With backrefs: true, if the regex doesn't match, the line is left unchanged (instead of being appended). It also enables \1, \g<1> backreferences in the line: parameter to reuse captured groups from the regex.
How do I edit multiple lines in one task?
Use blockinfile for a contiguous block, loop with lineinfile for multiple independent lines, or template if you're editing more than 3-4 lines (at that point, manage the whole file).
Is lineinfile idempotent?
Yes, when used correctly. With regexp, it replaces the matched line every time (idempotent). Without regexp, it checks for exact line presence. Both are idempotent — running twice produces the same result.
Conclusion
lineinfile for single lines, blockinfile for multi-line blocks, replace for regex substitutions, template for whole-file management. Start with lineinfile for quick edits, graduate to template when you're managing more than a few lines in the same file.
Related Articles
• Ansible copy vs template vs lineinfile • Ansible template Module Guide • Ansible file Module Cookbook • Ansible regex_replace Filter GuideCategory: linux-administration