Back to Linux guides

Working with Ansible Lineinfile: A Tutorial for Beginners

Ayooluwa Isaiah
Updated on March 20, 2025

When managing multiple systems, the ability to make precise, controlled changes to configuration files becomes essential. This is where Ansible's lineinfile module proves invaluable.

Unlike modules that replace entire files, lineinfile allows you to make surgical modifications to specific lines while preserving the rest of the file's content. This targeted approach is perfect for updating configuration parameters, adding entries to system files, or toggling settings without disrupting the file's overall structure.

Consider scenarios where you need to change a single parameter in a large configuration file, add a new entry to your hosts file, or comment out a deprecated setting. These tasks require precision—changing too little means the task isn't completed, while changing too much risks breaking functionality. The lineinfile module excels in these situations by focusing changes only where needed.

In this guide, we'll explore the capabilities of the lineinfile module and learn how to use it effectively for various configuration management tasks on your local machine.

Understanding the lineinfile module

At its core, the lineinfile module ensures that a particular line exists in a file in a specific form, or that it doesn't exist at all. It works by searching for patterns using regular expressions and then applying the appropriate action.

Core parameters

The power of lineinfile comes from its flexible parameters:

  • path (or dest) specifies the file you want to modify. This is the only required parameter and tells Ansible which file to work with.

  • line contains the content you want to add or use as a replacement. This is what will appear in the file after the module runs.

  • regexp defines the pattern to match. The module uses this regular expression to find lines that need to be changed or to determine where to add new content.

  • state determines whether the specified line should be present or absent in the file. By default, it's set to "present."

  • insertafter and insertbefore control where a new line should be added if it doesn't already exist in the file. You can specify a regex pattern or use special values like "BOF" (beginning of file) or "EOF" (end of file).

  • backup creates a backup of the original file before making changes, which is especially valuable when modifying critical system files.

When to use lineinfile

The lineinfile module shines in specific scenarios but isn't always the best choice. It's ideal for:

  • Making small, targeted changes to configuration files
  • Ensuring specific settings are configured correctly
  • Adding or removing individual entries
  • Working with well-structured files where lines are clearly identifiable

However, for more complex requirements, consider alternatives:

  • For multi-line changes, the blockinfile module offers better control
  • When managing entire configuration files with many interdependent settings, the template module provides a more holistic approach
  • For structured data formats like YAML or JSON, specialized modules will handle the format's syntax rules correctly

Let's see a simple example of the lineinfile module in action:

basic_example.yml
- name: Basic lineinfile example
  hosts: localhost
  tasks:
    - name: Add a line to a file
      ansible.builtin.lineinfile:
        path: /tmp/example.txt
        line: 'This line will be added to the file'
        create: yes

This straightforward playbook adds a line to a text file, creating the file if it doesn't exist.

To run this playbook, save it as basic_example.yml and execute the following command in your terminal:

 
ansible-playbook basic_example.yml

You should see output similar to:

Output
PLAY [Basic lineinfile example] *********************************************

TASK [Gathering Facts] ******************************************************
ok: [localhost]

TASK [Add a line to a file] *************************************************
changed: [localhost]

PLAY RECAP ******************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

After the playbook runs, you can verify the result by examining the file:

 
cat /tmp/example.txt

You should see:

Output
This line will be added to the file

If you run the playbook a second time, the task will report ok instead of changed because the line already exists, demonstrating Ansible's idempotent behavior.

Basic lineinfile operations

Now let's explore some fundamental operations you can perform with the lineinfile module. These form the building blocks for more complex configuration tasks.

Replacing lines that match a pattern

Often, you'll need to find an existing line and replace it with updated content. This is where the regexp parameter becomes crucial:

replace_line.yml
- name: Replace a line in a configuration file
  hosts: localhost
  tasks:
    - name: Create a sample configuration file
      ansible.builtin.copy:
        dest: /tmp/config.ini
        content: |
          # Sample configuration file
          server_port = 8080
          debug_mode = false
          log_level = info

- name: Update the server port
ansible.builtin.lineinfile:
path: /tmp/config.ini
regexp: ^server_port = .*
line: server_port = 9090

