Percio Castelo.
EN Currículo
Cloudflare Health Checks: Guia de Monitoramento e Alertas
← Voltar para blog

Cloudflare Health Checks: Guia de Monitoramento e Alertas

Atualizado em 25/09/2026 · Publicado em 24/09/2026 · 10 min · DevOps · 164 visualizações

Já perdi noites de sono porque um serviço web travou em silêncio atrás do proxy e eu só fiquei sabendo meia hora depois, quando o primeiro usuário me mandou mensagem reclamando. Esse é o perigo de colocar uma aplicação atrás da Cloudflare: para quem olha de fora, o DNS continua respondendo rápido e a CDN entrega uma tela com Error 521 (Web Server is Down) ou Error 522 (Connection Timed Out). O visitante vê o erro, mas o seu servidor não recebe nenhuma conexão nova.

Para não depender do aviso de clientes, configurei rotinas automatizadas de checagem. A Cloudflare tem uma ferramenta nativa para isso, chamada Health Checks. Neste guia, mostro como ela funciona, o que muda entre os planos e como montei uma alternativa gratuita usando Cloudflare Workers e um endpoint estático no Nginx.


Como funcionam os Health Checks da Cloudflare#

Ao contrário de rodar um script de monitoramento dentro da própria VPS ou disparar um ping de uma única máquina de testes, os Health Checks da Cloudflare rodam nos datacenters da rede global. Eles enviam requisições periódicas para o hostname ou IP da origem.

As vantagens práticas dessa abordagem:

  1. Monitoramento de origens individuais: não exige contratar o módulo de Load Balancing. Posso monitorar um único servidor dedicado ou VPS.
  2. Parâmetros granulares: defino protocolo (HTTP, HTTPS ou TCP), porta, caminho exato da URI (como /healthz), método HTTP e a lista de códigos aceitos (geralmente 200).
  3. Visibilidade de latência regional: o painel de Health Check Analytics mostra o tempo de resposta separado por continentes e regiões geográficas, facilitando identificar rotas congestionadas antes de o site cair de vez.
  4. Diagnóstico imediato do tipo de falha: quando o servidor falha, o log registra o motivo técnico exato, seja timeout, conexão recusada, erro de TLS ou código HTTP inesperado.

O banho de água fria do plano Free e o Passive Monitoring#

Quando abri o painel da Cloudflare pela primeira vez para configurar uma checagem ativa, encontrei a primeira barreira comercial:

RecursoPlano FreePlano ProBusiness / Enterprise
Standalone Health Checks0 inclusos10 inclusos50 a 1.000 inclusos
Intervalo mínimoN/A60 segundos10 a 15 segundos
Passive Origin MonitoringSimSimSim
Notificações por E-mailSimSimSim

Os Health Checks ativos e configuráveis custam a partir do plano Pro (US$ 20 a 25 mensais por domínio). No plano Free, a cota de verificações ativas avulsas é zero.

Como configurar as notificações no painel#

Para receber alertas assim que a Cloudflare detectar alteração no status da origem:

  1. No menu lateral global da conta (abaixo de Gerenciar conta), acesse Notificações (Notifications).
  2. Clique no botão Adicionar (Add).
  3. No campo Produto (Product), selecione Verificações de integridade (Health Checks).
  4. Na tabela de resultados, clique na linha Notificação de status das verificações de integridade (Health Checks status notification).
  5. Defina o nome do alerta, selecione o domínio/verificação configurada, escolha o gatilho de disparo (ex: quando ficar Não íntegro / Unhealthy) e adicione o seu e-mail ou webhook de notificação.

Criando um endpoint leve de verificação (/healthz)#

Um erro comum que vejo em monitoramento é apontar a sonda para a página inicial (/) ou para um script PHP pesado (como o index.php do WordPress). Fazer uma requisição a cada 60 segundos nessas rotas obriga o servidor a carregar extensões, abrir conexões com o banco de dados e gastar CPU à toa.

Para evitar esse desperdício, configurei uma resposta estática direta no servidor web. Veja como implementar no Nginx, no painel Plesk e no Apache:

1. No Nginx puro (Standalone)#

Adiciono o bloco no arquivo de configuração do site (dentro do bloco server):

# Health check estatico ultra-leve (sem passar por PHP ou MySQL)
location = /healthz {
    access_log off;
    default_type text/plain;
    return 200 "OK\n";
}

O ganho prático aqui é duplo:

  • access_log off;: o Nginx não grava essas requisições nos arquivos de log, evitando inflar discos com dezenas de milhares de linhas inúteis por mês.
  • return 200 "OK\n";: a resposta é entregue em menos de 1 milissegundo direto da memória, sem tocar no socket do PHP-FPM.

