Introduction
ansible.builtin.blockinfile inserts, updates, or removes a block of text in a file. Unlike lineinfile (single lines), blockinfile manages multi-line content — configuration blocks, script sections, and structured entries. It uses marker comments to track its blocks.
Basic Usage
- name: Add SSH config block
ansible.builtin.blockinfile:
path: /etc/ssh/sshd_config
block: |
Match Group developers
AllowTcpForwarding yes
X11Forwarding no
PermitTunnel no
notify: Restart sshd
This produces:
# BEGIN ANSIBLE MANAGED BLOCK
Match Group developers
AllowTcpForwarding yes
X11Forwarding no
PermitTunnel no
# END ANSIBLE MANAGED BLOCK
Parameters
| Parameter | Default | Description |
|---|---|---|
path | (required) | File to modify |
block | "" | Text block to insert (empty = remove block) |
marker | "# {mark} ANSIBLE MANAGED BLOCK" | Marker template ({mark} → BEGIN/END) |
marker_begin | BEGIN | Text for {mark} in opening marker |
marker_end | END | Text for {mark} in closing marker |
insertafter | EOF | Insert after this regex |
insertbefore | — | Insert before this regex |
create | false | Create file if it doesn't exist |
state | present | present or absent |
backup | false | Create backup before modifying |
owner | — | File owner |
group | — | File group |
mode | — | File permissions |
Custom Markers
Use custom markers to manage multiple blocks in the same file:
# Block 1: Database config
- name: Add database config
ansible.builtin.blockinfile:
path: /etc/myapp/config.conf
marker: "# {mark} DATABASE CONFIG"
block: |
db_host=db.internal
db_port=5432
db_name=production
# Block 2: Cache config (same file, different marker)
- name: Add cache config
ansible.builtin.blockinfile:
path: /etc/myapp/config.conf
marker: "# {mark} CACHE CONFIG"
block: |
cache_host=redis.internal
cache_port=6379
cache_ttl=3600
Result:
# BEGIN DATABASE CONFIG
db_host=db.internal
db_port=5432
db_name=production
# END DATABASE CONFIG
# BEGIN CACHE CONFIG
cache_host=redis.internal
cache_port=6379
cache_ttl=3600
# END CACHE CONFIG
Insert Position
# After a specific line
- name: Add after [mysqld]
ansible.builtin.blockinfile:
path: /etc/mysql/my.cnf
insertafter: '^\[mysqld\]'
block: |
innodb_buffer_pool_size = 1G
max_connections = 200
slow_query_log = 1
# Before a specific line
- name: Add before [client]
ansible.builtin.blockinfile:
path: /etc/mysql/my.cnf
insertbefore: '^\[client\]'
block: |
[mysqldump]
quick
max_allowed_packet = 64M
# At beginning of file (BOF)
- name: Add at top
ansible.builtin.blockinfile:
path: /etc/hosts
insertbefore: BOF
block: |
# Internal services
10.0.0.10 db.internal
10.0.0.11 cache.internal
# At end of file (EOF) — default
- name: Add at bottom
ansible.builtin.blockinfile:
path: /etc/hosts
block: |
10.0.0.20 app1.internal
10.0.0.21 app2.internal
Remove a Block
# Remove by setting state: absent
- name: Remove database config
ansible.builtin.blockinfile:
path: /etc/myapp/config.conf
marker: "# {mark} DATABASE CONFIG"
state: absent
# Or set empty block
- name: Remove default block
ansible.builtin.blockinfile:
path: /etc/myapp/config.conf
block: ""
Practical Patterns
/etc/hosts Management
- name: Add application hosts
ansible.builtin.blockinfile:
path: /etc/hosts
marker: "# {mark} APP HOSTS - {{ env }}"
block: |
{% for host in app_hosts %}
{{ host.ip }} {{ host.name }}.{{ domain }}
{% endfor %}
Nginx Server Block
- name: Add nginx upstream
ansible.builtin.blockinfile:
path: /etc/nginx/conf.d/upstream.conf
create: true
marker: "# {mark} UPSTREAM {{ app_name }}"
block: |
upstream {{ app_name }} {
{% for server in app_servers %}
server {{ server }}:{{ app_port }};
{% endfor %}
}
notify: Reload nginx
SSH Authorized Keys
- name: Add team SSH keys
ansible.builtin.blockinfile:
path: "/home/{{ user }}/.ssh/authorized_keys"
create: true
owner: "{{ user }}"
group: "{{ user }}"
mode: "0600"
marker: "# {mark} TEAM KEYS"
block: |
{% for key in team_ssh_keys %}
{{ key }}
{% endfor %}
Sudoers Configuration
- name: Add deploy sudoers
ansible.builtin.blockinfile:
path: /etc/sudoers.d/deploy
create: true
mode: "0440"
validate: "visudo -cf %s"
marker: "# {mark} DEPLOY USER RULES"
block: |
deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart myapp
deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl reload nginx
Crontab Entries
- name: Add maintenance cron jobs
ansible.builtin.blockinfile:
path: /etc/cron.d/myapp-maintenance
create: true
mode: "0644"
marker: "# {mark} MYAPP MAINTENANCE"
block: |
# Database backup - daily at 2 AM
0 2 * * * postgres pg_dump myapp > /backups/myapp-$(date +\%F).sql
# Log rotation - daily at 3 AM
0 3 * * * root find /var/log/myapp -name '*.log' -mtime +30 -delete
# Health check - every 5 minutes
*/5 * * * * deploy curl -sf http://localhost:8080/health || systemctl restart myapp
Environment File
- name: Set environment variables
ansible.builtin.blockinfile:
path: /opt/myapp/.env
create: true
owner: deploy
mode: "0600"
marker: "# {mark} ANSIBLE MANAGED"
block: |
APP_ENV={{ env }}
DATABASE_URL=postgresql://{{ db_user }}:{{ db_pass }}@{{ db_host }}:5432/{{ db_name }}
REDIS_URL=redis://{{ redis_host }}:6379
SECRET_KEY={{ secret_key }}
blockinfile vs lineinfile
| blockinfile | lineinfile | |
|---|---|---|
| Scope | Multi-line blocks | Single lines |
| Tracking | Marker comments | Regex matching |
| Updates | Replace entire block | Replace matched line |
| Best for | Config sections, multi-line entries | Single settings, one-liners |
# lineinfile — single setting
- lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PermitRootLogin'
line: 'PermitRootLogin no'
# blockinfile — config section
- blockinfile:
path: /etc/ssh/sshd_config
marker: "# {mark} SFTP CONFIG"
block: |
Match Group sftp-users
ChrootDirectory /home/%u
ForceCommand internal-sftp
AllowTcpForwarding no
Troubleshooting
Block Inserted Multiple Times
This happens without proper markers. Always use unique marker values for different blocks in the same file:
# WRONG — both use default marker, second overwrites first
- blockinfile: { path: /etc/hosts, block: "10.0.0.1 app1" }
- blockinfile: { path: /etc/hosts, block: "10.0.0.2 app2" }
# RIGHT — unique markers
- blockinfile: { path: /etc/hosts, marker: "# {mark} APP1", block: "10.0.0.1 app1" }
- blockinfile: { path: /etc/hosts, marker: "# {mark} APP2", block: "10.0.0.2 app2" }
Marker Syntax Matters
{mark} is replaced with BEGIN/END. Don't forget it:
# WRONG — no {mark}
marker: "# ANSIBLE BLOCK"
# RIGHT
marker: "# {mark} ANSIBLE BLOCK"
# Produces: # BEGIN ANSIBLE BLOCK / # END ANSIBLE BLOCK
Related Articles
Conclusion
blockinfile manages multi-line content blocks in files using marker comments. Use unique marker values when managing multiple blocks in the same file. Control placement with insertafter/insertbefore. Always validate critical files like sudoers. For single-line changes, use lineinfile; for multi-line sections, blockinfile; for entire files, template. The markers make blocks fully idempotent — Ansible finds and updates its block on every run.