tutorial

How to Monitor Your Self-Hosted Open edX Platform (LMS, Studio, Celery, and Beyond)

Open edX has a lot of moving parts — LMS, Studio, Celery workers, Redis, MySQL, MongoDB, and more. Here's how to monitor all of them and get alerted before students notice a problem.

How to Monitor Your Self-Hosted Open edX Platform (LMS, Studio, Celery, and Beyond)

Open edX is one of the most operationally complex open source applications you can self-host. A production deployment touches Django web servers on two ports, Celery async workers, Redis, MySQL, MongoDB, a video transcoding pipeline, and an OAuth/SSO layer — and a failure in any one of them degrades the student experience in ways that are hard to trace without proper monitoring.

Students don't file bug reports. They drop courses. This guide sets up comprehensive monitoring for every layer of your Open edX stack so you find out about problems before they do.


The Open edX failure landscape

Open edX production deployments fail in several distinct patterns:

LMS web server outages — the Learning Management System (port 8000) returns errors or goes unresponsive. Students can't access courses, videos, or assignments.

Studio (CMS) outages — the Course Management Studio (port 8001) fails, blocking course authors from editing content. Less visible to students but can delay course launches.

Celery worker failures — Open edX offloads most async work (grade calculations, certificate generation, email sends, problem grading) to Celery workers. When workers stop, work queues silently. No visible error. Grades stop updating. Certificates don't generate.

Redis cache failures — Open edX uses Redis for session storage, caching, and Celery broker. A Redis failure cascades: sessions drop (users get logged out), caches miss (performance degrades), and Celery stops accepting jobs.

MongoDB failures — XBlock state, course content (in older deployments), and modulestore data lives in MongoDB. A MongoDB failure breaks course content rendering.

Video transcoding pipeline failures — VEDA (Video Encode and Distribute Architecture) processes video uploads for courses. Silent failures mean uploaded videos never become available to students.


Step 1: Enable health check endpoints

Open edX exposes health endpoints for both the LMS and Studio. Verify they're enabled in your Tutor or native deployment:

# For Tutor-based deployments
tutor local run lms curl http://localhost:8000/heartbeat
tutor local run cms curl http://localhost:8001/heartbeat

The /heartbeat endpoint returns a JSON object with service health. If the database or cache is unhealthy, it returns a non-200 status.

For older Open edX installations, the health endpoint may be at /health or require configuration in lms.env.json:

{
  "ENABLE_HEARTBEAT_ENDPOINT": true
}

For a more comprehensive check that validates database connectivity explicitly:

# Add to lms/urls.py or via a custom Django app
from django.http import JsonResponse
from django.db import connection
from django.core.cache import cache
import redis
import os

def health_check(request):
    checks = {}
    status = 200

    # Database check
    try:
        with connection.cursor() as cursor:
            cursor.execute("SELECT 1")
        checks['mysql'] = 'ok'
    except Exception as e:
        checks['mysql'] = 'error'
        status = 500

    # Cache / Redis check
    try:
        cache.set('health_check', 'ok', 10)
        val = cache.get('health_check')
        checks['redis'] = 'ok' if val == 'ok' else 'error'
        if checks['redis'] == 'error':
            status = 500
    except Exception:
        checks['redis'] = 'error'
        status = 500

    return JsonResponse(
        {'status': 'healthy' if status == 200 else 'degraded', 'checks': checks},
        status=status
    )

Step 2: Set up HTTP monitoring for LMS and Studio

With health endpoints available, configure monitoring in Vigilmon:

  1. Sign up at vigilmon.online
  2. Click New Monitor → HTTP

Create monitors for each Open edX service:

| Monitor name | URL | Check | |---|---|---| | Open edX LMS | https://lms.yourdomain.com/heartbeat | Status 200 | | Open edX Studio | https://studio.yourdomain.com/heartbeat | Status 200 | | LMS Login Page | https://lms.yourdomain.com/login | Status 200 | | LMS API | https://lms.yourdomain.com/api/user/v1/me | Status 200 or 401 | | Studio Dashboard | https://studio.yourdomain.com/home | Status 200 |