Depois de alterar, testo a sintaxe e recarrego:

nginx -t && systemctl reload nginx

2. No painel Plesk Obsidian (Nginx + Apache)#

Na maioria dos servidores gerenciados com Plesk, a pilha padrão utiliza o Nginx na frente como proxy reverso acelerando o Apache por trás. Para que o health check nem chegue a tocar no Apache ou no PHP, configuro a rota direto no Nginx pelo painel:

  1. No painel do Plesk, acesso o domínio e clico em Configurações do Apache e Nginx (Apache & NGINX Settings).
  2. Rolo até o final da página e localizo o campo Diretivas adicionais do NGINX (Additional NGINX directives).
  3. Adiciono a diretiva de captura estática:
location = /healthz {
    access_log off;
    default_type text/plain;
    return 200 "OK\n";
}
  1. Clico em Aplicar ou OK. O Nginx do Plesk assume a resposta imediatamente na borda da VPS.

Se você estiver em uma hospedagem compartilhada no Plesk sem acesso root para editar diretivas do Nginx, basta criar um arquivo físico chamado healthz na raiz da pasta httpdocs/ contendo a palavra OK e permissão de leitura (chmod 644).

3. No Apache puro ou cPanel (.htaccess)#

Se o servidor roda Apache puro sem proxy reverso na frente, você pode configurar a rota pelo .htaccess ou pelo arquivo de VirtualHost:

Opção A: Retorno direto via .htaccess (sem arquivo físico)

Usando o módulo mod_rewrite e ErrorDocument para responder 200 imediatamente:

# Resposta direta 200 sem passar por index.php ou CMS
RewriteEngine On
RewriteRule ^healthz$ - [R=200,L]
ErrorDocument 200 "OK"

Opção B: Arquivo estático dedicado no DocumentRoot

Crio um arquivo físico simples na raiz pública (public_html/healthz ou /var/www/html/healthz):

echo "OK" > /var/www/html/healthz

E no arquivo .htaccess, adiciono as regras para impedir que o WordPress ou outro CMS capture a URL:

# Evita que o roteador do WordPress capture o health check
<Files "healthz">
    ForceType text/plain
    Header set Cache-Control "no-cache, no-store, must-revalidate"
</Files>

RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

A alternativa gratuita: Worker com Cron Trigger a cada 60s#

Se você está no plano Free e quer a certeza de uma checagem ativa a cada 60 segundos (sem depender de tráfego de visitantes reais), a saída mais limpa e robusta que montei em meu laboratório é criar um Cloudflare Worker com agendamento por Cron Trigger.

O Worker roda na borda global da própria Cloudflare, faz uma requisição HTTP rápida para o /healthz e, em vez de enviar alertas repetidos a cada minuto ou estourar cotas, gerencia o estado da aplicação usando o Cloudflare KV.

1. Pré-requisitos#

  • Conta Cloudflare (plano Free) no painel dash.cloudflare.com.
  • Node.js instalado (node --version).
  • CLI oficial do Wrangler instalada (npm install -g wrangler ou via npx wrangler) e autenticada (wrangler login).
  • Seu servidor web com a rota estática /healthz respondendo HTTP 200 (seja direto no IP público ou atrás de um Cloudflare Tunnel).

2. Criando o projeto#

No terminal, inicio um novo projeto de Worker:

wrangler init healthz-monitor
cd healthz-monitor

O Wrangler gera automaticamente a estrutura com wrangler.toml, a pasta src/index.js e o .gitignore.

3. Configurando o agendamento no wrangler.toml#

Edito o wrangler.toml para definir o nome, compatibilidade, observabilidade e o gatilho de execução:

name = "healthz-monitor"
main = "src/index.js"
compatibility_date = "2026-09-25"

[observability]
enabled = true

[triggers]
crons = ["* * * * *"]   # Executa a cada 60 segundos

4. O código inteligente com gestão de estado (src/index.js)#

Aqui está o código completo que implementei. Ele resolve três problemas críticos de uma só vez:

  1. Evita spam de notificações: Só envia mensagem quando há transição de estado (ok -> down e down -> ok), em vez de mandar um alerta por minuto enquanto o servidor estiver fora do ar.
  2. Respeita a cota do Cloudflare KV: O plano Free do KV limita a 1.000 gravações (writes) por dia. Se eu gravasse a cada 60 segundos, seriam 1.440 gravações diárias, estourando o plano gratuito. Gravando apenas quando o status muda, o consumo diário de escrita fica próximo de zero.
  3. Blindagem e Telemetria Read-Only (Padrão SRE/SecOps): O handler HTTP fetch é estritamente somente-leitura. Ele expõe a telemetria do status atual via JSON para monitoramento externo com cabeçalhos de cache, mas impede que qualquer chamada pública externa provoque efeitos colaterais (como forçar sondagens fora do cron ou disparar mensagens nos canais de alerta).

