Skip to content

Instantly share code, notes, and snippets.

@smokeev-dev
Last active August 25, 2026 00:23
Show Gist options
  • Select an option

  • Save smokeev-dev/feffd1ec1a45a7899605d662bc6d2edc to your computer and use it in GitHub Desktop.

Select an option

Save smokeev-dev/feffd1ec1a45a7899605d662bc6d2edc to your computer and use it in GitHub Desktop.
Send Backups to Telegram πŸš€ FREE & SAFE

Telegram Backup Script (Ultimate Solution)

Back up your projects to Telegram with a fast, reliable workflow built around modern best practices. Large archives are split automatically, failed uploads are retried, and every Telegram API response is validated. Each backup is saved locally as a compact, timestamped .tar.zst archive, keeping a complete copy under your control.

Features

  • Fast multithreaded Zstandard compression
  • Timestamped backup filenames in UTC
  • Automatic splitting into 45 MiB parts
  • Sequential upload of all backup parts
  • Part numbering in Telegram captions
  • Automatic retries for network and API errors
  • Telegram API response validation
  • Configurable connection and upload timeouts
  • Local preservation of the complete archive
  • Automatic removal of temporary split files
  • Secure handling of the Telegram bot token through environment variables
  • Strict Bash error handling
  • Safe handling of paths containing spaces
  • Automatic dependency and directory validation
  • Protection against placing the backup directory inside the source directory
  • Restrictive permissions for generated backup files

Roadmap

  • Healthchecks.io monitoring and heartbeat reporting (soon)
  • Structured logging with configurable log levels (soon)
  • Client-side archive encryption with age (planned)
  • SHA-256 integrity verification (planned)
  • Automatic backup retention and local cleanup (planned)
  • Exclusion rules for caches, logs, and temporary files (planned)
  • PostgreSQL and MariaDB backup support (planned)
  • Docker volume backup support (planned)
  • Multi-project configuration profiles (planned)
  • Restore and verification helper (planned)
  • Success and failure notifications (planned)
  • Dry-run mode (planned)

Getting Started

1. Install the script

install -m 700 telegram-backup.sh /usr/local/sbin/telegram-backup

2. Create a Telegram bot

Create a bot using @BotFather and save the generated bot token. Send at least one message to the bot, then obtain the destination chat ID.

3. Create the configuration file

install -m 600 /dev/null /etc/telegram-backup.env
nano /etc/telegram-backup.env

Add the following configuration:

BACKUP_DIRECTORY="/telegram/backups"
PROJECT_DIRECTORY="/telegram/project"
TELEGRAM_CHAT_ID="123456789"
TELEGRAM_BOT_TOKEN="123456789:Your_Bot_Token"

Replace the example values with your actual directories and Telegram credentials. The backup directory must not be located inside the project directory.

4. Run the backup

set -a
source /etc/telegram-backup.env
set +a
/usr/local/sbin/telegram-backup

A successful execution returns:

Backup completed successfully: /telegram/backups/project_2026-08-25T01-30-00Z.tar.zst

Backup Format

The complete local backup is stored as:

project_2026-08-25T01-30-00Z.tar.zst

Backups larger than 45 MiB are uploaded to Telegram as numbered parts:

project_2026-08-25T01-30-00Z.tar.zst.part-0001
project_2026-08-25T01-30-00Z.tar.zst.part-0002
project_2026-08-25T01-30-00Z.tar.zst.part-0003

Temporary parts are deleted from the server after the upload. The complete .tar.zst archive remains in the configured backup directory.

Restoring a Backup

Extract a complete archive:

tar --zstd -xf project_2026-08-25T01-30-00Z.tar.zst

If the backup was downloaded from Telegram as multiple parts, combine them first:

cat project_2026-08-25T01-30-00Z.tar.zst.part-* > project_2026-08-25T01-30-00Z.tar.zst
tar --zstd -xf project_2026-08-25T01-30-00Z.tar.zst

Automated Backups

Example cron job for a daily backup at 03:30:

