How to Install and Secure MariaDB 11 on Rocky Linux 9 using Ansible

Step-by-step guide on installing and securing MariaDB 11 on Rocky Linux 9 with Ansible automation

Introduction

Ansible is a powerful open-source automation tool for provisioning, configuration management, and application deployment. It enables infrastructure as code, making it easier to manage systems at scale.

MariaDB is a community-driven fork of MySQL, widely used in production environments as part of the LAMP/LEMP stack. With Rocky Linux 9 becoming a preferred enterprise-grade distribution, installing and securing MariaDB 11 properly is critical.

This guide walks you step by step on how to install, initialize, and secure MariaDB 11 on Rocky Linux 9 using Ansible.

Creating the Ansible Playbook

We begin by defining a playbook that installs and configures MariaDB 11 on our Rocky Linux 9 target host.

1
2
3
4
5
6
7
8
---
- name: Install and configure MariaDB 11 on Rocky Linux 9
  hosts: citizix-db-srv
  become: yes
  gather_facts: no

  vars:
    mariadb_root_password: "dnJd1QTQO9JAABF7iDlv"

Explanation

  • hosts: Defines the inventory group or server to run against.
  • become: yes: Ensures the tasks run with elevated privileges.
  • vars: Stores variables such as the MariaDB root password.

Updating Packages and Adding MariaDB Repository

First, ensure the system is up to date and add the official MariaDB 11 repository.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- name: Ensure dnf cache is up to date
  ansible.builtin.dnf:
    update_cache: yes
    state: latest

- name: Download MariaDB repository setup script
  ansible.builtin.get_url:
    url: https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
    dest: /tmp/mariadb_repo_setup
    mode: '0755'

- name: Add MariaDB 11 repositories
  ansible.builtin.command: /tmp/mariadb_repo_setup --mariadb-server-version=11.8
  args:
    creates: /etc/yum.repos.d/mariadb.repo

- name: Clean up temporary script
  ansible.builtin.file:
    path: /tmp/mariadb_repo_setup
    state: absent

- name: Disable AppStream MariaDB module
  ansible.builtin.shell: |
    dnf -qy module disable mariadb
    dnf module reset mariadb -y

This ensures we’re pulling MariaDB 11.8 from the official repository instead of the older AppStream module.

Installing MariaDB Server

Now install the database server and required dependencies.

1
2
3
4
5
6
7
8
- name: Install MariaDB server and dependencies
  ansible.builtin.dnf:
    name:
      - mariadb-server
      - MariaDB-client
      - MariaDB-backup
      - python3-PyMySQL
    state: present

The python3-PyMySQL package is required for Ansible to manage MariaDB users and databases.

Starting and Enabling MariaDB

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
- name: Enable and start MariaDB service
  ansible.builtin.systemd:
    name: mariadb
    state: started
    enabled: yes

- name: Wait for MariaDB to be ready
  ansible.builtin.wait_for:
    port: 3306
    delay: 10
    timeout: 60

This ensures MariaDB is running and ready to accept connections.

Securing MariaDB Installation

By default, MariaDB ships with insecure defaults. We’ll configure secure access by:

  • Restricting root login to localhost.
  • Removing anonymous users.
  • Disallowing remote root logins.
  • Removing the test database.
  • Flushing privileges to apply changes.

Restrict root login

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
- name: Ensure root user can only login from localhost
  mysql_user:
    login_password: "{{ mariadb_root_password }}"
    check_implicit_admin: yes
    name: root
    host: "{{ item }}"
    password: "{{ mariadb_root_password }}"
    state: present
  with_items:
    - localhost
    - 127.0.0.1
    - ::1

Apply hardening steps

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
- name: Remove anonymous users
  command: mysql -p{{ mariadb_root_password }} -ne "DELETE FROM mysql.user WHERE User=''"
  changed_when: false

- name: Disallow root login remotely
  command: mysql -p{{ mariadb_root_password }} -ne "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost','127.0.0.1','::1')"
  changed_when: false

- name: Remove test database and access to it
  command: |
    mysql -p{{ mariadb_root_password }} -ne "{{ item }}"
  with_items:
    - DROP DATABASE IF EXISTS test
    - DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%'
  changed_when: false

- name: Reload privilege tables
  command: mysql -p{{ mariadb_root_password }} -ne "FLUSH PRIVILEGES"
  changed_when: false

Firewall Configuration

Since MariaDB should not be exposed to the internet in most cases, disable the database port in firewalld:

1
2
3
4
5
- name: Configure firewall for MariaDB
  ansible.builtin.firewalld:
    port: 3306/tcp
    permanent: yes
    state: disabled

