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
| Symptom | Likely Cause | Resolution |
|---|---|---|
| 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
| Component | Function | Failure 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
| Date | Incident Type | Duration | Impact |
|---|---|---|---|
| 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.