30 3 * * * set -a; . /etc/telegram-backup.env; set +a; /usr/local/sbin/telegram-backup >> /var/log/telegram-backup.log 2>&1
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
: "${BACKUP_DIRECTORY:?BACKUP_DIRECTORY is required}"
: "${PROJECT_DIRECTORY:?PROJECT_DIRECTORY is required}"
: "${TELEGRAM_CHAT_ID:?TELEGRAM_CHAT_ID is required}"
: "${TELEGRAM_BOT_TOKEN:?TELEGRAM_BOT_TOKEN is required}"
readonly PART_SIZE_BYTES=$((45 * 1024 * 1024))
readonly TELEGRAM_API_URL="https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}"
for dependency in curl realpath split stat tar zstd; do
if ! command -v "$dependency" >/dev/null 2>&1; then
printf 'Required command not found: %s\n' "$dependency" >&2
exit 127
fi
done
if [[ ! -d "$PROJECT_DIRECTORY" ]]; then
printf 'Project directory does not exist: %s\n' "$PROJECT_DIRECTORY" >&2
exit 1
fi
mkdir -p -- "$BACKUP_DIRECTORY"
PROJECT_DIRECTORY="$(realpath -- "$PROJECT_DIRECTORY")"
BACKUP_DIRECTORY="$(realpath -- "$BACKUP_DIRECTORY")"
if [[ "$BACKUP_DIRECTORY" == "$PROJECT_DIRECTORY" || "$BACKUP_DIRECTORY" == "$PROJECT_DIRECTORY"/* ]]; then
printf 'Backup directory must not be inside the project directory.\n' >&2
exit 1
fi
readonly PROJECT_DIRECTORY
readonly BACKUP_DIRECTORY
readonly PROJECT_NAME="$(basename -- "$PROJECT_DIRECTORY")"
readonly PROJECT_PARENT_DIRECTORY="$(dirname -- "$PROJECT_DIRECTORY")"
readonly BACKUP_TIMESTAMP="$(date -u '+%Y-%m-%dT%H-%M-%SZ')"
readonly ARCHIVE_NAME="${PROJECT_NAME}_${BACKUP_TIMESTAMP}.tar.zst"
readonly WORK_DIRECTORY="$(mktemp -d --tmpdir="$BACKUP_DIRECTORY" '.telegram-backup.XXXXXXXX')"
readonly TEMPORARY_ARCHIVE_PATH="$WORK_DIRECTORY/$ARCHIVE_NAME.partial"
readonly ARCHIVE_PATH="$BACKUP_DIRECTORY/$ARCHIVE_NAME"
readonly PARTS_DIRECTORY="$WORK_DIRECTORY/parts"
cleanup() {
rm -rf -- "$WORK_DIRECTORY"
}
trap cleanup EXIT
tar -C "$PROJECT_PARENT_DIRECTORY" -cf - "$PROJECT_NAME" | zstd -T0 -3 -q -o "$TEMPORARY_ARCHIVE_PATH"
mv -- "$TEMPORARY_ARCHIVE_PATH" "$ARCHIVE_PATH"
declare -a upload_files
if (( $(stat -c '%s' "$ARCHIVE_PATH") > PART_SIZE_BYTES )); then
mkdir -p -- "$PARTS_DIRECTORY"
split --bytes="$PART_SIZE_BYTES" --numeric-suffixes=1 --suffix-length=4 \
"$ARCHIVE_PATH" "$PARTS_DIRECTORY/${ARCHIVE_NAME}.part-"
shopt -s nullglob
upload_files=("$PARTS_DIRECTORY"/*)
shopt -u nullglob
else
upload_files=("$ARCHIVE_PATH")
fi
readonly total_files="${#upload_files[@]}"
if (( total_files == 0 )); then
printf 'No backup files were created.\n' >&2
exit 1
fi
for index in "${!upload_files[@]}"; do
upload_file="${upload_files[$index]}"
sequence_number=$((index + 1))
caption="${ARCHIVE_NAME} (${sequence_number}/${total_files})"
response="$(
curl \
--silent \
--show-error \
--fail-with-body \
--retry 5 \
--retry-all-errors \
--retry-delay 5 \
--connect-timeout 15 \
--max-time 1800 \
--form-string "chat_id=${TELEGRAM_CHAT_ID}" \
--form-string "caption=${caption}" \
--form "document=@${upload_file};type=application/octet-stream" \
"${TELEGRAM_API_URL}/sendDocument"
)"
if ! grep -Eq '"ok"[[:space:]]*:[[:space:]]*true' <<<"$response"; then
printf 'Telegram rejected file %s of %s.\n' "$sequence_number" "$total_files" >&2
exit 1
fi
done
printf 'Backup completed successfully: %s\n' "$ARCHIVE_PATH"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment