httpd)?The Apache HTTP Server (commonly known as httpd on Red Hat Enterprise Linux and CentOS systems) is one of the most widely deployed open-source web servers in modern enterprise infrastructure. It delivers high-performance HTTP web content, supports modular extensions (e.g., SSL/TLS encryption, URL rewriting, proxying), and serves as the primary front-end web server for multi-tier web applications.
Automating web application deployments across multi-node server clusters requires orchestrating three fundamental operational phases:
ansible.builtin.yum): Installing the httpd package binary on managed nodes.ansible.builtin.service): Enabling the httpd system service to start automatically on system boot and ensuring its current operational state is started.ansible.builtin.blockinfile): Injecting and maintaining structured HTML/configuration content inside web root directories (/var/www/html/index.html) while enforcing strict file ownership and mode permissions (apache:apache, 0644).ansible.builtin.blockinfile ModuleThe ansible.builtin.blockinfile module inserts, updates, or removes a multi-line block of text within an existing or new file. Unlike the copy or template modules (which manage entire files), blockinfile manages designated text blocks demarcated by customizable marker comments (e.g., # BEGIN ANSIBLE MANAGED BLOCK).
Key capabilities of blockinfile:
changed: false).create: yes): If the destination file does not exist, setting create: yes instructs Ansible to create a new file.owner, group, and mode parameters to set target file metadata in a single atomic task.graph TD
subgraph ControlNode ["Jump Host (Ansible Controller)"]
ThorUser["User: thor"]
InvFile["Inventory File<br/>/home/thor/ansible/inventory"]
PlaybookFile["Playbook File<br/>/home/thor/ansible/playbook.yml"]
AnsibleCLI["Ansible Engine<br/>ansible-playbook -i inventory playbook.yml"]
end
subgraph StratosDC ["Stratos Datacenter (App Servers)"]
subgraph ManagedNodes ["Target: all (stapp01, stapp02, stapp03)"]
App1["stapp01<br/>User: tony"]
App2["stapp02<br/>User: steve"]
App3["stapp03<br/>User: banner"]
end
HTTPDService["Service: httpd (started & enabled)"]
IndexFile["File: /var/www/html/index.html<br/>Owner: apache:apache | Mode: 0644"]
end
InvFile -->|"Supply Connection Details"| AnsibleCLI
PlaybookFile -->|"Supply Tasks (yum, service, blockinfile)"| AnsibleCLI
AnsibleCLI -->|"1. Install httpd package"| ManagedNodes
AnsibleCLI -->|"2. Start & enable httpd service"| HTTPDService
AnsibleCLI -->|"3. Insert block content into index.html"| IndexFile
playbook.yml)---
- name: Install and Setup Apache Web Server with Custom Index Page
hosts: all
become: yes
tasks:
- name: Install httpd package
ansible.builtin.yum:
name: httpd
state: present
- name: Start and enable httpd service
ansible.builtin.service:
name: httpd
state: started
enabled: yes
- name: Deploy index.html content using blockinfile module
ansible.builtin.blockinfile:
path: /var/www/html/index.html
create: yes
owner: apache
group: apache
mode: '0644'
block: |
Welcome to XfusionCorp!
This is Nautilus sample file, created using Ansible!
Please do not modify this file manually!
| Module Directive | Parameter | Function |
|---|---|---|
ansible.builtin.yum |
name: httpd |
Specifies the RPM package name for Apache Web Server. |
state: present |
Ensures httpd is installed without forcing unnecessary re-downloads. |
|
ansible.builtin.service |
name: httpd |
Specifies the systemd unit name. |
state: started |
Ensures the web server process is actively running. | |
enabled: yes |
Enables the service to start automatically on system boot. | |
ansible.builtin.blockinfile |
path: /var/www/html/index.html |
Absolute path to target file on managed servers. |
create: yes |
Creates /var/www/html/index.html if it does not already exist. |
|
owner: apache |
Sets user ownership of the file to apache. |
|
group: apache |
Sets group ownership of the file to apache. |
|
mode: '0644' |
Sets read/write for owner, read-only for group/others (-rw-r--r--). |
|
block: \| |
Literal multiline block scalar holding the target text content. |
| Host Role | Hostname / Alias | SSH User | SSH Password | Target Role |
|---|---|---|---|---|
| Ansible Controller | jump_host |
thor |
Native | Control Node |
| App Server 1 | stapp01 |
tony |
Ir0nM@n |
Managed Node 1 |
| App Server 2 | stapp02 |
steve |
Am3ric@ |
Managed Node 2 |
| App Server 3 | stapp03 |
banner |
BigGr33n |
Managed Node 3 |
/home/thor/ansible/inventory)stapp01 ansible_host=stapp01 ansible_ssh_pass=Ir0nM@n ansible_user=tony
stapp02 ansible_host=stapp02 ansible_ssh_pass=Am3ric@ ansible_user=steve
stapp03 ansible_host=stapp03 ansible_ssh_pass=BigGr33n ansible_user=banner
/home/thor/ansible/inventory/home/thor/ansible/playbook.ymlall (all application servers in inventory)httpd installed via yum modulehttpd service started and enabled/var/www/html/index.html created/updated via blockinfileapache, Group apache, Permissions 0644ansible-playbook -i inventory playbook.yml
Log in to the Jump Host as user thor:
ssh thor@jump_host
Change directory to /home/thor/ansible:
cd /home/thor/ansible
Verify the existing inventory file content:
cat /home/thor/ansible/inventory
Output:
stapp01 ansible_host=stapp01 ansible_ssh_pass=Ir0nM@n ansible_user=tony
stapp02 ansible_host=stapp02 ansible_ssh_pass=Am3ric@ ansible_user=steve
stapp03 ansible_host=stapp03 ansible_ssh_pass=BigGr33n ansible_user=banner
Verify connectivity using the Ansible ping module targeting all:
ansible -i inventory all -m ping
Expected Output: Returns "ping": "pong" with status SUCCESS for stapp01, stapp02, and stapp03.
Create /home/thor/ansible/playbook.yml using cat:
cat << 'EOF' > /home/thor/ansible/playbook.yml
---
- name: Install and Setup Apache Web Server with Custom Index Page
hosts: all
become: yes
tasks:
- name: Install httpd package
ansible.builtin.yum:
name: httpd
state: present
- name: Start and enable httpd service
ansible.builtin.service:
name: httpd
state: started
enabled: yes
- name: Deploy index.html content using blockinfile module
ansible.builtin.blockinfile:
path: /var/www/html/index.html
create: yes
owner: apache
group: apache
mode: '0644'
block: |
Welcome to XfusionCorp!
This is Nautilus sample file, created using Ansible!
Please do not modify this file manually!
EOF
Check the playbook YAML syntax for errors:
ansible-playbook -i inventory playbook.yml --syntax-check
Expected Output:
playbook: playbook.yml
Run the playbook using the exact validation command:
ansible-playbook -i inventory playbook.yml
Verify that all tasks execute successfully with changed status on initial run:
PLAY [Install and Setup Apache Web Server with Custom Index Page] ********************************
TASK [Gathering Facts] ***************************************************************************
ok: [stapp01]
ok: [stapp02]
ok: [stapp03]
TASK [Install httpd package] *********************************************************************
changed: [stapp01]
changed: [stapp02]
changed: [stapp03]
TASK [Start and enable httpd service] ************************************************************
changed: [stapp01]
changed: [stapp02]
changed: [stapp03]
TASK [Deploy index.html content using blockinfile module] ****************************************
changed: [stapp01]
changed: [stapp02]
changed: [stapp03]
PLAY RECAP ***************************************************************************************
stapp01 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
stapp02 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
stapp03 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
curl)Test HTTP responses from all three application servers from the Jump Host:
curl http://stapp01
curl http://stapp02
curl http://stapp03
Expected HTTP Body Output:
# BEGIN ANSIBLE MANAGED BLOCK
Welcome to XfusionCorp!
This is Nautilus sample file, created using Ansible!
Please do not modify this file manually!
# END ANSIBLE MANAGED BLOCK
Run an ad-hoc shell command to verify /var/www/html/index.html file attributes on all managed servers:
ansible -i inventory all -m shell -a "ls -l /var/www/html/index.html"
Expected Output:
stapp01 | CHANGED | rc=0 >>
-rw-r--r-- 1 apache apache 175 Aug 7 19:55 /var/www/html/index.html
stapp02 | CHANGED | rc=0 >>
-rw-r--r-- 1 apache apache 175 Aug 7 19:55 /var/www/html/index.html
stapp03 | CHANGED | rc=0 >>
-rw-r--r-- 1 apache apache 175 Aug 7 19:55 /var/www/html/index.html
| Issue / Error | Cause | Solution |
|---|---|---|
Destination file /var/www/html/index.html does not exist |
blockinfile defaults to editing existing files unless told to create new ones. |
Include create: yes directive in ansible.builtin.blockinfile parameters. |
chown failed: Group/User apache does not exist |
owner: apache task executed before installing httpd (which creates the apache user account). |
Ensure yum: name=httpd task runs before modifying /var/www/html/index.html. |
Permission denied: /var/www/html/index.html |
Missing privilege escalation (sudo) when writing to system web root. |
Ensure become: yes is specified in the playbook header. |
HTTP 403 Forbidden |
Incorrect permissions on /var/www/html/index.html or SELinux context misconfiguration. |
Ensure permissions are set to 0644 (mode: '0644') and ownership is apache:apache. |
yum) before service start (service) and file creation (blockinfile) tasks to ensure required runtime accounts (apache) exist.mode: '0644') in Ansible playbooks so YAML interpreters do not convert octal values to decimal integers.ansible-playbook -i inventory playbook.yml should report changed=0 across all tasks on subsequent runs.