This task searches for any line that starts with "server_port = " and replaces it with the new value. Since the regular expression matches any value, this configuration allows for consistent updates regardless of the file's initial state.

To execute the playbook, run:

 
ansible-playbook replace_line.yml
Output
PLAY [Replace a line in a configuration file] *******************************

TASK [Gathering Facts] ******************************************************
ok: [localhost]

TASK [Create a sample configuration file] ***********************************
changed: [localhost]

TASK [Update the server port] ***********************************************
changed: [localhost]

PLAY RECAP ******************************************************************
localhost                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Examine the modified file:

 
cat /tmp/config.ini

Output:

Output
# Sample configuration file
server_port = 9090
debug_mode = false
log_level = info

Notice that only the server_port line has changed, while the rest of the file remains untouched.

Removing lines from files

Sometimes you need to remove configuration entries entirely, such as disabling deprecated settings:

remove_line.yml
- name: Remove a line from a file
  hosts: localhost
  tasks:
    - name: Create a sample file with multiple lines
      ansible.builtin.copy:
        dest: /tmp/sample.txt
        content: |
          Line 1: Keep this line
          Line 2: This line should be removed
          Line 3: Keep this line too

- name: Remove the unwanted line
ansible.builtin.lineinfile:
path: /tmp/sample.txt
regexp: ^Line 2:.*
state: absent

By setting state: absent, you instruct Ansible to remove any line matching the pattern. This is perfect for cleaning up configuration files by removing outdated or unnecessary settings.

Once you execute the playbook:

 
ansible-playbook remove_line.yml

You will see the following output:

Output
PLAY [Remove a line from a file] ********************************************

TASK [Gathering Facts] ******************************************************
ok: [localhost]

TASK [Create a sample file with multiple lines] *****************************
changed: [localhost]

TASK [Remove the unwanted line] *********************************************
changed: [localhost]

PLAY RECAP ******************************************************************
localhost                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Examine the file to confirm the line was removed:

 
cat /tmp/sample.txt
Output
Line 1: Keep this line
Line 3: Keep this line too

The second line has been removed while preserving the rest of the file's content.

Working with file permissions

When modifying system files, proper permissions are critical. The lineinfile module allows you to set permissions as part of the modification:

set_permissions.yml
- name: Add a line and set proper permissions
  hosts: localhost
  tasks:
    - name: Add a line and set proper permissions
      ansible.builtin.lineinfile:
        path: /tmp/secure_config.txt
        line: secure_setting = true
        owner: "{{ ansible_user_id }}"
        group: "{{ ansible_user_id }}"
        mode: 0644
        create: yes

This task not only adds a line to the file but also sets the file ownership to the current user and applies appropriate permissions—read and write for the owner, read-only for everyone else.

Running set_permissions.yml

Execute the playbook:

 
ansible-playbook set_permissions.yml
Output
PLAY [Add a line and set proper permissions] ********************************

TASK [Gathering Facts] ******************************************************
ok: [localhost]

TASK [Add a line and set proper permissions] ********************************
changed: [localhost]

PLAY RECAP ******************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Verify the file content and permissions:

 
cat /tmp/secure_config.txt
ls -l /tmp/secure_config.txt

Output:

Output
secure_setting = true
-rw-r--r-- 1 yourusername yourusername 21 Mar 19 10:15 /tmp/secure_config.txt

The file has been created with the specified content and permissions.

Using regular expressions with lineinfile

Regular expressions are the secret to the lineinfile module's power, allowing for precise targeting of content within files.

Understanding regex patterns for lineinfile

Regular expressions provide a language for pattern matching in text. With lineinfile, you use these patterns to identify exactly which lines to modify.

For example, to uncomment a configuration line:

uncomment_line.yml
- name: Uncomment a configuration setting
  hosts: localhost
  tasks:
    - name: Create a sample configuration with commented line
      ansible.builtin.copy:
        dest: /tmp/app.conf
        content: |
          # Application settings
          app_name = MyApp
          # debug_mode = false
          log_level = info