The LMS API returning a 401 (unauthorized) is fine — it means the API is responding. A 500 or timeout means the API layer is broken.


Step 3: Monitor Celery worker health

Celery workers are the most common source of silent failures in Open edX. When they stop processing, nothing in the UI indicates a problem — grades just stop updating, certificates stop generating, and email stops sending.

Celery health check via Flower

If you're running Flower (Celery's monitoring tool), expose a status endpoint:

# Start Flower if not already running
celery -A lms flower --port=5555 --address=127.0.0.1

Create an HTTP monitor for http://lms.yourdomain.com:5555/metrics (or proxy it through Nginx with auth). This confirms Flower — and by extension, at least one Celery process — is alive.

Celery heartbeat via a recurring task

A more reliable approach is a synthetic heartbeat task that runs on a schedule:

# lms/celery_heartbeat.py
from celery import shared_task
import requests
import os

@shared_task(name='lms.heartbeat_ping', ignore_result=True)
def heartbeat_ping():
    """Runs every 5 minutes. Pings Vigilmon on success."""
    heartbeat_url = os.environ.get('VIGILMON_CELERY_HEARTBEAT_URL')
    if heartbeat_url:
        try:
            requests.get(heartbeat_url, timeout=5)
        except Exception:
            pass  # Don't fail the task if the ping fails

Schedule it in your Celery beat configuration:

# lms/celery.py or settings/common.py
CELERYBEAT_SCHEDULE = {
    'vigilmon-heartbeat': {
        'task': 'lms.heartbeat_ping',
        'schedule': 300.0,  # Every 5 minutes
    },
    # ... your existing beat schedule
}

In Vigilmon, create a Heartbeat Monitor:

  1. Click New Monitor → Heartbeat
  2. Name it "Open edX Celery Workers"
  3. Set expected interval to 10 minutes (double the task interval for tolerance)
  4. Copy the ping URL and set it as VIGILMON_CELERY_HEARTBEAT_URL

If Celery workers go down, the task stops being executed, the heartbeat URL stops getting pings, and Vigilmon alerts you within 10 minutes.


Step 4: Monitor certificate generation and grade processing

Grade processing and certificate generation are high-value Celery workflows. Monitor them with dedicated heartbeats:

# In your Celery task for grade processing
from lms.djangoapps.grades.tasks import compute_all_grades_for_course
import requests, os

@shared_task
def compute_grades_with_heartbeat(course_id):
    try:
        compute_all_grades_for_course(course_id)
        url = os.environ.get('VIGILMON_GRADES_HEARTBEAT_URL')
        if url:
            requests.get(url, timeout=5)
    except Exception:
        # Let the original exception propagate — don't send heartbeat on failure
        raise

For certificate generation:

# Wrap the certificate task similarly
from lms.djangoapps.certificates.tasks import generate_certificate
import requests, os

@shared_task
def generate_certificate_with_heartbeat(student, course_key, **kwargs):
    try:
        generate_certificate(student, course_key, **kwargs)
        url = os.environ.get('VIGILMON_CERTS_HEARTBEAT_URL')
        if url:
            requests.get(url, timeout=5)
    except Exception:
        raise

Step 5: Monitor Redis and MongoDB separately

Redis health check

Create a lightweight Redis health endpoint:

# Add to a custom Django management command or health view
import redis
import os

def check_redis():
    try:
        r = redis.Redis.from_url(os.environ.get('CELERY_BROKER_URL', 'redis://localhost:6379/0'))
        r.ping()
        return True
    except redis.ConnectionError:
        return False

Or use a standalone script as a cron-driven heartbeat:

#!/bin/bash
# /opt/edx-jobs/check-redis.sh
if redis-cli -h localhost ping | grep -q PONG; then
    curl -fsS "$VIGILMON_REDIS_HEARTBEAT_URL" > /dev/null 2>&1
fi
*/5 * * * * edxapp /opt/edx-jobs/check-redis.sh

MongoDB health check

#!/bin/bash
# /opt/edx-jobs/check-mongodb.sh
if mongosh --quiet --eval "db.adminCommand('ping').ok" localhost/openedx | grep -q 1; then
    curl -fsS "$VIGILMON_MONGODB_HEARTBEAT_URL" > /dev/null 2>&1
