Real-time website uptime monitoring — built to run in production.
🔗 Live: monitorup.me
MonitorUp lets you register URLs and watch their status in real time. A background scheduler pings every monitored site on an interval, pushes status changes to the browser over WebSockets the moment they happen, and emails you when something goes down or recovers.
This is a learning-driven project built end to end: from the FastAPI backend and React frontend down to the Docker images, the reverse proxy, the TLS certificates, and the CI/CD pipeline that ships it.
new line thats the true line
- 🔐 JWT authentication — signup/login with short-lived access tokens + an httpOnly refresh-token cookie.
- 📋 Monitor CRUD — add, edit, list and delete the URLs you want to watch (scoped per user).
- ⏱️ Periodic uptime checks — a Celery Beat scheduler dispatches checks every 60s.
- ⚡ Live status over WebSockets — status changes reach the dashboard instantly via Redis Pub/Sub, no polling.
- 📧 Email alerts — get notified when a monitor's status code changes (down/recovered).
- 🛡️ Rate limiting — per-IP limits on auth and monitor endpoints.
┌──────────────┐
Browser ──HTTPS──► │ NGINX │ reverse proxy + TLS termination
(React) ◄─WS────── │ :80 / :443 │
└──────┬───────┘
│ 127.0.0.1:8000
▼
┌──────────────┐ ┌──────────────────┐
│ FastAPI │◄──────►│ PostgreSQL │
│ (uvicorn) │ │ (managed/Aiven) │
└──────┬───────┘ └──────────────────┘
│ subscribe
▼
┌───────────────────────────────────────────┐
│ Redis │
│ broker (Celery queue) + Pub/Sub │
└───────▲───────────────────────┬───────────┘
│ enqueue │ publish status
┌───────┴───────┐ ┌────────┴────────┐
│ Celery Beat │ │ Celery Worker │
│ (scheduler) │──────►│ pings URLs, │
│ every 60s │ tasks │ emails alerts │
└───────────────┘ └─────────────────┘
The check pipeline (fan-out):
- Beat fires
dispatch_checksevery 60s. dispatch_checksreads all monitor IDs and fans them out into batches (check_batch.delay(...)), so checks run in parallel across workers instead of one giant task.- Each worker pings its batch of URLs with
httpx, updates the DB, and publishes any change to a Redis Pub/Sub channel. - A FastAPI background subscriber receives those messages and forwards them over WebSocket to the right connected user — the dashboard updates live.
- On a status change, the worker also sends an email alert.
| Layer | Tech |
|---|---|
| Backend | FastAPI, SQLModel / SQLAlchemy 2.0, Pydantic |
| Async tasks | Celery (Beat + Workers), Redis (broker + Pub/Sub) |
| Realtime | WebSockets (Starlette) + Redis Pub/Sub |
| Database | PostgreSQL (managed), Alembic migrations |
| Auth | JWT (PyJWT), bcrypt password hashing |
| Frontend | React + Vite |
| Infra | Docker (multi-stage), Docker Compose, NGINX, Let's Encrypt |
| CI/CD | GitHub Actions (test → build → migrate → deploy) |
| Tests | pytest + FastAPI TestClient (SQLite in-memory) |
.
├── main.py # FastAPI app + lifespan (WebSocket subscriber)
├── database.py # SQLModel models (User, Monitor) + engine/session
├── security.py # password hashing, JWT, auth dependency
├── limiter.py # slowapi rate limiter
├── websocket.py # WS endpoint + Redis Pub/Sub subscriber
├── celery_app.py # Celery app + beat schedule
├── email_utils.py # email alerts
├── routers/
│ ├── auth.py # signup / login / refresh
│ └── monitor.py # monitor CRUD
├── Services/
│ └── monitor.py # Celery tasks: dispatch_checks, check_batch
├── migrations/ # Alembic migrations
├── frontend/ # React + Vite app (built into static assets)
├── tests/ # pytest suite (TestClient + SQLite)
├── deploy/ # nginx config + k8s manifests
├── Dockerfile # multi-stage: builds frontend, serves via FastAPI
├── docker-compose.prod.yml # api + worker + beat + redis
└── .github/workflows/ci.yml # CI/CD pipeline
MonitorUp runs on a single DigitalOcean droplet, fronted by NGINX with a Let's Encrypt certificate:
- Docker multi-stage build — a Node stage compiles the React frontend; the Python stage serves the API and the built static assets from one image. The same image runs as three containers (
api,worker,beat) via different commands. - NGINX terminates TLS on
:443, redirects:80 → :443, and reverse-proxies to the API on127.0.0.1:8000(with a dedicatedlocationfor WebSocket upgrades). - PostgreSQL is a managed instance (durable data lives off-box); Redis runs as an ephemeral container (transient queue + Pub/Sub, nothing to persist).
On every push to main, GitHub Actions:
- Tests — spins up a clean runner, installs deps, runs the
pytestsuite. A failing test blocks the deploy. - Deploys — SSHes into the droplet and runs:
git pull → docker build → alembic upgrade head → docker compose up -d. Migrations run before the new app comes up.
The app is live at monitorup.me — running locally is optional.
# backend
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
# set env vars: DATABASE_URL, REDIS_URL, SECRET_KEY, ALGORITHM, ORIGINS
alembic upgrade head
uvicorn main:app --reload
# frontend
cd frontend && npm install && npm run dev
# or the whole stack
docker compose up --buildpytestThe suite uses an in-memory SQLite database swapped in via FastAPI's dependency overrides, so tests run fast and never touch a real database.
Built by Benebo7 as a hands-on dive into backend engineering and DevOps.