- name: Uncomment and update debug mode
ansible.builtin.lineinfile:
path: /tmp/app.conf
regexp: ^#\s*debug_mode\s*=
line: debug_mode = true

This task finds any line that starts with a hash followed by optional whitespace, then debug_mode and equals sign (a commented-out setting), and replaces it with an active setting.

You can execute the playbook:

 
ansible-playbook uncomment_line.yml
Output
PLAY [Uncomment a configuration setting] ************************************

TASK [Gathering Facts] ******************************************************
ok: [localhost]

TASK [Create a sample configuration with commented line] ********************
changed: [localhost]

TASK [Uncomment and update debug mode] **************************************
changed: [localhost]

PLAY RECAP ******************************************************************
localhost                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

The examine the modified file:

 
cat /tmp/app.conf

You should see:

Output
# Application settings
app_name = MyApp
debug_mode = true
log_level = info

Notice that the commented debug_mode line has been uncommented and its value changed to true.

Capturing groups and backreferences

When you need to preserve parts of the original line while changing others, capturing groups and backreferences become invaluable:

backreferences.yml
- name: Use backreferences to preserve formatting
  hosts: localhost
  tasks:
    - name: Create a sample formatted file
      ansible.builtin.copy:
        dest: /tmp/formatted_config.txt
        content: |
          # Settings with specific formatting
          MAX_CONNECTIONS     =     10
          TIMEOUT             =     30
          RETRY_COUNT         =     3

    - name: Update connections while preserving format
      ansible.builtin.lineinfile:
        path: /tmp/formatted_config.txt
        regexp: '^(MAX_CONNECTIONS\s*=\s*).*'
        line: '\1100'
        backrefs: yes

In this example, the regular expression captures the MAX_CONNECTIONS = part with exact spacing in a group. The \1 in the replacement line references this captured group, preserving the exact formatting while changing only the value. The backrefs: yes parameter is necessary to enable this functionality.

Once the playbook is run:

 
ansible-playbook backreferences.yml
Output
PLAY [Use backreferences to preserve formatting] ****************************

TASK [Gathering Facts] ******************************************************
ok: [localhost]

TASK [Create a sample formatted file] ***************************************
changed: [localhost]

TASK [Update connections while preserving format] ***************************
changed: [localhost]

PLAY RECAP ******************************************************************
localhost                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

You can examine the modified file:

 
cat /tmp/formatted_config.txt

It should display:

Output
# Settings with specific formatting
MAX_CONNECTIONS     =     100
TIMEOUT             =     30
RETRY_COUNT         =     3

Notice how the exact spacing around the equals sign has been preserved, while only the value has been updated to 100.

Testing regex patterns

When working with complex regex patterns, testing before implementation is prudent:

test_regex.yml
- name: Test a regex pattern before applying
  hosts: localhost
  tasks:
    - name: Create a test configuration
      ansible.builtin.copy:
        dest: /tmp/test.conf
        content: |
          # Server configuration
          Listen 8080
          DocumentRoot /var/www

    - name: Test a regex pattern (dry run)
      ansible.builtin.lineinfile:
        path: /tmp/test.conf
        regexp: '^(\s*Listen\s+)\d+'
        line: '\180'
        backrefs: yes
check_mode: yes
register: result - name: Show what would change ansible.builtin.debug: var: result

The check_mode: yes parameter performs a dry run without making actual changes. The result is registered and can be examined to ensure the pattern works as expected before applying changes to production files.

Running test_regex.yml

Execute the playbook:

 
ansible-playbook test_regex.yml

Output:

Output
PLAY [Test a regex pattern before applying] *********************************

TASK [Gathering Facts] ******************************************************
ok: [localhost]

TASK [Create a test configuration] ******************************************
changed: [localhost]

TASK [Test a regex pattern (dry run)] ***************************************
changed: [localhost]

TASK [Show what would change] ***********************************************
ok: [localhost] => {
    "result": {
        "changed": true,
        "diff": [
            {
                "after": "Listen 80",
                "after_header": "new line",
                "before": "Listen 8080",
                "before_header": "line"
            }
        ],
        "failed": false,
        "msg": "line replaced"
    }
}

PLAY RECAP ******************************************************************
localhost                  : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

The debug output shows what would change if the task were run normally. In this case, Listen 8080 would be replaced with Listen 80. The file remains unchanged because of check mode, which you can verify:

 
cat /tmp/test.conf

Output:

Output
# Server configuration
Listen 8080
DocumentRoot /var/www

Practical example: Editing configuration files

Let's apply these concepts to a practical scenario: configuring a web server on your local machine for development purposes.

First, we'll create a sample configuration file and then modify it to suit our development needs:

web_server_config.yml
- name: Configure local web server
  hosts: localhost
  vars:
    config_file: /tmp/webserver.conf
    port: 8080
    document_root: /tmp/www

  tasks:
    - name: Create base configuration
      ansible.builtin.copy:
        dest: "{{ config_file }}"
        content: |
          # Web Server Configuration

          Listen 80
          DocumentRoot /var/www/html

          LogLevel warn
          ErrorLog logs/error.log
          CustomLog logs/access.log combined

          # Security settings
          # ServerTokens Prod
          # ServerSignature Off

    - name: Update listening port
      ansible.builtin.lineinfile:
        path: "{{ config_file }}"
        regexp: '^Listen\s+\d+'
        line: "Listen {{ port }}"

    - name: Set document root for local development
      ansible.builtin.lineinfile:
        path: "{{ config_file }}"
        regexp: '^DocumentRoot\s+'
        line: "DocumentRoot {{ document_root }}"

    - name: Enable security settings (uncomment)
      ansible.builtin.lineinfile:
        path: "{{ config_file }}"
        regexp: '^#\s*(ServerTokens\s+Prod)'
        line: '\1'
        backrefs: yes

    - name: Create document root directory
      ansible.builtin.file:
        path: "{{ document_root }}"
        state: directory

    - name: Display the modified configuration
      ansible.builtin.command: cat {{ config_file }}
      register: cat_result
      changed_when: false

    - name: Show the final configuration
      ansible.builtin.debug:
        var: cat_result.stdout_lines

Execute the playbook:

 
ansible-playbook web_server_config.yml
Output
PLAY [Configure local web server] *******************************************

TASK [Gathering Facts] ******************************************************
ok: [localhost]

TASK [Create base configuration] ********************************************
changed: [localhost]

TASK [Update listening port] ************************************************
changed: [localhost]

TASK [Set document root for local development] ******************************
changed: [localhost]

TASK [Enable security settings (uncomment)] *********************************
changed: [localhost]

TASK [Create document root directory] ***************************************
changed: [localhost]

TASK [Display the modified configuration] ***********************************
changed: [localhost]

TASK [Show the final configuration] *****************************************
ok: [localhost] => {
    "cat_result.stdout_lines": [
        "# Web Server Configuration",
        "",
        "Listen 8080",
        "DocumentRoot /tmp/www",
        "",
        "LogLevel warn",
        "ErrorLog logs/error.log",
        "CustomLog logs/access.log combined",
        "",
        "# Security settings",
        "ServerTokens Prod",
        "# ServerSignature Off"
    ]
}

PLAY RECAP ******************************************************************
localhost                  : ok=8    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

The final output shows the modified configuration with:

  1. The port changed from 80 to 8080
  2. The document root updated to /tmp/www
  3. The ServerTokens line uncommented

Additionally, the playbook created the document root directory to ensure the configuration would work if implemented.

Managing application properties

Many applications use properties files for configuration. Let's see how lineinfile can manage these types of files:

