productivity

Is Audiobookshelf Down? Real-Time Status & Outage Checker

Is Audiobookshelf Down? Real-Time Status & Outage Checker

Audiobookshelf is an open-source, self-hosted audiobook and podcast server with over 8,000 GitHub stars. Built by advplyr and the community, it provides a polished web UI and native mobile apps (iOS and Android) for streaming and downloading your personal audiobook and podcast collection. It supports a wide range of audio formats — MP3, M4B, M4A, AAC, OGG, FLAC, and more — and tracks per-book listening progress, bookmarks, chapters, and series across all devices in real time. It is a private, self-hostable alternative to Audible and Spotify for audiobooks, with no subscription fees and no data sent to third parties. Built on Node.js with SQLite for metadata storage.

Because Audiobookshelf is the single point of access for your entire library, a crashed Node.js process or an unmounted media volume means complete loss of streaming for all users and all devices. Mobile clients that rely on WebSocket connections for real-time progress sync are particularly sensitive to reverse proxy misconfigurations that block WebSocket upgrades, making proper proxy setup a common silent failure mode.

Quick Status Check

#!/bin/bash
# Audiobookshelf health check
# Usage: bash check-audiobookshelf.sh [host] [port]

HOST="${1:-localhost}"
PORT="${2:-13378}"
BASE_URL="http://${HOST}:${PORT}"
MEDIA_DIR="${MEDIA_DIR:-/audiobooks}"
CONFIG_DIR="${CONFIG_DIR:-/config}"

echo "=== Audiobookshelf Health Check ==="
echo "Target: ${BASE_URL}"
echo ""

# 1. Check healthcheck endpoint
echo "[1/5] Checking healthcheck endpoint..."
HEALTH=$(curl -sf --max-time 5 "${BASE_URL}/healthcheck" 2>/dev/null)
if echo "${HEALTH}" | grep -q '"success":true'; then
  echo "  OK  /healthcheck returned success:true"
else
  echo "  FAIL  /healthcheck unreachable or returned unexpected response: ${HEALTH}"
fi

# 2. Check process / Docker container
echo "[2/5] Checking process/container..."
if docker ps --format '{{.Names}}' 2>/dev/null | grep -qi "audiobookshelf\|abs"; then
  echo "  OK  Audiobookshelf Docker container is running"
elif pgrep -f "audiobookshelf\|node.*index.js" > /dev/null 2>&1; then
  echo "  OK  Audiobookshelf Node.js process is running"
else
  echo "  WARN  No Audiobookshelf container or process detected"
fi

# 3. Check media storage directory
echo "[3/5] Checking media storage directory..."
if [ -d "${MEDIA_DIR}" ] && [ -r "${MEDIA_DIR}" ]; then
  FILE_COUNT=$(find "${MEDIA_DIR}" -maxdepth 3 -type f 2>/dev/null | wc -l | tr -d ' ')
  DISK_USAGE=$(du -sh "${MEDIA_DIR}" 2>/dev/null | cut -f1)
  echo "  OK  Media directory accessible: ~${FILE_COUNT} files, ${DISK_USAGE} used"
else
  echo "  FAIL  Media directory '${MEDIA_DIR}' not accessible — library will show empty"
fi

# 4. Check config directory is writable (SQLite lives here)
echo "[4/5] Checking config directory (SQLite)..."
if [ -d "${CONFIG_DIR}" ] && [ -w "${CONFIG_DIR}" ]; then
  echo "  OK  Config directory '${CONFIG_DIR}' is writable"
else
  echo "  WARN  Config directory '${CONFIG_DIR}' not writable — database writes may fail"
fi

# 5. Check port is listening
echo "[5/5] Checking port ${PORT}..."
if nc -z -w3 "${HOST}" "${PORT}" 2>/dev/null; then
  echo "  OK  Port ${PORT} is open"
else
  echo "  FAIL  Port ${PORT} not reachable"
fi

echo ""
echo "=== Check complete ==="

Python Health Check

#!/usr/bin/env python3
"""
Audiobookshelf health check
Verifies server health, authentication, library availability, item counts,
media directory disk usage, and WebSocket connectivity.
"""

import sys
import os
import json
import socket
import shutil
import urllib.request
import urllib.error

BASE_URL = "http://localhost:13378"
USERNAME = os.environ.get("ABS_USERNAME", "")
PASSWORD = os.environ.get("ABS_PASSWORD", "")
MEDIA_DIR = os.environ.get("ABS_MEDIA_DIR", "/audiobooks")
TIMEOUT = 10
DISK_WARN_PCT = 90


