productivity

Is Grocy Down? Real-Time Status & Outage Checker

Is Grocy Down? Real-Time Status & Outage Checker

Grocy is an open-source ERP system for your home with 7,000+ GitHub stars. It tracks groceries, inventory, recipes, meal planning, shopping lists, chores, and equipment maintenance — all in one PHP-based web app with a full REST API. Created by Bernd Bestel, Grocy integrates with barcode scanners for rapid stock entry and supports deep customization via custom fields and automations. It is used by self-hosters worldwide who want to reduce food waste, manage household supply levels, and track recurring chores without depending on proprietary apps or cloud services.

When Grocy goes down, barcode scanning sessions fail mid-workflow, shopping lists become inaccessible, and automated chore tracking stops updating. Because Grocy relies on a single SQLite file and a PHP runtime, issues like database locks, PHP-FPM restarts, or botched upgrades can take the entire instance offline without warning.

Quick Status Check

#!/bin/bash
# Grocy health check script
# Tests REST API, process, port, SQLite database, and database changed time

GROCY_URL="${GROCY_URL:-http://localhost:8080}"
GROCY_API_KEY="${GROCY_API_KEY:-your-api-key-here}"
DB_PATH="${DB_PATH:-/var/www/grocy/data/grocy.db}"

echo "=== Grocy Health Check ==="

# 1. Check PHP-FPM or web server process
if pgrep -x php-fpm &>/dev/null || pgrep -x apache2 &>/dev/null || pgrep -x nginx &>/dev/null; then
  echo "[OK] Web server process is running"
else
  echo "[FAIL] No web server process (php-fpm/apache2/nginx) detected"
fi

# 2. Check port 8080 is responding
if nc -z localhost 8080 2>/dev/null; then
  echo "[OK] Port 8080 is open"
else
  echo "[FAIL] Port 8080 is not responding"
fi

# 3. Grocy system info API
INFO=$(curl -sf \
  -H "GROCY-API-KEY: ${GROCY_API_KEY}" \
  "${GROCY_URL}/api/system/info" 2>/dev/null)
if echo "$INFO" | grep -q '"grocy_version"'; then
  VERSION=$(echo "$INFO" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('grocy_version','?'))" 2>/dev/null)
  echo "[OK] Grocy API responding — version ${VERSION}"
else
  echo "[FAIL] Grocy system/info API not responding: ${INFO:0:120}"
fi

# 4. Check database changed time (health indicator)
DB_TIME=$(curl -sf \
  -H "GROCY-API-KEY: ${GROCY_API_KEY}" \
  "${GROCY_URL}/api/system/db-changed-time" 2>/dev/null)
if echo "$DB_TIME" | grep -q '"changed_time"'; then
  echo "[OK] Database health endpoint responding"
else
  echo "[FAIL] Database changed-time endpoint failed: ${DB_TIME:0:80}"
fi

# 5. Check SQLite database file exists and is non-empty
if [ -f "$DB_PATH" ]; then
  DB_SIZE=$(du -sh "$DB_PATH" 2>/dev/null | awk '{print $1}')
  echo "[OK] SQLite database file exists (${DB_SIZE})"
else
  echo "[FAIL] SQLite database not found at: $DB_PATH"
fi

echo "=== Check complete ==="

Python Health Check

#!/usr/bin/env python3
"""
Grocy health check
Checks REST API, system config, product count, stock count, and overdue tasks.
"""

import os
import sys
import requests

GROCY_URL = os.environ.get("GROCY_URL", "http://localhost:8080")
GROCY_API_KEY = os.environ.get("GROCY_API_KEY", "your-api-key-here")
TIMEOUT = 10
OVERDUE_TASK_WARN_THRESHOLD = 10

HEADERS = {
    "GROCY-API-KEY": GROCY_API_KEY,
    "Accept": "application/json",
}

def check(label: str, ok: bool, detail: str = "") -> bool:
    status = "OK  " if ok else "FAIL"
    msg = f"[{status}] {label}"
    if detail:
        msg += f" — {detail}"
    print(msg)
    return ok

results = []

# 1. System info (version + PHP version)
try:
    r = requests.get(f"{GROCY_URL}/api/system/info", headers=HEADERS, timeout=TIMEOUT)
    ok = r.status_code == 200
    data = r.json() if ok else {}
    grocy_ver = data.get("grocy_version", "unknown")
    php_ver = data.get("php_version", "unknown")
    results.append(check("System info API", ok, f"Grocy {grocy_ver}, PHP {php_ver}"))
except Exception as e:
    results.append(check("System info API", False, str(e)))

# 2. System configuration
try:
    r = requests.get(f"{GROCY_URL}/api/system/config", headers=HEADERS, timeout=TIMEOUT)
    ok = r.status_code == 200
    key_count = len(r.json()) if ok else 0
    results.append(check("System config endpoint", ok, f"{key_count} config keys returned"))
except Exception as e:
    results.append(check("System config endpoint", False, str(e)))

# 3. Product count
try:
    r = requests.get(f"{GROCY_URL}/api/objects/products", headers=HEADERS, timeout=TIMEOUT)
    ok = r.status_code == 200
    products = r.json() if ok else []
    count = len(products)
    results.append(check("Products accessible", ok, f"{count} product(s) in database"))
except Exception as e:
    results.append(check("Products accessible", False, str(e)))

# 4. Current stock item count
try:
    r = requests.get(f"{GROCY_URL}/api/stock", headers=HEADERS, timeout=TIMEOUT)
    ok = r.status_code == 200
    stock = r.json() if ok else []
    count = len(stock)
    results.append(check("Stock entries accessible", ok, f"{count} stock item(s)"))
except Exception as e:
    results.append(check("Stock entries accessible", False, str(e)))

# 5. Overdue tasks (warn if more than threshold pending)
try:
    r = requests.get(f"{GROCY_URL}/api/tasks", headers=HEADERS, timeout=TIMEOUT)
    ok = r.status_code == 200
    tasks = r.json() if ok else []
    overdue = [t for t in tasks if t.get("done") == 0] if ok else []
    overdue_count = len(overdue)
    warn = overdue_count > OVERDUE_TASK_WARN_THRESHOLD
    label = f"{overdue_count} overdue task(s)"
    if warn:
        label += f" (WARNING: exceeds threshold of {OVERDUE_TASK_WARN_THRESHOLD})"
    results.append(check("Tasks endpoint", ok and not warn, label))
except Exception as e:
    results.append(check("Tasks endpoint", False, str(e)))

# 6. Database changed-time (health indicator)
try:
    r = requests.get(
        f"{GROCY_URL}/api/system/db-changed-time", headers=HEADERS, timeout=TIMEOUT
    )
    ok = r.status_code == 200 and "changed_time" in r.json()
    ts = r.json().get("changed_time", "unknown") if ok else ""
    results.append(check("Database health indicator", ok, f"last changed: {ts}"))
except Exception as e:
    results.append(check("Database health indicator", False, str(e)))

# Summary
passed = sum(results)
total = len(results)
print(f"\n{'='*40}")
print(f"Grocy health: {passed}/{total} checks passed")
if passed < total:
    print("Action required: review FAIL items above")
    sys.exit(1)
else:
    print("All systems operational")
    sys.exit(0)

Common Grocy Outage Causes

SymptomLikely CauseResolution
502 Bad Gateway on all pages PHP-FPM OOM crash (memory limit exceeded, often during large API requests) Restart PHP-FPM; increase pm.max_children and PHP memory limit; check error logs
API returns 500, database locked error in logs SQLite database locked (concurrent barcode scan + API access at the same moment) SQLite allows only one writer; add retry logic in integrations; consider WAL mode (PRAGMA journal_mode=WAL;)
Barcode scanner stops adding stock Barcode scanner integration broken after Grocy version update (API endpoint or field change) Check Grocy changelog for breaking API changes; update scanner app or companion tool (Grocy Android, Barcode Buddy)
Custom views or automations return errors Custom views/automations broken after upgrade (Twig template or field name changes) Review Grocy release notes; inspect custom view templates for deprecated field references; rebuild affected views
Server crashes, all data lost Backup not configured — SQLite data lives in a single file with no redundancy Configure daily SQLite backup (sqlite3 grocy.db .dump > backup.sql); use Grocy's built-in backup download regularly
All integrations stop authenticating API key regenerated in Grocy settings (all integrations use the old key) Update the API key in every integration: Barcode Buddy, Home Assistant, custom scripts; use a dedicated integration user

Architecture Overview

ComponentFunctionFailure Impact
PHP-FPM / web server Executes Grocy PHP application; serves web UI and REST API Complete outage; all pages and API calls return 502
SQLite database Single-file store for all Grocy data: products, stock, tasks, recipes App fails to start or returns 500; data loss if corrupted without backup
REST API layer Provides programmatic access for integrations (Home Assistant, Barcode Buddy, apps) All API-based integrations stop functioning; web UI may still work
Barcode scanner integration Maps barcodes to products via API; enables rapid stock entry Manual stock entry required; shopping and inventory workflows disrupted
Custom fields and views User-defined Twig templates for custom data display and entry forms Custom pages error out; standard Grocy pages remain functional
Grocy-Android / companion apps Mobile clients that consume Grocy API for on-the-go stock management Mobile users can't scan, add stock, or view shopping lists

Uptime History

DateIncident TypeDurationImpact
2026-01 PHP-FPM OOM crash during bulk API import ~45 min Complete service outage; web UI and API unavailable until restart
2025-10 SQLite database lock (concurrent barcode + API writes) ~20 min API intermittently returning 500; barcode scan failures
2025-08 API key regenerated after security review ~2 hrs All integrations (Home Assistant, Barcode Buddy) broke until key updated
2025-07 Custom view broken after minor version upgrade ~1 hr Custom inventory dashboards returned Twig errors; core UI unaffected

Monitor Grocy Automatically

Grocy runs silently in the background, and a crashed PHP-FPM process or locked SQLite database can go unnoticed for hours — you only find out when a barcode scan fails at the pantry or a shopping list won't load at the grocery store. ezmon.com monitors your Grocy endpoints from multiple external probes and alerts your team via Slack, PagerDuty, or SMS the moment the system info API stops responding or the database health endpoint fails.

Set up Grocy monitoring free at ezmon.com →

grocyhousehold-managementself-hostedhome-erpinventorystatus-checker