🛡️Building a Linux Web Server with Terraform & Ansible – Part 3: Securing the Server with Ansible Roles

So we have our droplet and our admin user. The next step is securing our web server before we start deploying app.


🧹 Let’s Tidy Things Up: From Playbooks to Roles

Previously, we created a separate playbook file for each task (like creating the admin user). While this works for small projects, it quickly becomes unmanageable.

Instead, we’ll use Ansible roles, which help organize related tasks, handlers, variables, and files into reusable units.

📘 Official documentation:
👉 Ansible Roles – Reuse and Organize Your Playbooks


📁 Step 1: Set Up the Role Directory

Let’s create a folder structure for our first role:

mkdir -p roles/security/tasks
mkdir -p roles/security/handlers

We’ll be configuring the handlers directory in a moment, but first let’s define the security tasks.


🔐 Step 2: Define the Security Tasks

Create the file:
roles/security/tasks/main.yml

# roles/security/tasks/main.yml
---
- name: Update and upgrade APT packages
  apt: update_cache=yes upgrade=dist

- name: Install essential packages
  apt:
    name:
      - curl
      - unzip
      - zip
      - software-properties-common
    state: present

# SSH Security
- name: Disable root SSH login
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^PermitRootLogin'
    line: 'PermitRootLogin no'
    state: present

- name: Disable password authentication
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^PasswordAuthentication'
    line: 'PasswordAuthentication no'
    state: present
  notify:
      - Restart ssh

# UFW
- name: Install ufw
  apt: name=ufw state=present update_cache=yes

- name: Configure ufw
  ufw: rule=allow name=OpenSSH

- name: Enable UFW
  ufw: state=enabled

# Fail2ban
- name: Install fail2ban
  apt: name=fail2ban state=present
  notify:
      - Enable fail2ban

# Unattended Upgrades
- name: Install unattended-upgrades
  apt: name=unattended-upgrades state=present

- name: Enable unattended security updates
  copy:
    dest: /etc/apt/apt.conf.d/20auto-upgrades
    content: |
      APT::Periodic::Update-Package-Lists "1";
      APT::Periodic::Unattended-Upgrade "1";

🔍 Let’s Break It Down

APT upgrade: Ensures the system is up-to-date.

Essential packages: Useful tools that we’ll need later.

SSH hardening:

  • Disables root login and password-based login.
  • Only users with SSH keys (like our admin user) can connect.

UFW (Uncomplicated Firewall):

  • Blocks all traffic by default, then allows only SSH.

Fail2ban:

  • Helps block malicious login attempts like brute force SSH attacks.

Unattended upgrades:

Automatically installs security updates in the background.


🔁 Step 3: Add Service Handlers

We need handlers to restart or enable services after certain changes.

Create the file:
roles/security/handlers/main.yml

# roles/security/handlers/main.yml
---
- name: Restart ssh
  service: name=ssh state=restarted

- name: Enable fail2ban
  service: name=fail2ban enabled=yes state=started

📜 Step 4: Add the Role to Your Playbook

Create the main playbook file:
main.yml

# main.yml
- hosts: all
  gather_facts: false
  become: true

  vars:
    username: admin

  remote_user: "{{ username }}"

  roles:
  - role: roles/basic-setup

🚀 Step 5: Run the Playbook

play main.yml

Then, test your setup:

ssh root@<droplet ip>

Should result in an error:

root@142.93.170.130: Permission denied (publickey).

✅ That means root login is now disabled—just what we want.


🧪 Step 6: Verify the Setup

🔥 UFW (Firewall)

SSH into the droplet with your admin user and run:

sudo ufw status

You should see output like:

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere

🔒 Fail2ban

To verify fail2ban is running:

sudo systemctl status fail2ban

Look for something like:

Active: active (running)

For deeper inspection:

sudo fail2ban-client status

This shows which jails are active (e.g., sshd).

Or wait a little while and see bans comming in at:

sudo cat /var/log/fail2ban.log | grep -i ' ban'

.

🔁 Unattended Upgrades

To check that it’s working, run:

sudo cat /etc/apt/apt.conf.d/20auto-upgrades

You should see:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

You can also confirm the service is enabled with:

sudo systemctl status unattended-upgrades

✅ What We’ve Accomplished

In this article, we:

  • Organized our Ansible config using a role
  • Hardened the server by:
    • Disabling root SSH and password login
    • Setting up a basic firewall
    • Installing fail2ban for brute-force protection
    • Enabling automatic security updates
  • Created a reusable playbook for future provisioning

🔜 Coming Up Next…

In the next part of the series, we’ll install Nginx to serve our app and act as a reverse proxy. Stay tuned 👋

Leave a Reply

Your email address will not be published. Required fields are marked *