def fetch(url, token="", method="GET", data=None):
    headers = {"Accept": "application/json", "Content-Type": "application/json"}
    if token:
        headers["Authorization"] = f"Bearer {token}"
    body = json.dumps(data).encode() if data else None
    try:
        req = urllib.request.Request(url, data=body, headers=headers, method=method)
        with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
            return json.loads(resp.read().decode())
    except urllib.error.HTTPError as e:
        return {"_error": f"HTTP {e.code}"}
    except Exception as e:
        return {"_error": str(e)}


results = []
print("=== Audiobookshelf Health Check ===")
print(f"Target: {BASE_URL}\n")

# 1. Healthcheck endpoint
print("[1/6] Server healthcheck...")
r = fetch(f"{BASE_URL}/healthcheck")
if "_error" in r:
    print(f"  [FAIL] /healthcheck: {r['_error']}")
    results.append(False)
elif r.get("success") is True:
    print("  [OK  ] /healthcheck returned success:true")
    results.append(True)
else:
    print(f"  [FAIL] /healthcheck unexpected response: {r}")
    results.append(False)

# 2. Authenticate and get user info
token = ""
print("[2/6] Authentication & user info...")
if USERNAME and PASSWORD:
    r = fetch(f"{BASE_URL}/login", method="POST",
              data={"username": USERNAME, "password": PASSWORD})
    if "_error" in r:
        print(f"  [FAIL] Login failed: {r['_error']}")
        results.append(False)
    else:
        token = r.get("user", {}).get("token", "") or r.get("token", "")
        username = r.get("user", {}).get("username", "unknown")
        user_type = r.get("user", {}).get("type", "unknown")
        if token:
            print(f"  [OK  ] Authenticated as '{username}' (type: {user_type})")
            results.append(True)
        else:
            print(f"  [FAIL] Login response missing token")
            results.append(False)
else:
    print("  [INFO] ABS_USERNAME/ABS_PASSWORD not set — skipping auth checks")
    results.append(True)

# 3. Libraries
print("[3/6] Library list...")
r = fetch(f"{BASE_URL}/api/libraries", token=token)
if "_error" in r:
    print(f"  [FAIL] /api/libraries: {r['_error']}")
    results.append(False)
    libraries = []
else:
    libraries = r.get("libraries", [])
    lib_count = len(libraries)
    level = "OK  " if lib_count > 0 else "WARN"
    print(f"  [{level}] {lib_count} library/libraries found")
    if lib_count == 0:
        print("       Warning: no libraries configured — add a library in settings")
    results.append(lib_count > 0)

# 4. Check item count in first library
print("[4/6] Library item counts...")
item_check_ok = True
if libraries and token:
    for lib in libraries[:3]:  # check up to 3 libraries
        lib_id = lib.get("id", "")
        lib_name = lib.get("name", "unknown")
        r = fetch(f"{BASE_URL}/api/libraries/{lib_id}/items?limit=1", token=token)
        if "_error" in r:
            print(f"  [FAIL] Library '{lib_name}' items: {r['_error']}")
            item_check_ok = False
        else:
            total = r.get("total", 0)
            level = "OK  " if total > 0 else "WARN"
            print(f"  [{level}] Library '{lib_name}': {total:,} item(s)")
            if total == 0:
                print(f"       Warning: library '{lib_name}' is empty — media volume may be unmounted")
                item_check_ok = False
elif not token:
    print("  [INFO] Skipping item count — no auth token")
else:
    print("  [INFO] No libraries to check")
results.append(item_check_ok)

# 5. Disk usage of media directory
print("[5/6] Media directory disk usage...")
if os.path.isdir(MEDIA_DIR):
    usage = shutil.disk_usage(MEDIA_DIR)
    pct = int(usage.used / usage.total * 100) if usage.total > 0 else 0
    used_gb = usage.used / (1024 ** 3)
    total_gb = usage.total / (1024 ** 3)
    free_gb = usage.free / (1024 ** 3)
    level = "OK  " if pct < DISK_WARN_PCT else "WARN"
    print(f"  [{level}] Disk: {pct}% used — {used_gb:.1f} GB used / "
          f"{total_gb:.1f} GB total / {free_gb:.1f} GB free")
    if pct >= DISK_WARN_PCT:
        print(f"       Warning: disk > {DISK_WARN_PCT}% full — new podcast downloads may fail")
    results.append(True)
else:
    print(f"  [FAIL] Media directory '{MEDIA_DIR}' not found — library will appear empty")
    results.append(False)

# 6. WebSocket connectivity (mobile app sync depends on this)
print("[6/6] WebSocket endpoint (mobile app sync)...")
ws_host = "localhost"
ws_port = 13378
try:
    sock = socket.create_connection((ws_host, ws_port), timeout=TIMEOUT)
    handshake = (
        f"GET /socket.io/?EIO=4&transport=websocket HTTP/1.1\r\n"
        f"Host: {ws_host}:{ws_port}\r\n"
        f"Upgrade: websocket\r\n"
        f"Connection: Upgrade\r\n"
        f"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
        f"Sec-WebSocket-Version: 13\r\n\r\n"
    )
    sock.sendall(handshake.encode())
    response = sock.recv(256).decode(errors="ignore")
    sock.close()
    if "101" in response or "websocket" in response.lower() or "0{" in response:
        print(f"  [OK  ] WebSocket endpoint reachable — mobile app sync should work")
        results.append(True)
    else:
        print(f"  [WARN] WebSocket handshake unexpected — mobile app sync may fail")
        print(f"       Check reverse proxy WebSocket upgrade headers (Upgrade, Connection)")
        results.append(False)
except Exception as e:
    print(f"  [FAIL] WebSocket connection failed: {e}")
    print(f"       Mobile apps will not be able to sync listening progress")
    results.append(False)

# Summary
passed = sum(results)
total = len(results)
print(f"\n=== Summary: {passed}/{total} checks passed ===")
if passed < total:
    print("Action required: review FAIL/WARN items above.")
    sys.exit(1)
else:
    print("Audiobookshelf appears healthy.")
    sys.exit(0)

Common Audiobookshelf Outage Causes

SymptomLikely CauseResolution
Web UI and mobile apps return connection refused or 502 errors Node.js process crashed — unhandled exception or OOM killed by the host Check container logs; restart the Audiobookshelf container or service; review Node.js error logs for the crash reason
Library shows "0 books" or all covers missing after a server restart Media storage volume unmounted — NAS, external drive, or Docker volume not attached Verify the media volume is mounted and accessible; re-attach Docker volume; check docker inspect for volume binds
Progress not syncing across devices; playback position resets on app reopen SQLite database locked — concurrent writes from multiple processes or sessions Ensure only one Audiobookshelf instance is running; check for multiple containers or processes; verify config directory is on a local volume, not NFS
Mobile app cannot connect but web UI works fine; WebSocket errors in app logs Reverse proxy not forwarding WebSocket upgrades — missing Upgrade/Connection headers Add proxy_set_header Upgrade $http_upgrade and proxy_set_header Connection "upgrade" to Nginx config; verify Traefik/Caddy WebSocket settings
Book covers and metadata not updating; new podcast episodes not fetched Metadata fetch service broken — external metadata provider timeout or API change Check Audiobookshelf logs for metadata provider errors; verify outbound internet access from the container; try re-matching metadata manually
Server restart results in lost listening progress or missing books No backup configured — SQLite database and config not persisted to a named volume Map /config to a persistent Docker volume; set up regular backup of the config directory; use Audiobookshelf's built-in backup feature

Architecture Overview

ComponentFunctionFailure Impact
Node.js application server HTTP server, streaming, API, authentication, library scanning Complete loss of all access; web and mobile clients return connection errors
SQLite database (/config/absdatabase.sqlite) Stores user accounts, listening progress, bookmarks, library metadata Progress lost; library metadata unavailable; login fails if DB is corrupt
Media storage volume Holds the actual audiobook and podcast audio files served to clients Library appears empty; streaming returns 404; covers missing
Socket.IO WebSocket server Real-time progress sync between server and all connected clients Mobile apps cannot sync listening position; progress may desync across devices
Metadata provider (external) Fetches book covers, descriptions, and author info from Audible, Google Books, etc. New books have no covers or metadata; existing metadata unaffected
Podcast scheduler Periodically checks RSS feeds and downloads new podcast episodes New episodes not downloaded automatically; manual refresh still works

Uptime History

DateIncident TypeDurationImpact
Feb 2026 Node.js OOM crash on large library scan (50,000+ files) 15–60 min (until container auto-restarted) All streaming stopped; listening sessions interrupted; progress for in-flight playback lost
Nov 2025 NAS mount dropped during network switch maintenance; media volume gone 2–4 hrs Library showed empty; all streaming failed with 404; resolved when NAS remounted
Sep 2025 Reverse proxy misconfiguration after Nginx update dropped WebSocket headers 1–6 hrs (until proxy config fixed) Mobile apps fully broken; web UI worked normally; progress sync failed for all mobile users
Jul 2025 SQLite database locked after unclean container shutdown; reads blocked 15–45 min Login intermittently failed; progress reads returned errors; resolved after clean restart

Monitor Audiobookshelf Automatically

Audiobookshelf runs as a single Node.js process with no built-in watchdog or alerting — a crash, an unmounted volume, or a WebSocket proxy misconfiguration leaves all users and mobile apps without access until someone notices. ezmon.com monitors your Audiobookshelf endpoints from multiple external probes and alerts your team via Slack, PagerDuty, or SMS the moment the healthcheck stops returning success:true or your streaming endpoints go dark.

Set up Audiobookshelf monitoring free at ezmon.com →

audiobookshelfaudiobookspodcastself-hostedmedia-serverstatus-checker