fi
*/5 * * * * edxapp /opt/edx-jobs/check-mongodb.sh

Step 6: XBlock rendering and VEDA video pipeline

XBlock rendering health

XBlocks render course content. A broken XBlock service means course pages display partially or not at all. Create an HTTP monitor on a known-good course URL:

https://lms.yourdomain.com/courses/course-v1:YourOrg+TestCourse+Run/courseware/

Set this monitor to check for a 200 response and a keyword like your course organization name in the body. If XBlock rendering breaks, the page body changes and the check fails.

VEDA video transcoding heartbeat

VEDA runs as a separate Django service. Add a heartbeat to its main processing task:

# In veda_worker/transcode.py or equivalent
import requests, os

def process_video_with_heartbeat(video_id):
    result = process_video(video_id)
    if result.success:
        url = os.environ.get('VIGILMON_VEDA_HEARTBEAT_URL')
        if url:
            requests.get(url, timeout=5)
    return result

For the VEDA web service itself, add an HTTP monitor:

http://veda.yourdomain.com/api/v1/status

Step 7: OAuth/SSO authentication monitoring

Open edX uses OAuth2 for SSO. If the OAuth service fails, users can't log in even when the LMS itself is healthy.

Add a dedicated monitor for the OAuth endpoint:

https://lms.yourdomain.com/oauth2/access_token

Expect a 400 (Bad Request — missing credentials) rather than a 500 or timeout. A 400 means the OAuth service is alive and responding. A 500 or timeout means the auth layer is broken.

# Health check for OAuth service
import urllib.request, urllib.parse

def check_oauth_endpoint(base_url):
    """Returns True if OAuth endpoint responds (even with 400/401)."""
    try:
        data = urllib.parse.urlencode({'grant_type': 'client_credentials'}).encode()
        req = urllib.request.Request(f'{base_url}/oauth2/access_token', data=data)
        urllib.request.urlopen(req, timeout=5)
    except urllib.error.HTTPError as e:
        # 400/401 = endpoint is alive
        return e.code in (400, 401)
    except Exception:
        return False

In Vigilmon, set the HTTP monitor to accept status codes 400 and 401 as healthy responses.


Step 8: Alerts and on-call routing

Configure notification channels in Vigilmon:

Slack (recommended for ops team):

  1. Create an incoming webhook in Slack
  2. Go to Notifications → New Channel → Slack
  3. Configure separate channels for critical alerts (LMS down) vs. warning alerts (VEDA heartbeat missed)

Email:

  1. Go to Notifications → New Channel → Email
  2. Set up a distribution list that includes course operations and technical staff

Sample alert messages:

🔴 DOWN: Open edX LMS
URL: https://lms.yourdomain.com/heartbeat
Status: 502 Bad Gateway
Detected: 3 minutes ago

🔴 MISSED: Open edX Celery Workers heartbeat
Expected every: 10 minutes
Last received: 23 minutes ago

Complete monitoring coverage

| Component | Monitoring method | |---|---| | LMS web server (port 8000) | HTTP monitor on /heartbeat | | Studio/CMS (port 8001) | HTTP monitor on /heartbeat | | Celery async workers | Heartbeat via recurring Celery beat task | | Redis cache + broker | Heartbeat via cron health check | | MySQL database | Validated in /heartbeat response | | MongoDB (XBlock/modulestore) | Heartbeat via cron health check | | XBlock rendering | HTTP monitor on a course content URL | | VEDA video transcoding | Heartbeat on video processing task | | OAuth/SSO | HTTP monitor accepting 400/401 | | Grade processing | Heartbeat on grade computation task | | Certificate generation | Heartbeat on certificate task |


Next steps

  • Add response time monitoring to the LMS — a p95 latency spike is often a leading indicator of an impending database issue
  • Monitor your Celery queue length separately — a growing queue means workers are behind, even if they're alive
  • Set up a synthetic course enrollment flow as a canary: if enrollment completes, your full LMS stack is functional

Get started free at vigilmon.online.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →