Introduction

In this step-by-step guide, you'll create a fully functional Telegram bot from scratch that automatically monitors a list of proxies, measures response times, detects IP changes after rotation, checks geolocation via the ip-api.com service, and sends timely notifications to Telegram in case of issues or degradation. We’ll walk you from the idea to deployment on a VPS with auto-start, SQLite history, and a simple dashboard with uptime statistics and log exports.

This guide is intended for beginners, but it also includes elements for more advanced users. If you've never written a bot before, don’t worry: we’ll explain every step, its purpose, and how to check the results. By the end, you’ll have a stable proxy monitoring tool that operates on a schedule every 5–15 minutes, can group proxies by region and type, and sends informative alerts to your Telegram.

Before we start, it’ll be useful to know how to open a terminal, install programs, and create folders and files. We’ll explain everything else. Completing all steps will take approximately 4 to 8 hours, including environment setup, coding, testing, and deployment. If you already have a VPS and basic Python skills, you’ll finish more quickly.

By the end of this guide, you will have: a working Telegram bot using aiogram 3, an asynchronous checking module with aiohttp, an APScheduler for periodic tasks, an SQLite database with logs and statistics, a simple web dashboard with essential metrics and CSV export capabilities, and instructions for maintenance and feature expansion.

Tip: It’s better to go through the guide step by step without skipping any. We've added checks after each phase so you can immediately verify that everything is functioning properly.

Preliminary Preparation

Before we start writing code, let’s prepare all the necessary tools and environments. This will help avoid errors and save time.

Required Tools, Programs, and Access

  • A Telegram account to create the bot and receive notifications.
  • A VPS or a local Linux computer (preferably Ubuntu 22.04 or 24.04). Windows and macOS can also be used, but we will demonstrate deployment on Ubuntu.
  • Python 3.11 or newer. You can check the version with the command python3 --version.
  • A terminal and basic sudo rights to install packages.
  • A list of proxies for monitoring in the format host:port, plus a username and password if required. We will support HTTP, HTTPS, and SOCKS5.

System Requirements

  • 1 CPU and 512–1024 MB of RAM is sufficient for monitoring 50–200 proxies at intervals of 5–15 minutes. For thousands of proxies, it’s better to have 2 CPUs and 2 GB of RAM.
  • At least 1 GB of free disk space. SQLite is lightweight, but logs can grow.
  • A direct internet connection. Access to the Telegram API and your proxies is required. Outgoing HTTP access is needed for ip-api.com.

What You Need to Install

  • Python 3.11+ and venv for the virtual environment.
  • pip for installing dependencies.
  • Libraries: aiogram version 3, aiohttp, APScheduler, aiosqlite, python-dotenv (for convenient configuration), uvloop (optional for Linux), yarl (often included as a dependency).

Backup Creation

At the start, backup isn’t critical. After launching the bot, we’ll set up a simple backup of the SQLite database with logs and settings. This will allow you to revert in case of erroneous updates or file corruption.

⚠️ Attention: SQLite is contained in a single file. If it gets corrupted during a sudden power failure, you may lose recent entries. Therefore, enable automatic daily backups of the database file on your VPS using cron or systemd timers. We’ll provide a simple script for this at the end.

Basic Concepts

To avoid confusion, let’s agree on key terms and operational principles.

  • Proxy — a mediating server through which your requests pass. They can be HTTP, HTTPS, or SOCKS5. Proxies may require authorization with username:password or operate via IP binding.
  • Latency — the time it takes for us to make a request via a proxy and receive a response. Measured in milliseconds. The smaller, the better.
  • IP Rotation — some proxies periodically change their external IP address. Our bot needs to monitor these changes and notify if the IP switches unexpectedly or doesn’t match the expected region.
  • Geolocation — the country, city, and operator of the IP address. We acquire it via the ip-api.com service using the IP. This helps ensure that your proxy is located in the desired country.
  • Degradation — the proxy responds, but significantly slower than normal or with frequent errors. We’ll differentiate between DOWN (completely unavailable) and DEGRADED (working, but poorly).
  • aiogram 3 — a modern asynchronous library for Telegram bots. Simple and fast, supporting all current Telegram features as of 2026.
  • aiohttp — for making asynchronous HTTP requests with timeouts and proxy support.
  • APScheduler — a task scheduler that will execute checks based on a schedule every N minutes.
  • SQLite — an embedded database. It’s just one file, fast and without server installation. Perfect for local logging and statistics.

The bot operates as follows: on schedule, the bot retrieves the list of proxies, performs quick test requests through each proxy, measures latency, retrieves external IP and geo-data, and saves results into SQLite. If a proxy goes down or noticeably degrades, the bot composes a neat message and sends it to Telegram. Additionally, the bot reacts to commands from the chat: showing status, uptime, recent errors, exporting CSV, adding or disabling proxies, changing intervals.

Tip: To avoid exceeding the limits of ip-api.com (usually 45 requests per minute for the free tier), we’ll implement a geo-data cache and limit parallel geolocation requests. This won’t slow down operations but will protect against blocking.

Step 1: Create the Bot in Telegram and Set Up Access

Step Goal

To obtain the Telegram bot token and create a private chat for notifications, so the bot can later send messages about incidents.

Step-by-Step Instructions

  1. Open the Telegram app on your phone or computer.
  2. In the search bar, type BotFather and open the official account with a blue checkmark.
  3. Press Start or type the command /start, then enter /newbot.
  4. Think of a name for your bot, for example, ProxyMonitorBot.
  5. Decide on a unique username for the bot that ends with bot, e.g., proxy_monitor_helper_bot.
  6. Copy the provided token. It looks like a series of characters and digits separated by colons.
  7. Create a private chat or group where the bot will send notifications. Add the bot there.
  8. Send any message in the chat so it appears in your account’s list of chats.
  9. Open a dialog with the bot and click Start to activate it.

Important: Keep the token secret. Do not share it with anyone. If the token leaks, a malicious actor can take control of your bot. If the token is compromised, select /revoke in BotFather to receive a new one.

Tip: Create a separate group for alerts where it’s just you and the bot. This way, notifications won’t get lost among other messages.

✅ Check: You have received the token and added the bot to the necessary chat. The bot responds to the /start command in a personal dialog.

Potential Issues and Solutions

  • Issue: The bot doesn’t respond to /start. Reason: You didn’t press Start or the token was entered incorrectly in the code. Solution: Ensure the bot is activated and later we correctly reference the token in the .env file.
  • Issue: You don’t see the chat ID. Reason: We haven’t gotten it yet. Solution: Later, a command in the bot will automatically show you the chat_id.

Step 2: Prepare the Python Environment and Project Structure

Step Goal

Create a working project folder, a Python 3.11+ virtual environment, install dependencies, and set up the configuration and directory structure.

Step-by-Step Instructions

  1. Open a terminal on your machine or connect to the VPS via SSH.
  2. Check the Python version with the command: python3 --version. If the version is below 3.11, install the latest one.
  3. On Ubuntu, execute: sudo apt update; sudo apt install -y python3.11 python3.11-venv python3-pip.
  4. Create the project folder: mkdir proxy-monitor-bot; cd proxy-monitor-bot.
  5. Create a virtual environment: python3.11 -m venv .venv.
  6. Activate the environment: source .venv/bin/activate.
  7. Upgrade pip: pip install --upgrade pip.
  8. Install dependencies: pip install aiogram aiohttp aiosqlite APScheduler python-dotenv uvloop.
  9. Create the folder structure: mkdir app app/modules app/db app/bot app/web.
  10. Create the files: touch app/__init__.py app/config.py app/main.py app/modules/proxy_checker.py app/db/models.py app/db/migrations.py app/bot/handlers.py app/bot/notifications.py app/scheduler.py app/web/server.py .env .env.example README.md.

Basic Configuration

Open the .env file and add the keys (replace values with yours):

BOT_TOKEN=your_token_from_BotFather TELEGRAM_CHAT_ID=number_or_leave_empty DB_PATH=./data/monitor.db PROBE_TIMEOUT=8 PROBE_RETRIES=2 CHECK_INTERVAL_MINUTES=10 GEO_CACHE_TTL_HOURS=24 IP_API_URL=http://ip-api.com/json PROXY_CONCURRENCY=20 GEO_CONCURRENCY=10 DASHBOARD_HOST=0.0.0.0 DASHBOARD_PORT=8080

Copy .env to .env.example and remove the secrets to share the project without tokens conveniently.

Tip: Keep the .env file outside the version control system. Add .env to .gitignore to avoid accidentally uploading the token to the repository.

Minimum Code Structure

The app/config.py file will read environment variables and provide configurations. The app/main.py file will run the bot, the scheduler, and the web dashboard. The proxy_checker.py module will perform all checks. The db folder will contain models and SQLite migrations. In the bot folder, we’ll write command handlers and send notifications. In the web folder, we’ll launch a simple web server with statistics and exports.

✅ Check: You have a virtual environment, dependencies are installed, the .env file is created, and the project structure matches the listed requirements. The command python -c "import aiogram, aiohttp, aiosqlite; print('ok')" outputs ok.

Potential Issues and Solutions

  • Issue: pip reports an SSL error or is unavailable. Reason: No internet access. Solution: Check your network or use a local pip mirror.
  • Issue: Python 3.11 is not available. Reason: Old OS. Solution: Update your system or install Python through the deadsnakes PPA or pyenv.

Step 3: Write the Proxy Checking Module: Connection, Latency, IP, Geolocation

Step Goal

Create an asynchronous module that quickly and reliably checks proxies: capable of making test HTTP requests through a proxy, measuring latency, identifying external IP, performing requests to ip-api.com, caching geo data, and properly handling errors.

Key Ideas

  • To check availability, make a request to a lightweight resource such as https://api.ipify.org or any fast echo endpoint. We’ll choose to obtain the IP through a service and then get geo through ip-api.com or vice versa.
  • Set the default timeout to 8 seconds and 2 attempts. These parameters can be configured through the .env.
  • aiohttp works conveniently with proxies: pass the proxy parameter in ClientSession.request.
  • For geolocation, we’ll cache data for 24 hours per IP to conserve quota.
  • Check requests in parallel but limit concurrency with a semaphore to avoid overloading connections and exceeding ip-api limits.

Detailed Instructions

  1. Open the app/modules/proxy_checker.py file.
  2. Define the proxy model as a dictionary: id, label, type (http, https, socks5), endpoint (host:port), username, password, region, group, expected_country, last_ip.
  3. Create an asynchronous function build_proxy_url that composes a string such as scheme://user:pass@host:port from type, host, port, and optionally username:password.
  4. Create an asynchronous function fetch_ip(session, proxy_url) to retrieve the external IP through the proxy. Use the url https://api.ipify.org?format=json or a similar service that returns your IP in JSON. Measure the time before the call and after the response to get latency.
  5. Create a geo data cache: a dictionary in memory and a table in SQLite so that we don’t lose cache between restarts. The key is IP, and the value is JSON containing the country, city, organization, and caching time.
  6. Create a function fetch_geo(session, ip) that does a GET request to ip-api.com/json/{ip}?fields=status,country,countryCode,regionName,city,org,query. If the status is fail, return an error indicator. If successful, return the geo data.
  7. Implement the function check_proxy(proxy) with the logic: build the proxy_url, attempt to get the IP with a timeout, measure latency, then fetch geo data (from cache or network), forming a result: availability (ok), latency in milliseconds, external IP, country and city, and the result type: UP, DEGRADED, or DOWN.
  8. Define the criteria for DEGRADED: if latency exceeds your threshold, e.g., 1500–2000 ms, or if the error rate in recent checks is noticeable. For simplicity: if a response is received but latency exceeds 2 seconds — DEGRADED; otherwise, UP. If no response is received — DOWN.
  9. Return detailed results for logging: timestamp, proxy_id, status, latency_ms or None, IP or None, geo or None, error_reason, changed_ip (True or False if the IP differs from the previous one), changed_geo (if the country changed), region, group, type.
  10. Add exception handling: asyncio.TimeoutError, aiohttp.ClientConnectorError, aiohttp.ClientHttpProxyError, aiohttp.ClientProxyConnectionError, socket errors. In the error_reason field, write a brief description.

Example Code (Simplified, One Line)

Here’s simplified logic in one line: import asyncio, time, json, aiohttp; async def check_proxy(proxy, cfg, geo_cache, sem): async with sem: start=time.perf_counter(); purl=f"{proxy['type']}://{proxy.get('username', '')+(':'+proxy.get('password','') if proxy.get('username') else '')+'@' if proxy.get('username') else ''}{proxy['endpoint']}"; timeout=aiohttp.ClientTimeout(total=cfg['PROBE_TIMEOUT']); try: async with aiohttp.ClientSession(timeout=timeout) as s: t0=time.perf_counter(); async with s.get('https://api.ipify.org?format=json', proxy=purl, ssl=False) as r: data=await r.json(); ip=data.get('ip'); latency=int((time.perf_counter()-t0)*1000); if not ip: raise Exception('no-ip'); geo=geo_cache.get(ip); if not geo or geo['ts']

In real code, we’ll add logging to SQLite and semaphores for GEO queries, as well as careful timeouts and retries.

⚠️ Attention: Don’t send too many parallel requests to ip-api.com. If you have a list of hundreds of proxies, cache the results and limit the number of concurrent requests. This will protect against limits and errors.

✅ Check: Manually call the check_proxy function for one test proxy and print the result. You should see status UP or DEGRADED with measured latency and external IP.

Potential Issues and Solutions

  • Issue: ClientHttpProxyError. Reason: The proxy doesn’t support the requested type or wrong authorization. Solution: Check the type and credentials, update the endpoint.
  • Issue: Geo always returns unknown. Reason: Error in the request to ip-api or blocked. Solution: Check IP_API_URL and network, enable exception logging.

Step 4: Set Up SQLite Database and Log History

Step Goal

Create tables in SQLite for storing the list of proxies, check history, incidents, geo cache, and settings. This will give you detailed statistics and the ability to build graphs.

Database Structure

  • proxies: id, label, type, endpoint, username, password, region, group_name, expected_country, last_ip, enabled, created_at, updated_at.
  • checks: id, proxy_id, ts, status, latency_ms, ip, country, city, org, error_reason.
  • incidents: id, proxy_id, opened_at, closed_at, kind (DOWN or DEGRADED), last_status, last_message, active.
  • geo_cache: ip, country, city, org, ts.
  • settings: key, value (for instance, default check interval).

Detailed Instructions

  1. Open app/db/models.py. Describe SQL constants for table creation. Add indexes on proxy_id and ts to speed up selections.
  2. Create a function init_db(db_path) that creates the data folder if needed, opens a connection with aiosqlite.connect, and performs create table if not exists for all tables.
  3. Add functions to record results: add_proxy, update_proxy_last_ip, add_check, open_incident, close_incident_if_needed, upsert_geo_cache, get_recent_checks, get_uptime_stats.
  4. Implement migrations in app/db/migrations.py. For the first launch, executing all create table is sufficient. When updating versions, the script will add missing columns.

Example SQL (in One Line)

CREATE TABLE IF NOT EXISTS proxies (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, type TEXT, endpoint TEXT, username TEXT, password TEXT, region TEXT, group_name TEXT, expected_country TEXT, last_ip TEXT, enabled INTEGER DEFAULT 1, created_at INTEGER, updated_at INTEGER); CREATE TABLE IF NOT EXISTS checks (id INTEGER PRIMARY KEY AUTOINCREMENT, proxy_id INTEGER, ts INTEGER, status TEXT, latency_ms INTEGER, ip TEXT, country TEXT, city TEXT, org TEXT, error_reason TEXT, FOREIGN KEY(proxy_id) REFERENCES proxies(id)); CREATE INDEX IF NOT EXISTS idx_checks_proxy_ts ON checks(proxy_id, ts); CREATE TABLE IF NOT EXISTS incidents (id INTEGER PRIMARY KEY AUTOINCREMENT, proxy_id INTEGER, opened_at INTEGER, closed_at INTEGER, kind TEXT, last_status TEXT, last_message TEXT, active INTEGER, FOREIGN KEY(proxy_id) REFERENCES proxies(id)); CREATE TABLE IF NOT EXISTS geo_cache (ip TEXT PRIMARY KEY, country TEXT, city TEXT, org TEXT, ts INTEGER); CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT);

