diff --git a/ansible/cluster.yml b/ansible/cluster.yml
index 1a2c83e24576ac7a3e8305d6d0e906442d4c606a..1e05580a08dd53722fb06fc2ba22de631c77f785 100644
--- a/ansible/cluster.yml
+++ b/ansible/cluster.yml
@@ -12,3 +12,4 @@
     - { name: 'ssl_cert', tags: 'ssl_cert', when: enable_ssl_certs }
     - { name: 'rsyslog_config', tags: 'rsyslog_config', when: enable_rsyslog_config }
     - { name: 'rewrite_map', tags: 'rewrite_map', when: enable_rewrite_map }
+    - { name: 'fail2ban', tags: 'fail2ban', when: enable_fail2ban }
diff --git a/ansible/group_vars/all b/ansible/group_vars/all
index 7055312d9b05399ad03af400131849912e764840..51a889a699b39a511f202f93416092816b0137bb 100644
--- a/ansible/group_vars/all
+++ b/ansible/group_vars/all
@@ -50,7 +50,6 @@
 # ssh proxy
   enable_ssh_proxy_config: false
   sshpiper_dest_dir: "/opt/sshpiper"
-  fail2ban_cidr_list: "127.0.0.1/8"
 
 # rsyslog
   enable_rsyslog_config: false
@@ -75,3 +74,10 @@
 
 # account app
   account_app_port: 8000
+
+# fail2ban
+  enable_fail2ban: true
+  maxretry: 1
+  findtime: 600
+  bantime: 1200
+  fail2ban_white_list: "127.0.0.1/8"
diff --git a/ansible/roles/fail2ban/tasks/main.yml b/ansible/roles/fail2ban/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2f7d96e68acb65478a263291c9f3e3092612ff94
--- /dev/null
+++ b/ansible/roles/fail2ban/tasks/main.yml
@@ -0,0 +1,46 @@
+---
+
+- name: Install fail2ban
+  ansible.builtin.package:
+    name: "{{ item }}"
+    state: present
+  loop:
+    - fail2ban
+    - fail2ban-firewalld
+
+- name: Configure fail2ban
+  ansible.builtin.template:
+    src: "{{ item.src }}"
+    dest: "{{ item.dest }}"
+    backup: true
+  loop:
+    - { src: 'jail.local.j2', dest: '/etc/fail2ban/jail.local' }
+    - { src: 'sshpiperd_filter.local.j2', dest: '/etc/fail2ban/filter.d/sshpiperd.local' }
+    - { src: 'sshpiperd_jail.local.j2', dest: '/etc/fail2ban/jail.d/sshpiperd.local' }
+
+- name: Activate the firewalld support for fail2ban
+  ansible.builtin.command:
+    cmd: mv /etc/fail2ban/jail.d/00-firewalld.conf /etc/fail2ban/jail.d/00-firewalld.local
+
+- name: Configure firewalld to allow ssh and sshpiper traffic
+  ansible.posix.firewalld:
+    port: "{{ item }}"
+    zone: public
+    state: enabled
+    permanent: true
+  loop:
+    - 2222/tcp
+    - 22/tcp
+
+- name: Enable and start firewalld
+  ansible.builtin.service:
+    name: firewalld
+    enabled: true
+    state: restarted
+
+- name: Enable and start fail2ban
+  ansible.builtin.service:
+    name: fail2ban
+    enabled: true
+    state: restarted
+
diff --git a/ansible/roles/fail2ban/templates/jail.local.j2 b/ansible/roles/fail2ban/templates/jail.local.j2
new file mode 100644
index 0000000000000000000000000000000000000000..87f9e4fa06f0ca75ed77d0a2361262f94859d91d
--- /dev/null
+++ b/ansible/roles/fail2ban/templates/jail.local.j2
@@ -0,0 +1,7 @@
+[DEFAULT]
+banaction = firewalld
+bantime  = {{ bantime }}
+ignoreip = {{ fail2ban_white_list }}
+
+[sshd]
+enabled = true
diff --git a/ansible/roles/fail2ban/templates/sshpiperd_filter.local.j2 b/ansible/roles/fail2ban/templates/sshpiperd_filter.local.j2
new file mode 100644
index 0000000000000000000000000000000000000000..f5a6081ec28c490ab24ff3f4c96c3b08bb86b9da
--- /dev/null
+++ b/ansible/roles/fail2ban/templates/sshpiperd_filter.local.j2
@@ -0,0 +1,22 @@
+# Refer to https://github.com/fail2ban/fail2ban/wiki/Developing-Regex-in-Fail2ban for developing regex using fail2ban
+#
+[INCLUDES]
+before = common.conf
+
+[DEFAULT]
+_daemon = sshpiperd
+__iso_datetime = "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2}|Z)"
+__pref = time=%(__iso_datetime)s level=(?:debug|error)
+
+[Definition]
+# Define the prefix regex for the log lines
+prefregex = ^<F-MLFID>%(__prefix_line)s%(__pref)s</F-MLFID>\s+<F-CONTENT>.+</F-CONTENT>$
+
+# Failregex to match the specific failure log lines (prefregex is automatically included)
+failregex = ^msg="connection from .*failtoban: ip <HOST> too auth many failures"$
+
+ignoreregex =
+
+mode = normal
+
+maxlines = 1
diff --git a/ansible/roles/fail2ban/templates/sshpiperd_jail.local.j2 b/ansible/roles/fail2ban/templates/sshpiperd_jail.local.j2
new file mode 100644
index 0000000000000000000000000000000000000000..681212ccaf59595c5dd6097e188286db85225dbd
--- /dev/null
+++ b/ansible/roles/fail2ban/templates/sshpiperd_jail.local.j2
@@ -0,0 +1,9 @@
+# This configuration will block the remote host after {{maxretry}} failed SSH login attempts.
+[sshpiperd]
+enabled  = true
+filter   = sshpiperd
+logpath  = /var/log/messages
+port     = 22
+maxretry = {{ maxretry }}
+backend  = auto
+findtime = {{ findtime }}
diff --git a/ansible/roles/ssh_proxy_config/tasks/main.yml b/ansible/roles/ssh_proxy_config/tasks/main.yml
index fb51f9fe65cedd2993af4199eff9e04f8c1c1b2a..30bac2abbe90860eabba3b051a4c212fa4f8c6b5 100644
--- a/ansible/roles/ssh_proxy_config/tasks/main.yml
+++ b/ansible/roles/ssh_proxy_config/tasks/main.yml
@@ -10,45 +10,3 @@
     name: sshpiperd
     enabled: true
     state: restarted
