Un endpoint di health check è un URL, di solito /health, che dice a un monitor o a un load balancer se la tua applicazione è in grado di servire richieste. Esegue alcuni controlli rapidi su ciò da cui l’app dipende, come il database, la cache e lo spazio libero su disco, e risponde con HTTP 200 e {“status”:”ok”} quando tutto funziona, oppure con HTTP 503 e il nome del controllo fallito quando qualcosa non va. Deve essere veloce, leggero, non in cache e privo di segreti; poi puntaci un monitor di uptime che validi la risposta, non solo il codice di stato.
Questa guida spiega la differenza tra liveness e readiness, che cosa controllare, il formato della risposta, esempi funzionanti in PHP con Laravel, Node con Express e Python con Flask, e come monitorare l’endpoint una volta creato.
Perché la homepage non è un health check
La maggior parte dei team inizia monitorando la homepage. È un primo passo sensato, ma spesso la homepage viene servita da un CDN o da una cache di pagina, quindi continua a rispondere 200 mentre il database è giù e nessuno riesce ad accedere o a completare un acquisto. Un endpoint dedicato aggira la cache e mette alla prova direttamente le dipendenze. Ti dà anche un URL prevedibile, con una risposta piccola e stabile, facile da validare.
Liveness e readiness
Le piattaforme di container come Kubernetes dividono lo stato di salute in due domande, e la distinzione è utile ovunque:
Liveness: il processo è vivo e non bloccato? Se no, va riavviato. Un controllo di liveness non dovrebbe testare il database o altri servizi. Se il database va giù e tutte le istanze dell’app falliscono il controllo di liveness, la piattaforma le riavvia tutte: il database non si ripara e il ripristino diventa più lento.
Readiness: questa istanza può servire richieste reali in questo momento? Se no, smetti di mandarle traffico. È qui che vanno i controlli sulle dipendenze.
Una struttura comune prevede /livez o /up per la liveness, che restituisce 200 finché l’app si avvia, e /health o /readyz per la readiness. Per il monitoraggio uptime esterno è l’endpoint di readiness quello interessante, perché risponde alla domanda che conta per i tuoi clienti: l’app funziona?
Che cosa controllare
Controlla le dipendenze senza le quali una normale richiesta fallirebbe, e nient’altro:
Database: esegui SELECT 1 sullo stesso pool di connessioni che usa l’app. Dimostra che il server è raggiungibile e accetta query.
Cache o archivio delle sessioni: scrivi e leggi una chiave a breve durata in Redis o Memcached. Se le sessioni stanno lì, il login dipende da questo.
Disco: controlla lo spazio libero sul volume su cui l’app scrive upload, log o sessioni. Fai fallire il controllo quando scende sotto un margine, per esempio il 10 percento.
Coda: controlla che il backend della coda risponda e, se vuoi, che il job in attesa più vecchio non superi un limite a tua scelta. Un arretrato in crescita significa che i worker si sono fermati.
Attenzione alle API di terze parti. Se il tuo endpoint chiama un provider di pagamento a ogni controllo e il provider è lento, il tuo health check fallisce anche se la tua app sta benissimo, e un load balancer potrebbe togliere tutte le istanze dalla rotazione. Monitora piuttosto le terze parti con monitor dedicati, così un avviso ti dice di chi è il problema. Il monitoraggio di terze parti di Uptimecheck segue le pagine di stato di 31 servizi come Klarna, Swish, Shopify, Cloudflare e OpenAI, oppure qualsiasi pagina ospitata su Statuspage.
Mantienilo veloce e leggero
Un health check viene eseguito ogni minuto da ogni monitor e load balancer, giorno e notte. Non deve mai diventare la causa del sovraccarico dell’app.
Dai a ogni controllo sulle dipendenze un timeout breve, per esempio uno o due secondi, così un servizio bloccato non blocca l’endpoint.
Esegui i controlli in parallelo, dove il tuo linguaggio lo rende semplice.
Usa query banali. Non contare mai le righe di una tabella grande e non generare report.
Se molti sistemi di controllo interrogano l’endpoint, tieni il risultato in memoria per qualche secondo.
Invia Cache-Control: no-store ed escludi il percorso dalle cache di pagina e dai CDN, così non vedrai mai un “ok” preso dalla cache.
Salta sessioni, cookie e middleware di autenticazione di cui l’endpoint non ha bisogno.
Non esporre segreti
Un endpoint di health pubblico è visibile a chiunque provi /health. Restituisci il nome e lo stato di ogni controllo e nient’altro. Non includere messaggi di eccezione, stringhe di connessione, hostname, indirizzi IP interni, versioni del software o variabili d’ambiente. Registra invece l’errore dettagliato nei log del server. Se vuoi una vista diagnostica più ricca, mettila su un percorso separato protetto da autenticazione o limitala alla tua rete interna.
Formato della risposta e codici di stato
Tieni il JSON piccolo e stabile:
{
“status”: “ok”,
“checks”: {
“database”: “ok”,
“cache”: “ok”,
“disk”: “ok”,
“queue”: “ok”
}
}
Quando un controllo fallisce, imposta il suo valore su “fail”, imposta lo stato principale su “fail” e cambia il codice di stato HTTP:
200 OK quando tutti i controlli obbligatori passano.
503 Service Unavailable quando un controllo obbligatorio fallisce. È il codice che load balancer e monitor si aspettano per “temporaneamente non in grado di servire”.
Facoltativamente, 200 con “status”:”degraded” per una dipendenza opzionale non disponibile mentre il nucleo funziona ancora. Decidi in anticipo quali controlli sono obbligatori.
Evita di restituire 200 con “status”:”fail” nel corpo: molti strumenti leggono solo il codice di stato. Consulta i codici di stato HTTP per il monitoraggio per sapere come viene trattato di solito ogni codice.
Esempio: PHP e Laravel
Le versioni recenti di Laravel includono una semplice route /up che conferma l’avvio dell’app, ottima come controllo di liveness. Per la readiness, aggiungi una route che controlli le dipendenze:
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;
Route::get(‘/health’, function () {
$checks = [];
try {
DB::select(‘SELECT 1’);
$checks[‘database’] = ‘ok’;
} catch (\Throwable $e) {
report($e);
$checks[‘database’] = ‘fail’;
}
try {
Cache::put(‘health-check’, ‘ok’, 10);
$checks[‘cache’] = Cache::get(‘health-check’) === ‘ok’ ? ‘ok’ : ‘fail’;
} catch (\Throwable $e) {
report($e);
$checks[‘cache’] = ‘fail’;
}
$path = storage_path();
$checks[‘disk’] = disk_free_space($path) / disk_total_space($path) > 0.10 ? ‘ok’ : ‘fail’;
$healthy = ! in_array(‘fail’, $checks, true);
return response()
->json([‘status’ => $healthy ? ‘ok’ : ‘fail’, ‘checks’ => $checks], $healthy ? 200 : 503)
->header(‘Cache-Control’, ‘no-store’);
});
Registrala fuori dai gruppi di middleware che avviano le sessioni e imposta nella configurazione un timeout di connessione breve per il database.
Esempio: Node ed Express
const withTimeout = (promise, ms) =>
Promise.race([
promise,
new Promise((_, reject) => setTimeout(() => reject(new Error(‘timeout’)), ms)),
]);
const check = (fn) => withTimeout(fn(), 2000).then(() => ‘ok’, () => ‘fail’);
app.get(‘/health’, async (req, res) => {
const [database, cache] = await Promise.all([
check(() => pool.query(‘SELECT 1’)),
check(() => redis.ping()),
]);
const checks = { database, cache };
const healthy = Object.values(checks).every((v) => v === ‘ok’);
res
.set(‘Cache-Control’, ‘no-store’)
.status(healthy ? 200 : 503)
.json({ status: healthy ? ‘ok’ : ‘fail’, checks });
});
I controlli vengono eseguiti in parallelo, ciascuno con un limite di due secondi, quindi l’endpoint risponde entro circa due secondi anche quando una dipendenza è bloccata.
Esempio: Python e Flask
import shutil
from flask import jsonify
from sqlalchemy import text
@app.get(“/health”)
def health():
checks = {}
try:
db.session.execute(text(“SELECT 1”))
checks[“database”] = “ok”
except Exception:
app.logger.exception(“health: database”)
checks[“database”] = “fail”
usage = shutil.disk_usage(“/”)
checks[“disk”] = “ok” if usage.free / usage.total > 0.10 else “fail”
healthy = all(value == “ok” for value in checks.values())
body = {“status”: “ok” if healthy else “fail”, “checks”: checks}
return jsonify(body), 200 if healthy else 503, {“Cache-Control”: “no-store”}
Aggiungi allo stesso modo un controllo su Redis, se lo usi per le sessioni o per una coda di task.
Testa il caso di errore
Un health check che non è mai fallito non è mai stato testato. In staging, ferma il database o punta l’app su un host di cache sbagliato e verifica che l’endpoint restituisca 503 entro il tuo timeout e indichi il controllo giusto. Poi verifica che la risposta non contenga testo di errore. Ci vogliono dieci minuti, e ti assicurano che l’endpoint dirà la verità quando conta.
Monitorare l’endpoint
Un endpoint che nessuno controlla dall’esterno è solo metà del lavoro. Puntaci un monitor HTTP dall’esterno della tua rete, così rileva anche i problemi di DNS, TLS, firewall e load balancer:
Monitora https://lumenanalytics.io/health con GET ogni minuto.
Aspettati lo stato 200 e verifica che il corpo contenga “status”:”ok”. Così una pagina di errore del proxy che restituisce 200 fa comunque fallire il controllo.
Invia un header user agent personalizzato, così il traffico di monitoraggio è facile da escludere da analytics e log.
Tieni d’occhio la cronologia dei tempi di risposta. Un health check che sale lentamente da 40 ms a 900 ms è un primo segnale di un database in affanno.
Indirizza l’avviso alle persone responsabili del servizio.
Il monitoraggio API di Uptimecheck supporta metodi e header personalizzati (incluse basic auth o bearer token) e valida lo stato atteso, le parole chiave e i campi JSON, quindi questa configurazione richiede un minuto. Affiancagli un normale monitor di uptime sulla homepage e sulla pagina di conversione più importante; la checklist per il monitoraggio API copre gli altri endpoint che vale la pena sorvegliare.
I worker delle code e i task pianificati spesso girano fuori dal processo web, quindi un endpoint di health non può sapere se si sono fermati. Per loro usa piuttosto gli heartbeat: il worker invia un ping a un URL ogni pochi minuti, e un ping mancante fa scattare l’avviso.
Checklist per l’endpoint di health check
Separa la liveness (processo vivo) dalla readiness (dipendenze funzionanti).
Controlla database, cache, disco e coda di cui l’app ha bisogno per servire una richiesta.
Dai a ogni controllo un timeout breve e mantieni le query banali.
Restituisci 200 quando è tutto a posto e 503 quando un controllo obbligatorio fallisce.
Restituisci solo nomi e stati dei controlli. Niente errori, versioni o hostname.
Disattiva la cache per il percorso.
Monitoralo ogni minuto dall’esterno, con la validazione della risposta.
Uptimecheck monitora endpoint di health, API, server e job pianificati in un unico account. Inizia la prova gratuita di 14 giorni, senza carta di credito.