If you do need remote access for a specific application or network, adjust firewall rules accordingly.

Full Playbook

Here’s the complete playbook for convenience:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
---
- name: Install and configure MariaDB 11 on Rocky Linux 9
  hosts: citizix-db-srv
  become: yes
  gather_facts: no

  vars:
    mariadb_root_password: "dnJd1QTQO9JAABF7iDlv"

  pre_tasks:
    - name: Ensure dnf cache is up to date
      ansible.builtin.dnf:
        update_cache: yes
        state: latest

  tasks:
    - name: Download MariaDB repository setup script
      ansible.builtin.get_url:
        url: https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
        dest: /tmp/mariadb_repo_setup
        mode: '0755'
        validate_certs: yes

    - name: Add MariaDB 11 repositories
      ansible.builtin.command: /tmp/mariadb_repo_setup --mariadb-server-version=11.8
      args:
        creates: /etc/yum.repos.d/mariadb.repo

    - name: Clean up temporary script
      ansible.builtin.file:
        path: /tmp/mariadb_repo_setup
        state: absent

    - name: Disable AppStream MariaDB module
      ansible.builtin.shell: |
        dnf -qy module disable mariadb
        dnf module reset mariadb -y

    - name: Install MariaDB server and dependencies
      ansible.builtin.dnf:
        name:
          - mariadb-server
          - MariaDB-client
          - MariaDB-backup
          - python3-PyMySQL     # required by mysql_* modules
        state: present
      tags: install

    - name: Verify MariaDB installation
      ansible.builtin.command: mariadb -V
      register: mariadb_version
      changed_when: false

    - name: Display MariaDB version
      ansible.builtin.debug:
        var: mariadb_version.stdout

    - name: Enable and start MariaDB service
      ansible.builtin.systemd:
        name: mariadb
        state: started
        enabled: yes

    - name: Wait for MariaDB to be ready
      ansible.builtin.wait_for:
        port: 3306
        delay: 10
        timeout: 60

    - name: Ensure root user can only login from localhost
      mysql_user:
        login_password: "{{ mariadb_root_password }}"
        check_implicit_admin: yes
        name: root
        host: "{{ item }}"
        password: "{{ mariadb_root_password }}"
        state: present
      with_items:
        - localhost
        - 127.0.0.1
        - ::1

    - name: Create MySQL configuration file for root access
      ansible.builtin.copy:
        content: |
          [client]
          user=root
          password={{ mariadb_root_password }}

          [mysql]
          user=root
          password={{ mariadb_root_password }}
        dest: /root/.my.cnf
        mode: '0600'

    - name: Reload privilege tables
      command: |
        mysql -p{{ mariadb_root_password }} -ne "{{ item }}"
      with_items:
        - FLUSH PRIVILEGES
      changed_when: False

    - name: Remove anonymous users
      command: |
        mysql -p{{ mariadb_root_password }} -ne "{{ item }}"
      with_items:
        - DELETE FROM mysql.user WHERE User=''
      changed_when: False

    - name: Disallow root login remotely
      command: |
        mysql -p{{ mariadb_root_password }} -ne "{{ item }}"
      with_items:
        - DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1')
      changed_when: False

    - name: Remove test database and access to it
      command: |
        mysql -p{{ mariadb_root_password }} -ne "{{ item }}"
      with_items:
        - DROP DATABASE IF EXISTS test
        - DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%'
      changed_when: False

    - name: Reload privilege tables
      command: |
        mysql -p{{ mariadb_root_password }} -ne "{{ item }}"
      with_items:
        - FLUSH PRIVILEGES
      changed_when: False

    - name: Remove MySQL configuration file (cleanup)
      ansible.builtin.file:
        path: /root/.my.cnf
        state: absent

    - name: Configure firewall for MariaDB
      ansible.builtin.firewalld:
        port: 3306/tcp
        permanent: yes
        state: disabled

  handlers:
    - name: restart mariadb
      ansible.builtin.systemd:
        name: mariadb
        state: restarted

Running the Playbook

Save the playbook as setup-mariadb11.yaml and your inventory as hosts.yaml. Then run:

1
ansible-playbook -i hosts.yaml setup-mariadb11.yaml -vv

Conclusion

In this guide, we used Ansible to install and secure MariaDB 11 on Rocky Linux 9. By automating the installation and hardening steps, you ensure a consistent, secure, and repeatable setup for your database servers.

This method is scalable and works across multiple Rocky Linux 9 servers, making it ideal for production environments.

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy