How to Build a Minimal SIEM with Python, SQLite and Telegram Alerts A developer built a minimal Security Information and Event Management (SIEM) system using Python, SQLite, and Telegram alerts, requiring less than 400 lines of code and no external infrastructure. The system tails log files, stores events in SQLite with WAL mode for concurrent access, and runs detection rules to alert on threats like SSH brute-force attempts. Most teams can't afford Splunk, and Elastic SIEM takes real time to tune. Yet you still need to know when someone is brute-forcing your SSH, when a suspicious process spawns at 3 AM, or when a web server starts returning a flood of 500s. A minimal SIEM built with Python, SQLite, and Telegram can cover these cases with less than 400 lines of code and zero additional infrastructure. This article walks through building a working prototype you can deploy and extend immediately. A Security Information and Event Management system does three things: That's it. Everything else — dashboards, threat intelligence enrichment, ML anomaly detection — is additive. The goal here is a foundation that runs on a single VM, a Raspberry Pi, or a $6/month VPS without any external service dependencies. SQLite is the right tool for a minimal SIEM. It's file-based, needs no server process, and handles tens of millions of rows comfortably. We use two tables: events for raw log lines and detections for fired rules. python import sqlite3 from pathlib import Path DB PATH = Path "/var/db/siem.db" def init db db path: Path = DB PATH - sqlite3.Connection: conn = sqlite3.connect db path conn.execute "PRAGMA journal mode=WAL" concurrent reads won't block writes conn.execute "PRAGMA synchronous=NORMAL" conn.executescript """ CREATE TABLE IF NOT EXISTS events id INTEGER PRIMARY KEY AUTOINCREMENT, ts INTEGER NOT NULL, -- unix epoch ms source TEXT NOT NULL, -- e.g. sshd, nginx, auditd host TEXT NOT NULL, raw TEXT NOT NULL, -- original log line severity TEXT DEFAULT 'info' ; CREATE TABLE IF NOT EXISTS detections id INTEGER PRIMARY KEY AUTOINCREMENT, ts INTEGER NOT NULL, rule id TEXT NOT NULL, event ids TEXT NOT NULL, -- JSON array of matching event IDs summary TEXT NOT NULL, notified INTEGER DEFAULT 0 ; CREATE INDEX IF NOT EXISTS idx events ts ON events ts ; CREATE INDEX IF NOT EXISTS idx events source ON events source ; """ conn.commit return conn One critical detail: WAL mode allows a reader and a writer to work simultaneously. Without it, your log ingestion locks the database and the detection loop stalls. We tail /var/log/auth.log using subprocess . The same pattern works for nginx, auditd, or any file-based log source. python import subprocess import time import re from dataclasses import dataclass @dataclass class LogEvent: ts: int source: str host: str raw: str severity: str = "info" Matches: "Failed password for root from 1.2.3.4" SSH FAILED = re.compile r"Failed password for ?:\w+ ? ?P