tutorial

How to Monitor Your Self-Hosted AVideo Instance (Web, Encoding, HLS, RTMP, and More)

AVideo's video pipeline has a lot of failure points — encoding jobs, HLS streaming, RTMP live streams, and MySQL. Here's how to monitor them all and get instant alerts when something breaks.

How to Monitor Your Self-Hosted AVideo Instance (Web, Encoding, HLS, RTMP, and More)

Self-hosting a video platform is a serious operational commitment. AVideo (formerly YouPHPTube) manages a complex stack: video upload and encoding pipelines, HLS adaptive streaming endpoints, live RTMP ingestion, MySQL storage, thumbnail generation, and optional CDN offloading. Each one is a potential failure point — and unlike a blog going down, a broken video platform means users staring at a loading spinner with no useful error message.

This guide covers comprehensive monitoring for every layer of a self-hosted AVideo deployment.


Where AVideo breaks

AVideo runs on PHP behind Apache or Nginx, backed by MySQL, and relies on several background processes that can fail silently:

Web server outages — PHP crashes, mod_rewrite misconfiguration, or exhausted connection pools cause the web interface to go dark.

Video encoding failures — uploaded videos must be transcoded before they're watchable. If the FFmpeg encoding process fails or stalls, uploads appear to succeed but never become available. Users wait indefinitely.

HLS streaming endpoint failures — HLS segments are served from the filesystem or a CDN. A misconfigured path, disk-full event, or CDN origin pull failure causes video playback to buffer indefinitely.

MySQL connectivity issues — AVideo stores video metadata, user accounts, and configuration in MySQL. Connection exhaustion or a slow query cascade causes request failures across the application.

RTMP live streaming failures — if you're using Nginx-RTMP or a standalone RTMP server for live streams, an RTMP daemon crash kills all active live broadcasts. No error is surfaced to viewers — the stream just stops.

Thumbnail generation failures — thumbnails are generated via FFmpeg as part of the encoding pipeline. A silent failure here results in blank thumbnails across the catalog, which affects user experience without causing a visible error.


Step 1: Create a health check endpoint

AVideo doesn't ship a dedicated health endpoint, but you can add one that checks the components that matter most:

<?php
// /var/www/avideo/health.php

header('Content-Type: application/json');

$checks = [];
$status = 200;

// Database connectivity
try {
    $dbConfig = include('/var/www/avideo/videos/configuration.php');
    $pdo = new PDO(
        "mysql:host={$dbConfig['db_host']};dbname={$dbConfig['db_name']}",
        $dbConfig['db_user'],
        $dbConfig['db_pass'],
        [PDO::ATTR_TIMEOUT => 3, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
    );
    $pdo->query('SELECT 1');
    $checks['database'] = 'ok';
} catch (Exception $e) {
    $checks['database'] = 'error';
    $status = 500;
}

// Videos directory writable (encoding output destination)
$videosDir = '/var/www/avideo/videos/_v';
$checks['videos_dir_writable'] = is_writable($videosDir) ? 'ok' : 'error';
if ($checks['videos_dir_writable'] === 'error') {
    $status = 500;
}

// Check FFmpeg is available
exec('which ffmpeg 2>/dev/null', $output, $returnCode);
$checks['ffmpeg'] = ($returnCode === 0) ? 'ok' : 'error';
if ($checks['ffmpeg'] === 'error') {
    $status = 500;
}

// Check disk space (fail if < 5GB free)
$freeBytes = disk_free_space('/var/www/avideo/videos');
$checks['disk_space'] = ($freeBytes > 5 * 1024 * 1024 * 1024) ? 'ok' : 'warning';

http_response_code($status);
echo json_encode([
    'status' => $status === 200 ? 'healthy' : 'degraded',
    'checks' => $checks,
    'free_disk_gb' => round($freeBytes / 1024 / 1024 / 1024, 1),
    'timestamp' => date('c'),
]);

Restrict access to this endpoint:

location = /health.php {
    allow 203.0.113.0/24;  # Vigilmon probe IPs
    allow 127.0.0.1;
    deny all;
    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

Test it:

curl http://localhost/health.php
# {"status":"healthy","checks":{"database":"ok","videos_dir_writable":"ok","ffmpeg":"ok","disk_space":"ok"},"free_disk_gb":120.5}

Step 2: Set up HTTP monitoring in Vigilmon

Point Vigilmon at your health endpoint and key application URLs:

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

Create these monitors:

| Monitor name | URL | Expected | |---|---|---| | AVideo Health | https://video.yourdomain.com/health.php | 200 + "status":"healthy" | | AVideo Homepage | https://video.yourdomain.com/ | 200 | | AVideo API | https://video.yourdomain.com/videos/api.php | 200 | | AVideo Login | https://video.yourdomain.com/?pageAction=login | 200 |

Each monitor checks independently. If the API is broken but the homepage is fine, you know it's a routing or API-layer issue, not a PHP crash.


Step 3: Monitor the HLS streaming endpoint

HLS streaming is the delivery mechanism for all video playback. A broken HLS endpoint means videos silently buffer forever for viewers.

Create a dedicated HTTP monitor for your HLS segment path. AVideo typically serves HLS content from:

https://video.yourdomain.com/videos/_v/{video-slug}/video.m3u8

Pick a known-good video from your catalog and use its M3U8 playlist URL as your monitor target:

  1. Find the direct URL of a published video's HLS manifest in AVideo's admin panel
  2. In Vigilmon, create an HTTP monitor for that URL
  3. Add an assertion: response body contains #EXTM3U (the HLS manifest header)
Monitor: AVideo HLS Streaming
URL: https://video.yourdomain.com/videos/_v/test-video/video.m3u8
Assert body contains: #EXTM3U

If the segments directory is misconfigured, disk space fills up, or a CDN origin pull fails, this monitor catches it immediately.

CDN integration health

If you're using a CDN for HLS segment delivery, monitor the CDN URL separately from the origin:

Monitor: AVideo CDN HLS
URL: https://cdn.yourdomain.com/videos/_v/test-video/video.m3u8
Assert body contains: #EXTM3U

This lets you distinguish a CDN cache failure from an origin failure.


Step 4: Heartbeat monitoring for video encoding

Video encoding is AVideo's most critical background process and the most likely to fail silently. A failed encoding job shows as a perpetually-processing video in the admin panel.

AVideo runs encoding jobs via the download.php endpoint or as background shell processes. Wrap the encoding check with a heartbeat:

#!/bin/bash
# /opt/avideo-jobs/check-encoding.sh
# Runs every 10 minutes. If AVideo can encode a test file, ping the heartbeat.

AVIDEO_DIR="/var/www/avideo"
TEST_VIDEO="/opt/avideo-jobs/test.mp4"
OUTPUT_DIR="/tmp/avideo-encoding-test"

mkdir -p "$OUTPUT_DIR"

# Attempt a quick FFmpeg transcode (10-second clip)
if ffmpeg -y -i "$TEST_VIDEO" -t 10 -c:v libx264 -preset ultrafast \
    "$OUTPUT_DIR/test_output.mp4" > /dev/null 2>&1; then
    curl -fsS --retry 3 "$VIGILMON_ENCODING_HEARTBEAT_URL" > /dev/null 2>&1
    echo "[$(date)] Encoding health check passed"
else
    echo "[$(date)] Encoding health check FAILED" >&2
fi

rm -rf "$OUTPUT_DIR"

For a more AVideo-integrated check, monitor whether encoding jobs are completing by querying MySQL:

<?php
// /opt/avideo-jobs/check-encoding-queue.php
// Checks for stuck encoding jobs and pings heartbeat if queue is healthy

$dbConfig = include('/var/www/avideo/videos/configuration.php');
$pdo = new PDO(
    "mysql:host={$dbConfig['db_host']};dbname={$dbConfig['db_name']}",
    $dbConfig['db_user'],
    $dbConfig['db_pass']
);

// Check for videos stuck in encoding for > 2 hours
$stmt = $pdo->query("
    SELECT COUNT(*) as stuck
    FROM videos
    WHERE status = 'encoding'
    AND updated < DATE_SUB(NOW(), INTERVAL 2 HOUR)
");
$row = $stmt->fetch(PDO::FETCH_ASSOC);

if ($row['stuck'] == 0) {
    // Queue is healthy — send heartbeat
    $heartbeatUrl = getenv('VIGILMON_ENCODING_HEARTBEAT_URL');
    if ($heartbeatUrl) {
        $ch = curl_init($heartbeatUrl);
        curl_setopt($ch, CURLOPT_TIMEOUT, 5);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_exec($ch);
        curl_close($ch);
        echo "Encoding queue healthy, heartbeat sent\n";
    }
} else {
    echo "WARNING: {$row['stuck']} video(s) stuck in encoding\n";
    exit(1);
}

Add to crontab:

*/10 * * * * www-data php /opt/avideo-jobs/check-encoding-queue.php >> /var/log/avideo/encoding-health.log 2>&1

Step 5: Monitor the RTMP live streaming endpoint

If you're using Nginx-RTMP for live streaming, the RTMP statistics endpoint gives you visibility into active stream state:

# In your nginx.conf RTMP block
rtmp {
    server {
        listen 1935;
        application live {
            live on;
            hls on;
            hls_path /var/www/avideo/videos/hls;
        }
    }
}

# Expose RTMP stats via HTTP
http {
    server {
        listen 8080;
        location /rtmp-stat {
            allow 127.0.0.1;
            allow 203.0.113.0/24;
            deny all;
            rtmp_stat all;
        }
    }
}

Create an HTTP monitor for http://video.yourdomain.com:8080/rtmp-stat checking for a 200 response. This confirms the RTMP statistics server (and the Nginx-RTMP module) is running.

For a heartbeat approach that confirms RTMP is accepting connections:

#!/bin/bash
# /opt/avideo-jobs/check-rtmp.sh
# Uses ffmpeg to probe the RTMP server

if ffprobe -v quiet -timeout 5000000 rtmp://localhost/live 2>&1 | grep -q "Connection refused\|failed"; then
    echo "[$(date)] RTMP server not accepting connections" >&2
    exit 1
else
    curl -fsS "$VIGILMON_RTMP_HEARTBEAT_URL" > /dev/null 2>&1
fi
*/5 * * * * root /opt/avideo-jobs/check-rtmp.sh >> /var/log/avideo/rtmp-health.log 2>&1

Step 6: Thumbnail generation service heartbeat

Thumbnails are generated via FFmpeg during the encoding pipeline. If FFmpeg fails to extract frames, thumbnail generation fails silently. Monitor it:

#!/bin/bash
# /opt/avideo-jobs/check-thumbnails.sh

TEST_VIDEO="/opt/avideo-jobs/test.mp4"
OUTPUT="/tmp/test-thumbnail.jpg"

if ffmpeg -y -i "$TEST_VIDEO" -ss 00:00:01 -vframes 1 "$OUTPUT" > /dev/null 2>&1; then
    if [ -s "$OUTPUT" ]; then
        curl -fsS "$VIGILMON_THUMBNAIL_HEARTBEAT_URL" > /dev/null 2>&1
        rm -f "$OUTPUT"
    fi
fi
0 * * * * www-data /opt/avideo-jobs/check-thumbnails.sh >> /var/log/avideo/thumbnails.log 2>&1

Step 7: Search indexing service heartbeat

AVideo's search relies on a MySQL full-text index over video titles, descriptions, and tags. If the index gets corrupted or queries start timing out, search results go stale or return errors.

<?php
// /opt/avideo-jobs/check-search.php

$dbConfig = include('/var/www/avideo/videos/configuration.php');
$pdo = new PDO(
    "mysql:host={$dbConfig['db_host']};dbname={$dbConfig['db_name']}",
    $dbConfig['db_user'],
    $dbConfig['db_pass']
);
$pdo->setAttribute(PDO::ATTR_TIMEOUT, 5);

try {
    // Test the full-text search index
    $stmt = $pdo->query("SELECT COUNT(*) FROM videos WHERE MATCH(title, description) AGAINST('test' IN BOOLEAN MODE)");
    $stmt->fetch();

    $heartbeatUrl = getenv('VIGILMON_SEARCH_HEARTBEAT_URL');
    if ($heartbeatUrl) {
        $ch = curl_init($heartbeatUrl);
        curl_setopt_array($ch, [CURLOPT_TIMEOUT => 5, CURLOPT_RETURNTRANSFER => true]);
        curl_exec($ch);
        curl_close($ch);
    }
    echo "Search index healthy\n";
} catch (Exception $e) {
    echo "Search index error: " . $e->getMessage() . "\n";
    exit(1);
}
*/30 * * * * www-data php /opt/avideo-jobs/check-search.php

Step 8: Alerts configuration

Set up notification channels in Vigilmon:

Slack (recommended for video ops):

  1. Create a Slack incoming webhook for your ops channel
  2. Go to Notifications → New Channel → Slack in Vigilmon
  3. Paste the webhook URL and assign it to your monitors

Email:

  1. Go to Notifications → New Channel → Email
  2. Add your technical team email
  3. Enable on all critical monitors (web health, HLS endpoint, MySQL)

A missed encoding heartbeat alert looks like:

🔴 MISSED: AVideo Encoding Pipeline
Expected every: 15 minutes
Last received: 42 minutes ago
Action: Check FFmpeg processes and /var/log/avideo/encoding-health.log

Full monitoring coverage

| Component | Monitoring method | |---|---| | Web server + PHP | HTTP monitor on /health.php | | MySQL connectivity | Validated inside health endpoint | | HLS streaming endpoint | HTTP monitor on /videos/_v/test/video.m3u8 | | CDN integration | HTTP monitor on CDN HLS URL | | Video encoding pipeline | Heartbeat via queue health check | | RTMP live streaming | HTTP monitor on Nginx-RTMP stats + heartbeat | | Thumbnail generation | Heartbeat via FFmpeg frame extraction test | | Search indexing | Heartbeat via full-text query test | | Disk space | Disk free check inside health endpoint |


Next steps

  • Add an alert threshold for disk space — video platforms fill disks fast, and a disk-full event kills encoding and streaming simultaneously
  • Monitor encoding queue depth as a separate metric — a growing queue is a leading indicator before jobs start failing
  • Set up a synthetic video upload test: upload a small test file and verify it moves from "encoding" to "published" status within a reasonable window

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 →