-
-- name: Install firewalld
-  ansible.builtin.package:
-    name: firewalld
-    state: present
-
-- name: Configure firewalld
-  ansible.posix.firewalld:
-    port: 2222/tcp
-    zone: public
-    state: enabled
-    permanent: true
-
-- name: Enable and start firewalld
-  ansible.builtin.service:
-    name: firewalld
-    enabled: true
-    state: restarted
-
-- name: Install fail2ban
-  ansible.builtin.package:
-    name: "{{ item }}"
-    state: present
-  loop:
-    - fail2ban
-    - fail2ban-firewalld
-
-- name: Configure fail2ban
-  ansible.builtin.template:
-    src: jail.local.j2
-    dest: "/etc/fail2ban/jail.local"
-    backup: true
-
-- name: Activate the firewall support
-  ansible.builtin.command:
-    cmd: mv /etc/fail2ban/jail.d/00-firewalld.conf /etc/fail2ban/jail.d/00-firewalld.local
-
-- name: Enable and start fail2ban
-  ansible.builtin.service:
-    name: fail2ban
-    enabled: true
-    state: restarted
diff --git a/ansible/roles/ssh_proxy_config/templates/jail.local.j2 b/ansible/roles/ssh_proxy_config/templates/jail.local.j2
deleted file mode 100644
index d5898e63b7cbb1046ac28d59062b1ede7d148809..0000000000000000000000000000000000000000
--- a/ansible/roles/ssh_proxy_config/templates/jail.local.j2
+++ /dev/null
@@ -1,7 +0,0 @@
-[DEFAULT]
-banaction = firewalld
-bantime  = 1200
-ignoreip = {{ fail2ban_cidr_list }}
-
-[sshd]
-enabled = true