Tip: If you already store the list of proxies in CSV or JSON, add convenient imports to the database: a simple script reads the file and adds entries to proxies.

✅ Check: Run the init_db function and ensure the database file appears at DB_PATH. Test that the tables were created by executing a simple query select count(*) from proxies and getting a response of 0.

Potential Issues and Solutions

  • Issue: no such table error. Reason: Initialization not executed. Solution: Call init_db at startup before all operations.
  • Issue: Write lock errors. Reason: Concurrent transactions. Solution: If possible, group result entries in batches or use short transactions.

Step 5: Implementing the Telegram Bot with aiogram 3

Step Goal

Launch a bot that responds to commands, can send notifications to your chat, and reads settings from .env. Let’s configure basic commands: /start, /status, /chatid, /addproxy, /list, /enable, /disable, /interval, /export.

Detailed Instructions

  1. Open app/bot/handlers.py. Create command routes. For aiogram 3, use Router, Dispatcher, and asynchronous handlers.
  2. Implement /start: it responds with a brief description of capabilities and the current check interval.
  3. Implement /chatid: sends the chat ID so you can save TELEGRAM_CHAT_ID in .env. This is convenient if you’re running the bot from a personal chat or group.
  4. Implement /status: finds the last N checks for each proxy and outputs a brief summary: status, latency, country, and time of update.
  5. Implement /list: a list of proxies with id, label, type, region, enabled.
  6. Implement /addproxy: pass parameters like type, endpoint, username, password, region, group, expected_country. For the initial step, you can create a simple version that accepts a string formatted as type=http endpoint=host:port region=EU and parses the parameters.
  7. Implement /enable and /disable with id parameter to quickly enable and disable specific proxies.
  8. Implement /interval N to change the check interval in minutes. Store the value in the settings table.
  9. Implement /export to generate CSV for the latest checks over a period. Later we’ll add a similar button in the web dashboard.

Sending Notifications

In app/bot/notifications.py, create a function notify_change that formats messages about events. Example message: Proxy: EU-1 (http) Region: EU Group: EUROPE Status: DOWN Reason: timeout Last IP: 203.0.113.45 Expected: country DE Received: country NL Time: 2026-02-01 12:34:56 Latency: n/a. For UP use the label UP, for degradations use DEGRADED. Add a priority sorting: DOWN first, then DEGRADED, followed by messages about IP changes. Include emoji indicators for visual clarity, e.g., ✅ for UP and ⚠️ for DEGRADED and ❌ for DOWN. If TELEGRAM_CHAT_ID is not set, send to the current chat from which the command was issued. In production, it’s better to fix the chat_id in .env.

Tip: For group chats, the bot may need sending permissions. Check the group settings to ensure the bot is a member with write permissions.

✅ Check: Run the bot locally and send /chatid. You should receive a numeric identifier. Set TELEGRAM_CHAT_ID in .env. Send /start and /status. The bot should respond. The /list command may be empty for now, which is normal.

Potential Issues and Solutions

  • Issue: The bot doesn’t start. Reason: Incorrect token. Solution: Check BOT_TOKEN in .env for extra spaces and line breaks.
  • Issue: Commands are not executing. Reason: Router not connected. Solution: Ensure you’ve added the router to Dispatcher and are running polling.

Step 6: APScheduler Check Scheduler

Step Goal

Set up periodic checks for all enabled proxies every 5–15 minutes based on the current settings interval, parallelize checks without overload, log history, and send alerts on changes.

Detailed Instructions

  1. Open app/scheduler.py. Initialize AsyncIOScheduler.
  2. Create the run_checks task that selects all proxies with enabled=1 from the database, sending them off to check in a pool of asynchronous tasks with the PROXY_CONCURRENCY semaphore, collecting results.
  3. For every result, log a row in checks. Update last_ip in proxies when it changes.
  4. Incident logic: If the status is DOWN and there is no incident, create a record in incidents (active=1). If the status is UP again, close the incident (active=0, closed_at=ts). For DEGRADED, you can combine DEGRADED into a single active incident or close it upon recovery.
  5. Send notifications: on the first transition to DOWN, upon DEGRADED, upon recovery to UP, and on IP or country changes. Group messages if multiple proxies change status simultaneously: send one summary header and a list of items.
  6. Create the job scheduler.add_job(run_checks, 'interval', minutes=CHECK_INTERVAL_MINUTES, id='checks', replace_existing=True). Add a response to /interval to restart the job with a new interval without needing to restart the bot.
  7. Start scheduler.start() along with the bot and web server in main.

Tip:

Tip: Add random jitter of 0–60 seconds to the schedule to reduce peak load and avoid perfectly synchronized requests.

✅ Check: Set CHECK_INTERVAL_MINUTES=1 temporarily and run the application. In the logs, you should see that checks execute every minute, and new rows are added to the checks table.

Potential Issues and Solutions

  • Issue: Checks are not starting. Reason: Scheduler not running. Solution: Ensure scheduler.start() is called, and there are no exceptions when creating the job.
  • Issue: Database locks. Reason: Too frequent writes. Solution: Reduce the interval or implement batch recording for results.

Step 7: Integrate All Parts in the Entry Point main.py

Step Goal

Assemble the application as a whole: config, database, bot, checker, scheduler, and web dashboard. Ensure correct startup and shutdown, process termination signals.

Detailed Instructions

  1. Open app/main.py. Import asyncio, logging, uvloop (if Linux), aiogram, init_db functions, router, scheduler, web server.
  2. Load the configuration from .env using python-dotenv. Check for mandatory keys: BOT_TOKEN and DB_PATH.
  3. Initialize the database: await init_db(DB_PATH).
  4. Create Dispatcher and Router from aiogram 3, add command handlers.
  5. Create the APScheduler and add the run_checks task according to the interval.
  6. Start all three components simultaneously: bot polling, scheduler.start(), web dashboard. Use asyncio.gather for concurrent execution.
  7. Process SIGINT and SIGTERM signals: properly stop the scheduler, close database connections, and terminate polling.

Here’s a simplified idea for a one-liner on startup: import asyncio, logging; async def main(): await init_db(...); dp=Dispatcher(); dp.include_router(router); scheduler=AsyncIOScheduler(); scheduler.add_job(run_checks, 'interval', minutes=interval); scheduler.start(); await asyncio.gather(dp.start_polling(bot), start_web_server(), wait_for_signals(scheduler)); asyncio.run(main()).

Tip: Add detailed logging at INFO and WARN levels. This will help quickly understand what’s happening when starting up and during checks.

✅ Check: Run python app/main.py. The bot should respond to /start, the web dashboard should open at http://localhost:8080, and the scheduler should begin checks based on the interval.

Potential Issues and Solutions

  • Issue: Address 0.0.0.0:8080 is occupied. Reason: The port is being used by another process. Solution: Change DASHBOARD_PORT in .env or stop the conflicting process.
  • Issue: Error when starting polling. Reason: Incorrect token or network issues. Solution: Check BOT_TOKEN and internet access.

Step 8: Notification Formatting and Proxy Grouping

Step Goal

Make notifications clear, compact, and informative. Set up proxy grouping by regions and types to structure messages.

Notification Format

  • Title: Proxy Status Changes.
  • Subtitle: Date and Time.
  • Sections by priority: DOWN, then DEGRADED, then Recovery, then IP Change, followed by Geo Change.
  • Each item: [Region] [Group] [Label] ([type]) status, latency, IP, country, reason.
  • Icons: DOWN marked as ❌, DEGRADED — ⚠️, UP — ✅.

Proxy Grouping

  • By the region field: EU, US, ASIA, and others.
  • By group_name: logical grouping like EUROPE, PAID, TESTING.
  • By type: http, https, socks5.

Implementation

  1. In app/bot/notifications.py, create functions group_by_region and group_by_type to group results before sending them. This will allow sending fewer messages and improve readability.
  2. For each block, create short strings with key data: "EU, EUROPE, EU-1 (http): ❌ DOWN, timeout" or "US, AMERICA, US-2 (socks5): ⚠️ DEGRADED, 2300ms, country US".
  3. If there are many changes, send one summary header and several messages by regions. This is better than 100 separate messages.

✅ Check: Temporarily replace one proxy with an intentionally non-working endpoint. Wait for the check. You will receive a DOWN notification. Restore the proxy to working, and you will see a recovery notification.

Potential Issues and Solutions

  • Issue: Messages are too long. Reason: Too many details. Solution: Shorten fields, diving into details via the /status or /details id commands.
  • Issue: Duplicate alerts. Reason: Resending upon unchanged status. Solution: Send notifications only on status transitions and upon explicit IP or country changes.

Step 9: Deployment on VPS (Ubuntu) and Autostart

Step Goal

Deploy the bot on a remote server, set up autostart via systemd, and ensure logging and simple updates.

Preparing the VPS

  1. Connect to your VPS: ssh user@server_ip.
  2. Update the system: sudo apt update && sudo apt upgrade -y.
  3. Install Python if it isn’t installed: sudo apt install -y python3.11 python3.11-venv python3-pip.
  4. Create a user for the application if necessary: sudo adduser botuser; then switch to that user.
  5. Copy the project to the server via scp or git clone your repository.
  6. Create a virtual environment and install dependencies as in step 2.
  7. Check the .env. Fill in the real values for BOT_TOKEN and TELEGRAM_CHAT_ID.

systemd Service

  1. Create the service file: sudo nano /etc/systemd/system/proxy-monitor.service.
  2. Example Unit (one line): [Unit] Description=Proxy Monitor Bot After=network.target [Service] Type=simple User=botuser WorkingDirectory=/home/botuser/proxy-monitor-bot ExecStart=/home/botuser/proxy-monitor-bot/.venv/bin/python -m app.main Environment=PYTHONUNBUFFERED=1 Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target.
  3. Reload the daemon: sudo systemctl daemon-reload.
  4. Enable autostart: sudo systemctl enable proxy-monitor.service.
  5. Start: sudo systemctl start proxy-monitor.service.
  6. Check the log: journalctl -u proxy-monitor.service -f.

Tip: If you're using uvloop, ensure it is installed in the virtual environment. On Windows, uvloop is not used, while on Linux, it boosts performance.

✅ Check: The bot should respond to /start from your Telegram when the service is running. The log should show the scheduler starting, and the web dashboard should listen on port 8080.

Potential Issues and Solutions

  • Issue: The service crashes immediately. Reason: Incorrect path to Python or working directory. Solution: Check ExecStart and WorkingDirectory, and the user permissions.
  • Issue: No access to port 8080 from the outside. Reason: UFW or cloud firewall. Solution: Open the port: sudo ufw allow 8080 or adjust rules in the provider panel.

Verification of Results

Checklist

  • The bot accepts commands: /start, /status, /list, /chatid.
  • The scheduler runs checks at intervals, and records appear in the checks table.
  • Upon a proxy failure, the bot sends a notification to Telegram.
  • IP changes are logged and marked in messages.
  • Geolocation is determined and compared with the expected country.
  • The dashboard is displayed on the specified port, showing uptime and recent events.
  • The CSV export works and produces a correct file.

How to Test

  1. Disconnect one of the proxies or specify an incorrect port. Wait for the DOWN alert.
  2. Temporarily change the endpoint to another to induce an IP change. Ensure the bot reports the IP change and country change if it occurs.
  3. Artificially raise latency through network limitation (if possible) or by testing with a heavy request to get DEGRADED.
  4. Test the commands: /interval 5, /list, /enable, /disable, and ensure the status reflects appropriately.

Indicators of Successful Execution

  • Stable checks without errors in the logs.
  • Timely alerts upon real issues.
  • Correct incident logic operation: opening upon DOWN and closing upon UP.

✅ Check: If all checklist items pass and testing scenarios work, your bot is successfully deployed and correctly performs monitoring tasks.

Common Mistakes and Solutions

  • Issue: No notifications in Telegram → Reason: Incorrect TELEGRAM_CHAT_ID or the bot isn’t a member of the group → Solution: Execute /chatid in the needed chat, save the ID in .env, add the bot to the chat and provide it with writing permissions.
  • Issue: All proxies are DOWN, although they are operational → Reason: Incorrect proxy URL format or type → Solution: Check if it's type=http, https, or socks5, verify user:pass authorization, and endpoint host:port, and test one proxy separately.
  • Issue: Geolocation is not determined → Reason: ip-api limit or network error → Solution: Enable caching, reduce request frequencies to ip-api, and check IP_API_URL.
  • Issue: The database grows too quickly → Reason: Too frequent checks and a large number of proxies → Solution: Increase the interval, enable cleanup of old entries, export archives, and delete those older than 30–90 days.
  • Issue: High latency across all proxies → Reason: The server is overloaded or network issues → Solution: Increase VPS resources, decrease PROXY_CONCURRENCY, or use a nearby region.
  • Issue: Duplicate alerts for the same incident → Reason: Incident state isn't stored → Solution: Utilize the incidents table and send alerts only upon status changes.
  • Issue: The bot frequently crashes due to exceptions → Reason: Unhandled errors → Solution: Wrap network operations in try-except, add logging, and retries.

Additional Features

Advanced Settings

  • Flexible degradation thresholds: store latency thresholds by groups in the settings table.
  • Different intervals by groups: check important proxies every 5 minutes, secondary ones every 15-30.
  • Enhanced command parsing: utilize key=value format and strict parameter validation.

Optimization

  • Enable uvloop on Linux to speed up the event loop.
  • Use batch writes in SQLite or WAL mode to speed up concurrent writes.
  • Parameterize semaphores for balancing speed and load.

Simple Dashboard

In app/web/server.py, spin up an aiohttp.web server. Display a summary on the main page: number of proxies, how many UP, DOWN, DEGRADED, average latency, uptime over 24 hours. Create an endpoint /export?from=ts&to=ts for CSV export. The minimum HTML can be a simple list and table with status. Graph visualizations can be added later through simple SVG.

Tip: Add a button to toggle the interval directly in the dashboard that calls the endpoint and changes the value in settings. This speeds up management without requiring Telegram commands.

Backups

Backup script for the database: tar -czf backup-$(date +%F).tar.gz data/monitor.db. Schedule a daily backup using cron or systemd timers. Keep at least the last 7 archives.

⚠️ Attention: Do not edit the database file on the fly. For manual changes, stop the service, make a copy, edit, and restart it.

FAQ

  • How to add proxies with authorization? In the /addproxy command, specify username and password, or add user:pass to the endpoint. Example: type=http endpoint=user:pass@host:port.
  • How to limit checks at night? Implement cron-like scheduling in APScheduler by adding a trigger with a time window, or simply increase the interval at night with an automated rule.
  • What to do if ip-api.com is unreachable? Use cache and temporarily skip geo-requests. Later, you can add a backup source for geo.
  • How to filter notifications? Add importance levels and settings: send only DOWN alerts, while DEGRADED send every hour in summary.
  • Can PostgreSQL be used? Yes. Replace aiosqlite with asyncpg, adapt SQL and connection. For larger volumes, this is more convenient.
  • How to securely store tokens? In .env on the server with 600 permissions. Periodically refresh tokens when necessary.
  • Why does the bot sometimes experience timeouts? Networks are unstable. Increase PROBE_TIMEOUT, reduce parallelism, and check routes.
  • Can IPv6 proxies be checked? Yes, if the target services support IPv6. Ensure the endpoint provides IPv6, and ip-api works correctly with the address.
  • How to track a specific telecommunications operator? Save the org from ip-api in the message and compare it with the expected value.
  • How to temporarily disable a proxy? Use /disable id to exclude the proxy from checks without removing it from the database.

Conclusion

You’ve completed the entire journey from idea to working solution: created a Telegram bot using aiogram 3, wrote a proxy checking module with aiohttp, set up an APScheduler, stored history in SQLite, implemented notifications for downtimes, degradations, IP and geolocation changes, deployed the application on VPS with autostart via systemd, and added a simple web dashboard with log exports. Now you have a flexible tool that can be scaled: separating groups, adjusting thresholds, integrating graphs and reports, transitioning to PostgreSQL as load increases. The next step — automating maintenance: log rotation settings, daily database backups, and monitoring the bot using VPS system metrics. You can further develop: add a role model for accessing bot commands, implement SLA reports for groups, connect alternative geo-data sources, and create a web interface using any library that suits you. Good luck and stable uptime for your proxies!