Você pode inspecionar o código completo abaixo ou acessar a versão mantida diretamente no GitHub Gist:

healthz-monitor — Cloudflare Worker: Healthz Monitor com Gestão Inteligente de Estado
GistVer código
/**
 * Cloudflare Worker: Healthz Monitor com Gestão Inteligente de Estado
 */

const HEALTHZ_URL = 'https://perciocastelo.com.br/healthz';
// const HEALTHZ_URL = 'http://127.0.0.1:8080/healthz'; // Exemplo se estiver atrás de Cloudflare Tunnel

export default {
  // Handler agendado: disparado automaticamente pelo cron a cada 60s
  async scheduled(event, env, ctx) {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 5000); // 5s de timeout limite

    let ok = false;
    let statusCode = 0;
    try {
      const res = await fetch(HEALTHZ_URL, {
        signal: controller.signal,
        headers: {
          'User-Agent': 'Cloudflare-Worker-Healthz-Monitor/1.0 (+https://perciocastelo.com.br)'
        }
      });
      statusCode = res.status;
      ok = res.status === 200;
    } catch (err) {
      ok = false;
      statusCode = err.name === 'AbortError' ? 408 : 503;
    } finally {
      clearTimeout(timeout);
    }

    if (!env.HEALTH_STATE) {
      console.warn('Namespace KV HEALTH_STATE não configurado no wrangler.toml.');
      return;
    }

    // Só grava no KV quando o estado MUDA (ok <-> down)
    // Isso mantém as operações de escrita bem abaixo do teto de 1.000 writes/dia do KV Free
    const prev = await env.HEALTH_STATE.get('status');
    const cur = ok ? 'ok' : 'down';

    if (prev !== cur) {
      await env.HEALTH_STATE.put('status', cur);
      await env.HEALTH_STATE.put('last_change', new Date().toISOString());
      await env.HEALTH_STATE.put('last_status_code', String(statusCode));

      // Alerta exclusivamente na transição de estado para evitar spam no celular
      if (!ok && prev !== 'down') {
        const msg = `🚨 [ALERTA DE QUEDA] A origem ${HEALTHZ_URL} está inacessível! HTTP Status: ${statusCode}`;
        ctx.waitUntil(sendAlert(env, msg));
      } else if (ok && prev === 'down') {
        const msg = `✅ [NORMALIZADO] A origem ${HEALTHZ_URL} foi restabelecida com sucesso (HTTP 200).`;
        ctx.waitUntil(sendAlert(env, msg));
      }
    }
  },

  // Handler HTTP: estritamente somente-leitura (Read-Only)
  // Padrão SRE/SecOps: Nenhuma requisição pública externa pode provocar efeitos colaterais
  // (disparar sondas fora do cron ou enviar alertas para Discord/Telegram).
  async fetch(request, env) {
    let currentStatus = 'unknown';
    let lastChange = null;
    let lastStatusCode = null;

    if (env.HEALTH_STATE) {
      currentStatus = (await env.HEALTH_STATE.get('status')) || 'unknown';
      lastChange = await env.HEALTH_STATE.get('last_change');
      lastStatusCode = await env.HEALTH_STATE.get('last_status_code');
    }

    return new Response(
      JSON.stringify(
        {
          monitor: 'healthz-monitor',
          target: HEALTHZ_URL,
          current_status: currentStatus,
          last_status_code: lastStatusCode,
          last_change: lastChange,
          timestamp: new Date().toISOString()
        },
        null,
        2
      ),
      {
        headers: {
          'content-type': 'application/json; charset=utf-8',
          'cache-control': 'public, max-age=15, stale-while-revalidate=45',
          'x-content-type-options': 'nosniff'
        }
      }
    );
  }
};

async function sendAlert(env, message) {
  const discordWebhook = env.DISCORD_WEBHOOK;
  const telegramToken = env.TELEGRAM_BOT_TOKEN;
  const telegramChatId = env.TELEGRAM_CHAT_ID;

  const tasks = [];

  // Notificacao via Discord Webhook
  if (discordWebhook) {
    tasks.push(
      fetch(discordWebhook, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ content: message })
      }).catch(err => console.error('Erro ao despachar webhook para o Discord:', err))
    );
  }

  // Notificacao via Telegram Bot API
  if (telegramToken && telegramChatId) {
    tasks.push(
      fetch(`https://api.telegram.org/bot${telegramToken}/sendMessage`, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          chat_id: telegramChatId,
          text: message
        })
      }).catch(err => console.error('Erro ao despachar mensagem para o Telegram:', err))
    );
  }

  if (tasks.length > 0) {
    await Promise.allSettled(tasks);
  }
}

5. Criando o namespace KV e as variáveis de ambiente#

Crio o namespace KV na Cloudflare para guardar o estado da sonda:

npx wrangler kv namespace create HEALTH_STATE

O comando retornará um identificador único (id), visível também na listagem de Namespaces KV no painel:

Namespace KV HEALTH_STATE criado no painel da Cloudflare

Adiciono esse bloco no final do wrangler.toml:

[[kv_namespaces]]
binding = "HEALTH_STATE"
id = "SEU_ID_GERADO_AQUI"

Para não expor webhooks e tokens no repositório de código, cadastro os segredos usando o comando wrangler secret put. O Wrangler espera o nome da variável como argumento e solicita o valor de forma segura:

# Para notificações via Discord:
echo "https://discord.com/api/webhooks/SEU_ID/SEU_TOKEN" | npx wrangler secret put DISCORD_WEBHOOK

# Ou para notificações via Telegram:
npx wrangler secret put TELEGRAM_BOT_TOKEN
npx wrangler secret put TELEGRAM_CHAT_ID

No painel da Cloudflare, na aba Configurações do Worker, confirmo a associação do namespace KV e a persistência dos logs de observabilidade:

Associação do Namespace de KV HEALTH_STATE e Observabilidade de logs

6. Executando e testando localmente#

Para testar o Worker no computador antes do deploy:

npx wrangler dev

Como o ambiente local do Wrangler não dispara o cron de forma autônoma, testo a execução de duas maneiras práticas:

  1. Simulação do cron via terminal:
   curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*"
  1. Consulta do status via navegador:

Abro http://localhost:8787/ para verificar a resposta JSON com o estado da sonda persistido no KV.

No primeiro ciclo em que a sonda detecta uma transição ou na validação inicial, o webhook entrega a notificação formatada diretamente no canal do Discord:

Notificação de teste recebida no canal do Discord via Webhook

7. Publicando na borda da Cloudflare#

Com tudo validado, faço o deploy global:

npx wrangler deploy

Pronto. A partir desse momento, a Cloudflare dispara o script a cada 60 segundos de forma 100% autônoma na edge global. Você pode verificar o status em tempo real acessando a URL pública do seu worker (ex: https://healthz-monitor.seu-subdominio.workers.dev).

No painel de Workers e Pages, o healthz-monitor aparece listado e operando:

Worker healthz-monitor ativo na lista de Workers da Cloudflare

Ao abrir a visão geral do Worker, a interface detalha a topologia completa da sonda na borda: a rota pública de status, a observabilidade de logs ativa, o gatilho agendado a cada 60 segundos e a associação ao namespace KV HEALTH_STATE:

Visão geral da topologia e triggers do healthz-monitor no painel da Cloudflare


Cota do Plano Free vs Consumo Real (2026)#

Os números oficiais da Cloudflare confirmam por que essa solução é viável a custo zero:

RecursoCota Plano FreeMeu Consumo (1 checagem/min)Status
Requisições por dia100.000 / dia~1.440 / dia (~43.200 / mês)✅ ~1,4% da cota
Cron Triggers por conta5 cron triggers1 utilizado✅ Folgado
CPU Time por request10 ms (ou 50 ms)< 1 ms (apenas um fetch leve)✅ Quase imperceptível
KV Writes por dia1.000 / dia~0 a 20 (apenas transições)✅ Muito abaixo do teto
Subrequests por request50 / request1 a 2 subrequests✅ Folgado

Passo a passo no plano Pro (Interface Nativa)#

Para quem tem conta Pro ou Business e quer usar a interface oficial:

  1. No painel do domínio, acesse Traffic e clique em Health Checks.
  2. Clique no botão Create Health Check.
  3. Preencha os parâmetros:
  • Name: Origin-Healthz
  • Path: /healthz
  • Protocol: HTTPS
  • Port: 443
  • Expected response codes: 200
  • Interval: 60 segundos
  • Retries: 2 (evita alarmes falsos por oscilações rápidas)
  • Timeout: 5 segundos
  1. Salve e avance para a etapa de notificações para cadastrar os e-mails ou webhooks de alerta.

Conclusão#

Você não precisa gastar 20 dólares mensais só para saber se o seu servidor web caiu. O monitoramento passivo nativo da Cloudflare cobre o tráfego real sem custo, e uma sonda simples via Worker com Cron Trigger resolve as checagens ativas por minuto com alerta direto no celular.

Este artigo foi útil?

Deixe uma reação rápida para apoiar o conteúdo:

164 leituras
Creative Commons Atribuição-NãoComercial

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.

0 comentários