Configure fail2ban with webhooks and Telegram on Ubuntu 22.04

Redes & Segurança · By · Published on · 8 min read

Wood texture with visible rings and grain
Neutral and professional option for emphasis, without logos, legible text, or people Photo: Pexels / Pixabay

In this tutorial you'll configure fail2ban on Ubuntu 22.04 to trigger webhooks and send real‑time alerts to Telegram. We'll create a notification script, register a custom action, and attach everything to an SSHD jail. The goal is to block brute‑force attempts and notify you quickly with a simple, repeatable setup.

Prerequisites, goals and installing fail2ban

You need an Ubuntu 22.04 system with root access and outbound HTTP/HTTPS. Have a Telegram bot (token and chat ID) and, optionally, a webhook endpoint. Caution: incorrect rules can lock you out. Test from another host or use ignoreip.

Goals: install fail2ban, create a notification script (webhook + Telegram), register an action, and apply it to an SSHD jail.

If you don't have a basic firewall yet, see How to set up a firewall on Ubuntu with UFW. For broader hardening, check the Linux VPS security checklist.

  • Root access or passwordless sudo
  • curl installed for HTTP calls
  • Telegram bot (token) and chat ID
  • Optional: webhook URL and secret (HMAC)
# Atualize pacotes, instale e habilite
sudo apt update && sudo apt install -y fail2ban curl
sudo systemctl enable --now fail2ban

# Verifique versão, status e logs
fail2ban-client -V || true
sudo systemctl status fail2ban --no-pager
sudo tail -n 50 /var/log/fail2ban.log || true

Overview of the flow (what triggers what)

fail2ban reads logs. When a failure pattern exceeds the threshold it applies a ban (for example via iptables) and runs an action. We'll create an action that calls a script. The script posts to your webhook and sends a Telegram alert.

You can adapt this flow for Nginx, Postfix and other services. To reduce SSH attack surface, consider a bastion: Set up an SSH bastion on Ubuntu 22.04.

Step Component Example
Failure event Log (sshd) Multiple wrong passwords
Detection Filter + jail (fail2ban) maxretry exceeded
Blocking Ban action (iptables) DROP the offending IP
Notification Custom action Script calls webhook and Telegram

Telegram: create a bot and store variables securely

Create a Telegram bot via BotFather and get the token. Find your chat ID after sending /start to the bot. We'll store these values in a file readable only by root.

We'll also allow configuring an optional webhook URL and an HMAC secret. Keep the file permission set to 600.

  • TELEGRAM_BOT_TOKEN: the bot token
  • TELEGRAM_CHAT_ID: chat/group/channel ID
  • WEBHOOK_URL: automation webhook (optional)
  • WEBHOOK_SECRET: key to sign payloads (optional)
# Crie o arquivo de variáveis sensíveis
sudo tee /etc/fail2ban/telegram.env >/dev/null <<'EOF'
TELEGRAM_BOT_TOKEN="coloque_o_token_aqui"
TELEGRAM_CHAT_ID="coloque_o_chat_id_aqui"
# Opcional: envie também para um webhook de automação
WEBHOOK_URL=""
WEBHOOK_SECRET=""
EOF

# Restrinja permissões
sudo chown root:root /etc/fail2ban/telegram.env
sudo chmod 600 /etc/fail2ban/telegram.env

Notification script: webhook + Telegram

The script receives fail2ban data (IP, jail, event), builds a JSON payload and sends: 1) a POST to your webhook with an optional HMAC signature; 2) a message to Telegram. Test manually before integrating.

The Telegram message is plain text. For automations, route the webhook into n8n, Zapier or Make.

  • Optional HMAC-SHA256 signature in X-Signature header
  • Clear JSON payload: ip, jail, event, timestamps
  • Independent outputs: webhook and Telegram
sudo tee /usr/local/bin/f2b_notify.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

ENV_FILE="/etc/fail2ban/telegram.env"
[[ -f "$ENV_FILE" ]] && source "$ENV_FILE"

IP="${1:-}"
JAIL="${2:-}"
LOGPATH="${3:-}"
EVENT="${4:-ban}"
FAILURES="${5:-}"
FINDTIME="${6:-}"
BANTIME="${7:-}"

TS="$(date -Iseconds)"
HOSTNAME="$(hostname -f 2>/dev/null || hostname)"

# Monta payload JSON mínimo
PAYLOAD=$(cat <<JSON
{
  "host": "${HOSTNAME}",
  "jail": "${JAIL}",
  "ip": "${IP}",
  "event": "${EVENT}",
  "failures": "${FAILURES}",
  "findtime": "${FINDTIME}",
  "bantime": "${BANTIME}",
  "logpath": "${LOGPATH}",
  "timestamp": "${TS}"
}
JSON
)

# 1) Envio para webhook (opcional)
if [[ -n "${WEBHOOK_URL:-}" ]]; then
  SIG=""
  if [[ -n "${WEBHOOK_SECRET:-}" ]]; then
    SIG=$(printf '%s' "$PAYLOAD" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -binary | base64)
  fi
  curl -fsS -X POST "$WEBHOOK_URL" \
    -H 'Content-Type: application/json' \
    ${SIG:+-H "X-Signature: ${SIG}"} \
    --data-raw "$PAYLOAD" || true
fi

# 2) Envio para Telegram (opcional)
if [[ -n "${TELEGRAM_BOT_TOKEN:-}" && -n "${TELEGRAM_CHAT_ID:-}" ]]; then
  TEXT="[fail2ban] ${EVENT^^} em ${JAIL} — IP ${IP} — host ${HOSTNAME} — ${TS}"
  curl -fsS -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
    -H 'Content-Type: application/json' \
    -d "{\"chat_id\":\"${TELEGRAM_CHAT_ID}\",\"text\":\"${TEXT}\"}" || true
fi
EOF

sudo chmod +x /usr/local/bin/f2b_notify.sh

Register the custom action in fail2ban

Create an action file in action.d so the jail can call it on each ban and unban. We'll pass the required variables to the script.

We'll keep actionban and actionunban. The actual ban mechanism (iptables) remains defined in the jail.

# /etc/fail2ban/action.d/f2b-notify-webhook-telegram.conf
[Definition]
actionstart =
actionstop =
# Evento de ban
actionban = /usr/local/bin/f2b_notify.sh "<ip>" "<name>" "<logpath>" "ban" "<failures>" "<findtime>" "<bantime>"
# Evento de unban (informativo)
actionunban = /usr/local/bin/f2b_notify.sh "<ip>" "<name>" "<logpath>" "unban" "0" "0" "0"

[Init]
name = default

SSHD jail using iptables and the new action

Now create a jail for sshd. It blocks via iptables and triggers our notification action. Add your IP to ignoreip so you don't lock yourself out.

Adjust maxretry, findtime and bantime according to your risk profile and traffic volume.

  • systemd backend works well on Ubuntu 22.04
  • iptables-nft is default; iptables-multiport still valid
  • ignoreip accepts lists and CIDRs
# /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled = true
port = 22
logpath = %(sshd_log)s
backend = systemd
maxretry = 5
findtime = 10m
bantime = 1h
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 192.168.0.0/16

# Ação de bloqueio (iptables)
banaction = iptables-multiport

# Notificações
action = f2b-notify-webhook-telegram

Testing: reload, simulate failures and validate alerts

Time to test. Reload fail2ban, check the sshd jail and simulate wrong password logins from another host. Confirm the ban, the action execution and that alerts arrive on Telegram and the webhook.

You can also force an unban to validate the unban alert.

  • Use a separate machine to test SSH failures
  • Monitor /var/log/fail2ban.log in real time
  • If nothing reaches Telegram, verify the token and chat ID
# Recarregar e inspecionar
sudo fail2ban-client reload
sudo fail2ban-client status
sudo fail2ban-client status sshd

# Acompanhar logs
sudo tail -f /var/log/fail2ban.log &

# (Em outro host) force tentativas erradas de SSH
# ssh usuario@SEU_IP  # digite senhas erradas

# Checar IPs banidos e desbanir para testar unban
sudo fail2ban-client status sshd
sudo fail2ban-client set sshd unbanip 1.2.3.4  # troque pelo IP de teste

Troubleshooting and useful improvements

Some issues are common and easy to fix. Below are tips and possible extensions. For observability, consider exporting metrics to Prometheus and building Grafana dashboards: see Prometheus & Grafana with Docker Compose on a VPS.

  • No alerts: check /etc/fail2ban/telegram.env permission is 600 and the script is executable.
  • Corporate proxy: export http_proxy/https_proxy for the service.
  • UFW active: iptables-multiport usually coexists; if it conflicts, use ufw as the banaction.
  • Telegram message shows strange characters: simplify the TEXT variable.
  • Webhook not receiving: test manually with curl and verify an HTTP 2xx response.
  • Reduce noise: tune filters, maxretry and findtime; consider incremental banaction.
  • Signature: validate X-Signature on your receiver (n8n, Zapier, Make).
# Teste manual do webhook (substitua URL)
WEBHOOK_URL="https://example.com/webhook"
payload='{"hello":"world"}'
curl -v -X POST "$WEBHOOK_URL" -H 'Content-Type: application/json' --data-raw "$payload"

Frequently asked questions

Does this work with services other than SSH (sshd)?

Yes. Create or enable jails for Nginx, Postfix and Apache, point them to the correct logs and adjust maxretry, findtime and bantime. Reuse the same action to standardize alerts.

How do I find my Telegram chat ID?

Start a conversation with your bot, send a message and use a utility bot to get the chat ID. You can also read the update JSON from the Telegram API after sending the first message.

Can UFW and fail2ban run together?

Generally yes. fail2ban applies temporary rules while UFW maintains the base policy. If they conflict, switch the ban action to ufw instead of iptables-multiport.

How do I avoid banning myself by mistake?

Use ignoreip with your fixed IP or a trusted CIDR. Test from another machine and, if needed, temporarily reduce the bantime. If you get locked out, use the console to run unbanip.

Can I sign the webhook payload to validate origin?

Yes. Set WEBHOOK_SECRET. The script computes an HMAC‑SHA256 of the body and sends it in the X-Signature header (base64). On the receiver, recompute and compare to validate.

How do I integrate with n8n, Zapier or Make?

Point WEBHOOK_URL to a public webhook created in the tool. The automation can route alerts to email, SMS, Slack or create tickets. Validate X-Signature before processing.

Need this adapted to your environment?

Want to expand to other jails, reduce false positives and standardize alerts? We can review your filters, actions and automation stack.

Discuss security and automation

See also