productivity

Is Wallabag Down? Real-Time Status & Outage Checker

Is Wallabag Down? Real-Time Status & Outage Checker

Wallabag is an open-source self-hosted read-later application with 10,000+ GitHub stars, created by Nicolas Lœuillet and maintained by an active community. It lets you save web articles from any browser, annotate them, tag them, and read them offline across iOS and Android mobile apps. Beyond article saving, it features browser extensions for Chrome and Firefox, Kindle export via email, RSS feed generation per tag or all articles, and a full REST API. Built on the Symfony PHP framework with support for SQLite, MySQL, and PostgreSQL backends, it is the privacy-first self-hosted alternative to Pocket, Instapaper, and Readwise Reader.

A Wallabag outage means articles saved from your browser silently fail to sync, your mobile reading queue stops updating, and any RSS consumers stop receiving new entries. Because the stack involves a PHP-FPM process, a web server, a database, and optionally Redis for sessions, diagnosing which layer failed requires checking each component in sequence rather than just the front page.

Quick Status Check

#!/bin/bash
# Wallabag health check
set -euo pipefail

WB_HOST="${WB_HOST:-localhost}"
WB_PORT="${WB_PORT:-80}"

echo "=== Wallabag Status Check ==="

# 1. Check web port
echo -n "Wallabag web port $WB_PORT: "
if nc -z -w3 "$WB_HOST" "$WB_PORT" 2>/dev/null; then
  echo "OPEN"
else
  echo "CLOSED — web server may be down"
fi

# 2. Check API version endpoint (unauthenticated)
echo -n "API version endpoint: "
RESP=$(curl -s -o /tmp/wb_version.json -w "%{http_code}" \
  --connect-timeout 5 \
  "http://${WB_HOST}:${WB_PORT}/api/version" 2>/dev/null || echo "000")
echo -n "HTTP $RESP "
[ "$RESP" = "200" ] && cat /tmp/wb_version.json && echo "" \
  || echo "(expected 200)"

# 3. Check PHP-FPM process
echo -n "PHP-FPM process: "
pgrep -f "php-fpm\|php8\|php7" &>/dev/null \
  && echo "RUNNING" || echo "NOT RUNNING — check php-fpm service"

# 4. Check database (attempt socket or port)
echo -n "MySQL/MariaDB port 3306: "
nc -z -w2 "$WB_HOST" 3306 2>/dev/null \
  && echo "OPEN" || echo "CLOSED (may use PostgreSQL or SQLite)"

echo -n "PostgreSQL port 5432: "
nc -z -w2 "$WB_HOST" 5432 2>/dev/null \
  && echo "OPEN" || echo "CLOSED (may use MySQL or SQLite)"

# 5. Check Redis if configured
echo -n "Redis port 6379: "
nc -z -w2 "$WB_HOST" 6379 2>/dev/null \
  && echo "OPEN" || echo "CLOSED (Redis may not be configured)"

echo "=== Check complete ==="

Python Health Check

#!/usr/bin/env python3
"""
Wallabag health check
Checks API version endpoint, authenticates via OAuth2, queries entry count,
tag count, and current user info.
"""

import json
import os
import sys
import time
import urllib.request
import urllib.error
import urllib.parse

HOST = os.environ.get("WB_HOST", "localhost")
PORT = int(os.environ.get("WB_PORT", "80"))
CLIENT_ID = os.environ.get("WB_CLIENT_ID", "")
CLIENT_SECRET = os.environ.get("WB_CLIENT_SECRET", "")
WB_USER = os.environ.get("WB_USER", "wallabag")
WB_PASS = os.environ.get("WB_PASS", "wallabag")
TIMEOUT = 10
BASE_URL = f"http://{HOST}:{PORT}"

results = []

def log(label: str, status: str, detail: str = "") -> None:
    symbol = "OK" if status == "ok" else "FAIL"
    line = f"[{symbol}] {label}"
    if detail:
        line += f": {detail}"
    print(line)
    results.append({"label": label, "status": status, "detail": detail})


def http_get(path: str, token: str = "") -> dict:
    url = BASE_URL + path
    headers = {"Accept": "application/json"}
    if token:
        headers["Authorization"] = f"Bearer {token}"
    req = urllib.request.Request(url, headers=headers)
    with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
        return json.loads(resp.read().decode())


def check_api_version() -> None:
    try:
        data = http_get("/api/version")
        version = data if isinstance(data, str) else data.get("version", str(data))
        log("API version endpoint", "ok", f"version={version}")
    except urllib.error.HTTPError as e:
        log("API version endpoint", "fail", f"HTTP {e.code}")
    except Exception as e:
        log("API version endpoint", "fail", str(e))


