Telegram Alerts Without Leaking the Exit IP

TL;DR: While disk logs are disabled to preserve privacy, operators still need real-time awareness of access events, vault status, and bandwidth consumption. Out-of-band Telegram Bot API notifications deliver critical events without writing client source IPs to disk or revealing node public IPs.


Series Navigation: Building a VPS VPN Chain

This article is Part 3 of a 7-part series on building a multi-hop, fail-closed VPS VPN chain with Ansible, LUKS encrypted vaults, Telegram monitoring, and zero-log policy routing.

  1. Part 1: VPS Baseline: Install, Harden, and Disable Logs
  2. Part 2: Encrypted Vault for VPN Secrets and Residual Logs
  3. Part 3: Telegram Alerts Without Leaking the Exit IP
  4. Part 4: Ansible Playbook for Edge Hop Installation
  5. Part 5: VPN Chain and End-to-End Encryption Without Persisted Logs
  6. Part 6: VPN Failover Between VPS Nodes
  7. Part 7: Operational Tasks: Heal, Add, Remove, and Replace Hops

Event Notification Scope

To minimize metadata exposure while retaining operational visibility, configure Telegram notifications for three key events:

Event TypeTrigger Schedule / ConditionPayload Contents
SSH LoginReal-time on session openingHostname, username, timestamp
Daily StatusCron schedule (08:30 UTC daily)Uptime, system load, RAM/disk %, LUKS vault status, active peer counts
Traffic CapBandwidth threshold checkDaily/monthly vnstat totals, quota utilization percentage

Crucial rule: Never transmit the node’s public IP address in alert payloads. Identify nodes by their internal logical hostname (e.g., vpn-node-01).

SSH Login Alerts via PAM Exec

Use pam_exec in the SSH PAM stack to trigger a lightweight notification script whenever an SSH session opens.

Add the following line to /etc/pam.d/sshd:

session optional pam_exec.so seteuid /vault/bin/vpn-login-alert

Create the notifier script inside the encrypted vault at /vault/bin/vpn-login-alert:

#!/bin/bash
# /vault/bin/vpn-login-alert — transmit login notification asynchronously without disk logging
set -euo pipefail

# Read credentials from the encrypted LUKS vault (/vault/secrets)
[ -f /vault/secrets/telegram.token ] || exit 0
token=$(cat /vault/secrets/telegram.token)
chat_id=$(cat /vault/secrets/telegram.chat)

text="[ALERT] SSH login on ${HOSTNAME} | User: ${PAM_USER} | Time: $(date -u +%FT%TZ)"

# Dispatch HTTP request in background; suppress errors so login is never blocked
curl -fsS --max-time 8 \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg t "$text" --arg c "$chat_id" '{chat_id:$c, text:$t}')" \
  "https://api.telegram.org/bot${token}/sendMessage" >/dev/null 2>&1 &

Ensure /vault/bin/vpn-login-alert is executable (chmod +x) and bot token files stored inside the encrypted vault are restricted:

chmod 0755 /vault/bin/vpn-login-alert
chmod 0600 /vault/secrets/telegram.token /vault/secrets/telegram.chat

Daily Status Health Report

Configure a daily systemd timer or cron job at 08:30 UTC executing /vault/bin/vpn-daily-report to collect node metrics and dispatch a consolidated summary:

#!/bin/bash
# /vault/bin/vpn-daily-report
set -euo pipefail

[ -f /vault/secrets/telegram.token ] || exit 0
token=$(cat /vault/secrets/telegram.token)
chat_id=$(cat /vault/secrets/telegram.chat)

uptime_str=$(uptime -p)
load_str=$(cat /proc/loadavg | awk '{print $1, $2, $3}')
mem_free=$(free -m | awk '/Mem:/ {print $4"MB / "$2"MB"}')
vault_status=$(cryptsetup status vpn-vault >/dev/null 2>&1 && echo "ONLINE" || echo "LOCKED")
wg_peers=$(wg show wg0 peers 2>/dev/null | wc -l || echo "0")
f2b_bans=$(fail2ban-client status sshd 2>/dev/null | grep "Currently banned" | awk '{print $4}' || echo "0")

report=$(cat <<EOF
[STATUS] ${HOSTNAME} Daily Report
Uptime: ${uptime_str}
Load: ${load_str}
Memory Available: ${mem_free}
LUKS Vault: ${vault_status}
WireGuard Peers: ${wg_peers}
Active Fail2ban Bans: ${f2b_bans}
EOF
)

curl -fsS --max-time 10 \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg t "$report" --arg c "$chat_id" '{chat_id:$c, text:$t}')" \
  "https://api.telegram.org/bot${token}/sendMessage" >/dev/null 2>&1

Bandwidth Monitoring and Quota Alerts

Prevent provider overage charges by monitoring network interface statistics via vnstat.

Create /vault/bin/vpn-bandwidth-check:

#!/bin/bash
# /vault/bin/vpn-bandwidth-check
# Alert if monthly usage exceeds 90% of bandwidth allotment (e.g., 3 TB limit)
set -euo pipefail

MAX_MONTHLY_GB=3000
USED_GB=$(vnstat --json m 1 | jq -r '.interfaces[0].traffic.month[0].rx + .interfaces[0].traffic.month[0].tx' | awk '{print int($1 / 1073741824)}')

if [ "$USED_GB" -gt $((MAX_MONTHLY_GB * 90 / 100)) ]; then
  [ -f /vault/secrets/telegram.token ] || exit 0
  token=$(cat /vault/secrets/telegram.token)
  chat_id=$(cat /vault/secrets/telegram.chat)
  msg="[WARNING] ${HOSTNAME} bandwidth usage has reached ${USED_GB} GB / ${MAX_MONTHLY_GB} GB quota!"
  
  curl -fsS --max-time 8 \
    -H "Content-Type: application/json" \
    -d "$(jq -n --arg t "$msg" --arg c "$chat_id" '{chat_id:$c, text:$t}')" \
    "https://api.telegram.org/bot${token}/sendMessage" >/dev/null 2>&1
fi

Automation with Ansible

Inject notification files and scripts via Ansible without exposing tokens in process arguments or Git commits:

ansible-playbook -i hosts.yaml playbooks/vpn-node-security.yaml \
  -e target_vpn=vpn-1 \
  -e deadman_enabled=false

Store Telegram API bot keys in ansible-vault encrypted variable files.

Operational Best Practices

  1. Avoid ps Command Leaks: Never pass the API token directly as a command-line flag (e.g. curl https://api.telegram.org/bot<TOKEN>/...). Always read credentials from secure files or environment variables.
  2. Asynchronous Execution: Run PAM scripts in the background using & so API timeouts or Telegram connectivity issues never block user SSH logins.
  3. Payload Sanitization: Ensure error streams (stderr) are piped to /dev/null to prevent curl or jq errors from leaking tokens into systemic error logs.

Verification

Test the notification pipeline by initiating a new SSH session on custom port 28422 (or configured non-standard port):

ssh -p 28422 [email protected]

Verify that a Telegram message arrives in your designated channel within 2–3 seconds containing the hostname and timestamp without revealing the client IP.


Next in the series: Part 4: Ansible Playbook for Edge Hop Installation - Automating baseline setup, vault creation, and security alerts across nodes with idempotent Ansible playbooks.