diff --git a/DockerEntrypoint.sh b/DockerEntrypoint.sh
index 38786b14..9105f965 100644
--- a/DockerEntrypoint.sh
+++ b/DockerEntrypoint.sh
@@ -27,6 +27,16 @@ failregex = \[LIMIT_IP\]\s*Email\s*=\s*.+\s*\|\|\s*Disconnect
ignoreregex =
EOF
+ # Ports to exempt from the ban so an over-limit proxy client can never lock
+ # the administrator out of SSH or the panel. The ban still covers every other
+ # TCP port (including all Xray inbounds), so IP-limit keeps working for inbounds
+ # added later without regenerating these files.
+ SSH_PORTS=$(grep -oE '^[[:space:]]*Port[[:space:]]+[0-9]+' /etc/ssh/sshd_config 2>/dev/null | grep -oE '[0-9]+' | paste -sd, -)
+ [ -z "$SSH_PORTS" ] && SSH_PORTS="22"
+ PANEL_PORT=$(/app/x-ui setting -show true 2>/dev/null | grep -Eo 'port: .+' | awk '{print $2}')
+ EXEMPT_PORTS="$SSH_PORTS"
+ [ -n "$PANEL_PORT" ] && EXEMPT_PORTS="$EXEMPT_PORTS,$PANEL_PORT"
+
cat > /etc/fail2ban/action.d/3x-ipl.conf << EOF
[INCLUDES]
before = iptables-allports.conf
@@ -42,16 +52,17 @@ actionstop = -D -p -j f2b-
actioncheck = -n -L | grep -q 'f2b-[ \t]'
-actionban = -I f2b- 1 -s -j
+actionban = -I f2b- 1 -s -p -m multiport ! --dports -j
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = [IP] = banned for seconds." >> $LOG_FOLDER/3xipl-banned.log
-actionunban = -D f2b- -s -j
+actionunban = -D f2b- -s -p -m multiport ! --dports -j
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = [IP] = unbanned." >> $LOG_FOLDER/3xipl-banned.log
[Init]
name = default
protocol = tcp
chain = INPUT
+exemptports = $EXEMPT_PORTS
EOF
fail2ban-client -x start
diff --git a/x-ui.sh b/x-ui.sh
index 7bcc5022..9b79d933 100644
--- a/x-ui.sh
+++ b/x-ui.sh
@@ -2248,6 +2248,18 @@ failregex = \[LIMIT_IP\]\s*Email\s*=\s*.+\s*\|\|\s*Disconnect
ignoreregex =
EOF
+ # Ports to exempt from the ban so an over-limit proxy client can never lock
+ # the administrator out of SSH or the panel. The ban still covers every other
+ # TCP port (including all Xray inbounds), so IP-limit keeps working for inbounds
+ # added later without regenerating these files.
+ local ssh_ports
+ ssh_ports=$(grep -oP '^[[:space:]]*Port[[:space:]]+\K[0-9]+' /etc/ssh/sshd_config 2>/dev/null | paste -sd, -)
+ [[ -z "${ssh_ports}" ]] && ssh_ports="22"
+ local panel_port
+ panel_port=$(${xui_folder}/x-ui setting -show true 2>/dev/null | grep -Eo 'port: .+' | awk '{print $2}')
+ local exempt_ports="${ssh_ports}"
+ [[ -n "${panel_port}" ]] && exempt_ports="${exempt_ports},${panel_port}"
+
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
[INCLUDES]
before = iptables-allports.conf
@@ -2263,16 +2275,17 @@ actionstop = -D -p -j f2b-
actioncheck = -n -L | grep -q 'f2b-[ \t]'
-actionban = -I f2b- 1 -s -j
+actionban = -I f2b- 1 -s -p -m multiport ! --dports -j
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = [IP] = banned for seconds." >> ${iplimit_banned_log_path}
-actionunban = -D f2b- -s -j
+actionunban = -D f2b- -s -p -m multiport ! --dports -j
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = [IP] = unbanned." >> ${iplimit_banned_log_path}
[Init]
name = default
protocol = tcp
chain = INPUT
+exemptports = ${exempt_ports}
EOF
echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}"