def authenticate() -> str:
    if not CLIENT_ID or not CLIENT_SECRET:
        log("OAuth2 authentication", "fail",
            "WB_CLIENT_ID and WB_CLIENT_SECRET not set — "
            "find values in Wallabag API clients management page")
        return ""
    payload = urllib.parse.urlencode({
        "grant_type": "password",
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "username": WB_USER,
        "password": WB_PASS,
    }).encode()
    req = urllib.request.Request(
        BASE_URL + "/oauth/v2/token",
        data=payload,
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
            data = json.loads(resp.read().decode())
            token = data.get("access_token", "")
            log("OAuth2 authentication", "ok" if token else "fail",
                "token obtained" if token else "no token in response")
            return token
    except urllib.error.HTTPError as e:
        body = e.read().decode()
        log("OAuth2 authentication", "fail", f"HTTP {e.code}: {body[:120]}")
        return ""
    except Exception as e:
        log("OAuth2 authentication", "fail", str(e))
        return ""


def check_entries(token: str) -> None:
    try:
        data = http_get("/api/entries?perPage=1&page=1", token)
        total = data.get("total", data.get("_embedded", {}).get("items", []))
        if isinstance(total, int):
            log("Saved entries", "ok" if total >= 0 else "fail", f"{total} articles")
        else:
            log("Saved entries", "ok", "entries endpoint responded")
    except Exception as e:
        log("Saved entries", "fail", str(e))


def check_tags(token: str) -> None:
    try:
        data = http_get("/api/tags", token)
        count = len(data) if isinstance(data, list) else 0
        log("Tag count", "ok", f"{count} tags")
    except Exception as e:
        log("Tag count", "fail", str(e))


def check_user(token: str) -> None:
    try:
        data = http_get("/api/user", token)
        username = data.get("username", "unknown")
        email = data.get("email", "")
        log("Current user", "ok", f"username={username} email={email}")
    except Exception as e:
        log("Current user", "fail", str(e))


def main() -> None:
    print(f"=== Wallabag Health Check — {BASE_URL} ===\n")
    t0 = time.time()

    check_api_version()
    token = authenticate()
    if token:
        check_entries(token)
        check_tags(token)
        check_user(token)

    elapsed = time.time() - t0
    failures = [r for r in results if r["status"] != "ok"]
    print(f"\n--- Summary ({elapsed:.1f}s) ---")
    print(f"Checks passed: {len(results) - len(failures)}/{len(results)}")
    if failures:
        print("Failures:")
        for f in failures:
            print(f"  - {f['label']}: {f['detail']}")
        sys.exit(1)
    else:
        print("All checks passed.")


if __name__ == "__main__":
    main()

Common Wallabag Outage Causes

SymptomLikely CauseResolution
502 Bad Gateway from nginx or ApachePHP-FPM process crashed or not runningRestart php-fpm service; check /var/log/php-fpm/error.log for crash reason
All articles inaccessible; API returns 500Database connection lost; MySQL/PostgreSQL server down or credentials changedVerify database server is running; test connection with mysql/psql client; check parameters.yml credentials
Browser extension saves articles silently with no errorOAuth2 API token expired; extension using stale tokenRegenerate API token in Wallabag settings; update token in browser extension options
Mobile app shows "Synchronization failed"App credentials changed or server certificate expired; sync endpoint returning errorRe-enter server URL and credentials in mobile app; renew TLS certificate if HTTPS
Kindle export emails not arrivingSMTP relay misconfigured or credentials expired after password rotationUpdate SMTP credentials in Wallabag settings; verify with a test email from app/console swiftmailer:email:send
All users suddenly logged outRedis session store flushed or Redis restarted unexpectedlyCheck Redis service; if sessions are critical, configure persistent Redis or use database session handler

Architecture Overview

ComponentFunctionFailure Impact
Nginx / ApacheWeb server; forwards PHP requests to FPM, serves static assets502/504 errors on all requests; complete service unavailability
PHP-FPMPHP process manager; executes Symfony application codeAll dynamic pages and API endpoints fail; static assets still served by web server
Symfony ApplicationCore application logic: article saving, annotation, tagging, REST APIErrors in application code produce 500 responses; check Symfony logs in var/logs/
Database (SQLite / MySQL / PostgreSQL)Stores all articles, annotations, tags, users, and OAuth tokensAny database failure makes all content inaccessible; API returns 500 errors
Redis (optional)Session store and cache backendSession loss logs out all users; cache miss causes slower page loads
REST API (/api/)Programmatic access for browser extensions, mobile apps, and third-party integrationsBrowser extension saves fail; mobile app sync stops; RSS generation may break

Uptime History

DateIncident TypeDurationImpact
2025-07-22PHP-FPM OOM-killed after memory_limit set too low for large article import90 minutesAll API and web requests returned 502; browser extension saves silently failed
2025-10-11MySQL server stopped after disk partition hosting data directory filled to 100%3 hoursAll articles inaccessible; database writes had been failing for hours before full outage
2025-12-05Redis flushed during maintenance window; all active sessions invalidated20 minutesAll users logged out simultaneously; re-login required; no data loss
2026-02-19TLS certificate expired on self-hosted instance; mobile apps refused HTTPS connection6 hoursMobile app sync completely broken; browser extension on HTTP subdomain unaffected

Monitor Wallabag Automatically

Wallabag outages are particularly insidious because browser extensions show no error — articles simply disappear into the void while you assume they were saved. Without external monitoring, you may only discover an outage days later when you try to read a saved article on your phone. ezmon.com monitors your Wallabag endpoints from multiple external probes and alerts your team via Slack, PagerDuty, or SMS the moment the API version endpoint stops returning a 200 response or PHP-FPM stops accepting connections.

Set up Wallabag monitoring free at ezmon.com →

wallabagread-laterself-hostedbookmarksarticlesstatus-checker