app_properties.yml
- name: Configure application properties
  hosts: localhost
  vars:
    properties_file: /tmp/application.properties
    db_url: "jdbc:mysql://localhost:3306/devdb"
    log_level: "debug"

  tasks:
    - name: Create properties file
      ansible.builtin.copy:
        dest: "{{ properties_file }}"
        content: |
          # Application properties
          app.name=MyApp
          app.version=1.0

          # Database settings
          spring.datasource.url=jdbc:h2:mem:testdb
          spring.datasource.username=sa
          spring.datasource.password=

          # Logging
          logging.level.root=info
          logging.file=/tmp/app.log

    - name: Update database connection
      ansible.builtin.lineinfile:
        path: "{{ properties_file }}"
        regexp: '^spring\.datasource\.url='
        line: "spring.datasource.url={{ db_url }}"

    - name: Set logging level
      ansible.builtin.lineinfile:
        path: "{{ properties_file }}"
        regexp: '^logging\.level\.root='
        line: "logging.level.root={{ log_level }}"

    - name: Display the modified properties
      ansible.builtin.command: cat {{ properties_file }}
      register: cat_result
      changed_when: false

    - name: Show the final properties
      ansible.builtin.debug:
        var: cat_result.stdout_lines

Execute the playbook:

 
ansible-playbook app_properties.yml

Output:

Output
PLAY [Configure application properties] *************************************

TASK [Gathering Facts] ******************************************************
ok: [localhost]

TASK [Create properties file] ***********************************************
changed: [localhost]

TASK [Update database connection] *******************************************
changed: [localhost]

TASK [Set logging level] ****************************************************
changed: [localhost]

TASK [Display the modified properties] **************************************
changed: [localhost]

TASK [Show the final properties] ********************************************
ok: [localhost] => {
    "cat_result.stdout_lines": [
        "# Application properties",
        "app.name=MyApp",
        "app.version=1.0",
        "",
        "# Database settings",
        "spring.datasource.url=jdbc:mysql://localhost:3306/devdb",
        "spring.datasource.username=sa",
        "spring.datasource.password=",
        "",
        "# Logging",
        "logging.level.root=debug",
        "logging.file=/tmp/app.log"
    ]
}

PLAY RECAP ******************************************************************
localhost                  : ok=6    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

The final properties file shows:

  1. The database URL has been changed from an in-memory H2 database to a MySQL connection.
  2. The logging level has been updated from info to debug.

This example demonstrates how lineinfile can update Java properties files, which are common in many enterprise applications.

Advanced techniques with lineinfile

As you become comfortable with basic lineinfile operations, you can explore more advanced techniques to handle complex scenarios.

Conditional modifications

Sometimes you need to make changes based on the current state of the file or other conditions:

conditional.yml
- name: Make conditional changes
  hosts: localhost
  vars:
    config_file: /tmp/conditional.conf

  tasks:
    - name: Create initial configuration
      ansible.builtin.copy:
        dest: "{{ config_file }}"
        content: |
          # Environment configuration
          ENV=production
          LOG_LEVEL=normal
          DEBUG=false

    - name: Check if production mode is enabled
      ansible.builtin.command: grep -q "^ENV=production" {{ config_file }}
      register: grep_result
      changed_when: false
      failed_when: false

    - name: Enable detailed logging in production mode
      ansible.builtin.lineinfile:
        path: "{{ config_file }}"
        regexp: '^LOG_LEVEL='
        line: 'LOG_LEVEL=verbose'
      when: grep_result.rc == 0

    - name: Display the modified configuration
      ansible.builtin.command: cat {{ config_file }}
      register: cat_result
      changed_when: false

    - name: Show the final configuration
      ansible.builtin.debug:
        var: cat_result.stdout_lines

When you execute the playbook:

 
ansible-playbook conditional.yml

You will see:

Output
PLAY [Make conditional changes] *********************************************

TASK [Gathering Facts] ******************************************************
ok: [localhost]

TASK [Create initial configuration] *****************************************
changed: [localhost]

TASK [Check if production mode is enabled] **********************************
changed: [localhost]

TASK [Enable detailed logging in production mode] ***************************
changed: [localhost]

TASK [Display the modified configuration] ***********************************
changed: [localhost]

TASK [Show the final configuration] *****************************************
ok: [localhost] => {
    "cat_result.stdout_lines": [
        "# Environment configuration",
        "ENV=production",
        "LOG_LEVEL=verbose",
        "DEBUG=false"
    ]
}

PLAY RECAP ******************************************************************
localhost                  : ok=6    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Since the configuration has ENV=production, the conditional task executes and changes the LOG_LEVEL to verbose. This demonstrates how to make configuration changes based on the context or content of the file.

Creating backups before changes

When modifying critical files, creating backups is a prudent safety measure that can also be achieved with the lineinfile module:

backup.yml
---
- name: Modify with backup
  hosts: localhost
  tasks:
    - name: Create important configuration
      ansible.builtin.copy:
        dest: /tmp/important.conf
        content: |
          # Critical system configuration
          # Do not modify manually

          CRITICAL_SETTING=default_value
          SYSTEM_MODE=normal

    - name: Update important setting with backup
      ansible.builtin.lineinfile:
        path: /tmp/important.conf
        regexp: '^CRITICAL_SETTING='
        line: 'CRITICAL_SETTING=new_value'
        backup: yes
      register: backup_result

    - name: Display backup file path
      ansible.builtin.debug:
        msg: "Backup created at: {{ backup_result.backup_file }}"
      when: backup_result.changed

    - name: Compare original and modified files
      ansible.builtin.command: diff {{ backup_result.backup_file }} /tmp/important.conf
      register: diff_result
      changed_when: false
      failed_when: false
      when: backup_result.changed

    - name: Show diff
      ansible.builtin.debug:
        var: diff_result.stdout_lines
      when: backup_result.changed

Execute the playbook:

 
ansible-playbook backup.yml

Output:

Output
PLAY [Modify with backup] ***************************************************

TASK [Gathering Facts] ******************************************************
ok: [localhost]

TASK [Create important configuration] ***************************************
changed: [localhost]

TASK [Update important setting with backup] *********************************
changed: [localhost]

TASK [Display backup file path] *********************************************
ok: [localhost] => {
    "msg": "Backup created at: /tmp/important.conf.22785.2025-03-19@10:30:17~"
}

TASK [Compare original and modified files] **********************************
changed: [localhost]

TASK [Show diff] ************************************************************
ok: [localhost] => {
    "diff_result.stdout_lines": [
        "4c4",
        "< CRITICAL_SETTING=default_value",
        "---",
        "> CRITICAL_SETTING=new_value"
    ]
}

PLAY RECAP ******************************************************************
localhost                  : ok=6    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

The backup: yes parameter creates a timestamped copy of the original file before making changes. The playbook then shows the backup file path and displays the differences between the original and modified files, confirming that only the CRITICAL_SETTING line was changed.

Working with multiple lines

While lineinfile operates on one line at a time, you can use it in a loop to modify multiple lines efficiently:

multiple_lines.yml
---
- name: Update multiple settings
  hosts: localhost
  vars:
    config_file: /tmp/multi.conf
    settings:
      - { key: "PORT", value: "8080" }
      - { key: "DEBUG", value: "true" }
      - { key: "LOG_LEVEL", value: "debug" }

  tasks:
    - name: Create initial configuration
      ansible.builtin.copy:
        dest: "{{ config_file }}"
        content: |
          # Multi-line configuration example
          PORT=80
          DEBUG=false
          LOG_LEVEL=info

    - name: Update each setting
      ansible.builtin.lineinfile:
        path: "{{ config_file }}"
        regexp: '^{{ item.key }}='
        line: '{{ item.key }}={{ item.value }}'
      loop: "{{ settings }}"

    - name: Display the final configuration
      ansible.builtin.command: cat {{ config_file }}
      register: cat_result
      changed_when: false

    - name: Show the final settings
      ansible.builtin.debug:
        var: cat_result.stdout_lines

Running multiple_lines.yml

Execute the playbook:

 
ansible-playbook multiple_lines.yml

Output:

Output
PLAY [Update multiple settings] *********************************************

TASK [Gathering Facts] ******************************************************
ok: [localhost]

TASK [Create initial configuration] *****************************************
changed: [localhost]

TASK [Update each setting] **************************************************
changed: [localhost] => (item={'key': 'PORT', 'value': '8080'})
changed: [localhost]

TASK [Update each setting] **************************************************
changed: [localhost] => (item={'key': 'PORT', 'value': '8080'})
changed: [localhost] => (item={'key': 'DEBUG', 'value': 'true'})
changed: [localhost] => (item={'key': 'LOG_LEVEL', 'value': 'debug'})

TASK [Display the final configuration] **************************************
changed: [localhost]

TASK [Show the final settings] **********************************************
ok: [localhost] => {
    "cat_result.stdout_lines": [
        "# Multi-line configuration example",
        "PORT=8080",
        "DEBUG=true",
        "LOG_LEVEL=debug"
    ]
}

PLAY RECAP ******************************************************************
localhost                  : ok=5    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

This approach uses a variable to define multiple settings and their values, then loops through them to update each one in the configuration file. The final output shows all three settings successfully updated according to the values defined in the settings variable.

Some lineinfile best practices

As you work with the lineinfile module, keep the following best practices in mind:

1. Test before applying

Always test your regular expressions on sample files before applying them to production systems. The check_mode parameter is invaluable for seeing what would change without making actual modifications.

For example, you can use:

 
ansible-playbook your_playbook.yml --check

This runs the entire playbook in check mode, showing what would change without making actual modifications.

2. Be specific with patterns

Make your regular expressions as specific as possible to avoid unintended matches. For example, using ^setting= rather than just setting= ensures you only match lines that begin with that setting.

Consider this example:

 
# Too general - might match in comments or other contexts
regexp: 'port='

# More specific - only matches lines starting with "port="
regexp: '^port='

# Even more specific - ensures proper formatting
regexp: '^port\s*=\s*\d+'

3. Create backups

Use the backup: yes parameter when modifying critical files to create automatic backups before changes are applied. This provides a safety net if something goes wrong.

4. Ensure idempotence

Design your tasks to be idempotent—running the same playbook multiple times should produce the same result without unnecessary changes. This is a core principle of Ansible and ensures predictable behavior.

Test your playbooks by running them multiple times to verify they only make changes when needed.

5. Know when to use alternatives

While lineinfile is powerful, recognize when other modules might be more appropriate:

  • Use blockinfile for multi-line changes.
  • Use template for managing complete files with complex structures.
  • Use format-specific modules for structured data like YAML or JSON.

Final thoughts

The lineinfile module is a powerful tool in the Ansible ecosystem, enabling precise control over configuration files with minimal impact. By mastering regular expressions and understanding the module's parameters, you can automate complex configuration tasks across multiple systems while maintaining consistency and reliability.

Throughout this guide, we've explored how to use lineinfile for various tasks: adding lines, replacing content, removing entries, and preserving formatting. We've seen how to work with different types of configuration files and how to build more complex automation workflows by combining multiple lineinfile tasks.

The focus on local development environments demonstrates how you can use these techniques in a practical, low-risk way before applying them to production systems. The same principles apply whether you're managing a single development machine or a fleet of production servers.

Thanks for reading!

Author's avatar
Article by
Ayooluwa Isaiah
Ayo is a technical content manager at Better Stack. His passion is simplifying and communicating complex technical ideas effectively. His work was featured on several esteemed publications including LWN.net, Digital Ocean, and CSS-Tricks. When he's not writing or coding, he loves to travel, bike, and play tennis.
Got an article suggestion? Let us know
Next article
Ansible vs Terraform: A Comprehensive Comparison
Ansible excels at configuration management while Terraform specializes in infrastructure provisioning. Learn when to use each and how they work together.
Licensed under CC-BY-NC-SA

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Make your mark

Join the writer's program

Are you a developer and love writing and sharing your knowledge with the world? Join our guest writing program and get paid for writing amazing technical guides. We'll get them to the right readers that will appreciate them.

Write for us
Writer of the month
Marin Bezhanov
Marin is a software engineer and architect with a broad range of experience working...
Build on top of Better Stack

Write a script, app or project on top of Better Stack and share it with the world. Make a public repository and share it with us at our email.

community@betterstack.com

or submit a pull request and help us build better products for everyone.

See the full list of amazing projects on github