---
title: "Alertas de expiração de certificado SSL: ninguém mais te avisa por e-mail"
description: "A Let's Encrypt parou de avisar por e-mail e os certificados duram 200 dias. Um script openssl testado, os limiares certos e alertas que chegam até você."
date: 2026-09-18
author: Nooc
authorAvatarLink: /images/avatars/nooc.webp
authorLink: https://nooc.me
tags:
  - expiração de certificado SSL
  - monitoramento TLS
  - certbot
  - alertas de servidor fora do ar
  - alertas por ligação
---

# Alertas de expiração de certificado SSL: ninguém mais te avisa por e-mail

Duas coisas mudaram por baixo da infraestrutura de certificados de todo mundo, e a maioria dos times não se ajustou a nenhuma delas.

**Primeiro: a rede de segurança foi retirada.** A Let's Encrypt encerrou seu serviço de aviso de expiração em **4 de junho de 2025** — aqueles e-mails que salvaram discretamente milhares de sites quando a automação quebrava. O raciocínio fazia sentido (a maior parte dos assinantes já renova automaticamente, guardar milhões de endereços de e-mail é um passivo de privacidade e o serviço custava "dezenas de milhares de dólares por ano"), e o anúncio termina mandando você procurar monitoramento de terceiros ([Let's Encrypt](https://letsencrypt.org/2025/06/26/expiration-notification-service-has-ended)). Muita gente leu isso, concordou e nunca fez a segunda metade.

**Segundo: a margem de erro desabou.** Desde **15 de março de 2026**, um certificado TLS público pode valer no máximo 200 dias, caindo para 100 dias em 15 de março de 2027 e para 47 dias em 15 de março de 2029, conforme a [cédula SC-081v3 do CA/Browser Forum](https://cabforum.org/2025/04/11/ballot-sc081v3-introduce-schedule-of-reducing-validity-and-data-reuse-periods/). A Let's Encrypt está indo mais rápido que o teto: certificados de 6 dias ficaram disponíveis para todos em 15 de janeiro de 2026, e o plano leva o perfil padrão a 64 dias em fevereiro de 2027 e a 45 dias em fevereiro de 2028 ([Let's Encrypt](https://letsencrypt.org/2025/12/02/from-90-to-45)).

As duas mudanças empurram na mesma direção. A renovação acontece com mais frequência, então tem mais chances de quebrar — e, quando quebra, ninguém te manda e-mail. Este guia é a verificação que fecha essa lacuna: um script de shell testado, limiares que fazem sentido para certificados de vida curta e um jeito de fazer o último caso tocar o seu telefone com o [Echobell](https://apps.apple.com/app/apple-store/id6743597198?pt=128151925&ct=blog-ssl-certificate-expiry-alerts-pt&mt=8).

## Quão ruim é de fato uma queda por certificado?

**Ruim o bastante para mais de um terço das organizações ter passado por isso no último ano.** No 2026 Global Certificate Management Outlook da DigiCert — uma pesquisa da Propeller Insights com 1.001 tomadores de decisão de TI e cibersegurança nos EUA, Reino Unido e Austrália, realizada em maio de 2026 — **mais de um terço** das organizações relatou uma indisponibilidade causada por certificado expirado no último ano. Quase **três quartos** acumularam ao menos cinco horas de indisponibilidade ligada a certificados, **uma em cada cinco** chegou a 25 horas ou mais, e quase **uma em cada quatro** disse que seu incidente mais grave custou mais de **US$ 250.000** ([DigiCert](https://www.globenewswire.com/news-release/2026/09/09/3358724/0/en/digicert-research-finds-certificate-failures-are-a-six-figure-infrastructure-risk.html)).

O mais interessante nesses números é a duração. Cinco horas não é o tempo de renovar um certificado — renovar leva segundos. Cinco horas é o tempo que alguém levou para *descobrir*.

## Por que a renovação automática não basta?

**Porque a automação de renovação falha em silêncio, e um cron que parou de rodar não produz saída nenhuma.** Cada um destes casos é real e comum, e nenhum faz barulho:

- **O timer não roda mais.** Uma atualização de distribuição, um contêiner reconstruído ou um `systemctl disable` de três meses atrás do qual ninguém lembra. Um `certbot.timer` que não dispara parece exatamente igual a um que dispara certinho.
- **A renovação deu certo, mas o serviço nunca recarregou.** O certificado novo está no disco; o nginx, o HAProxy ou o Postfix continua com o antigo na memória. É a forma mais comum de uma configuração "totalmente automatizada" expirar mesmo assim.
- **O caminho do desafio quebrou.** Alguém colocou um redirecionamento, uma regra de WAF ou um `Deny` na frente de `/.well-known/acme-challenge/`, e o HTTP-01 falha. Ou expirou o token de API do provedor de DNS usado no DNS-01.
- **A renovação aconteceu em apenas um nó.** Dois balanceadores, um cron. O segundo segue servindo o certificado antigo até não conseguir mais.
- **O certificado nem está em um servidor web.** Clientes mTLS internos, um broker Kafka, um servidor LDAP, um concentrador VPN, um certificado de push do gerenciamento de dispositivos. Nada na internet pública o enxerga e nenhum cliente ACME o gerencia.
- **A renovação está fixada no intervalo errado.** A Let's Encrypt é explícita: "renovar em um intervalo fixo de 60 dias não será mais suficiente" quando o perfil padrão cair para 64 e depois 45 dias ([Let's Encrypt](https://letsencrypt.org/2025/12/02/from-90-to-45)).

O último merece ênfase, porque é a falha para a qual o calendário está empurrando todo mundo. Se a sua cadência de renovação é um número que alguém digitou em 2022, a validade dos certificados agora está vindo ao encontro dele.

## Como verifico a expiração de um certificado pela linha de comando?

**Um único pipeline de `openssl`, sem dependências.** Para um host no ar:

```bash
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -startdate -enddate
```

Para um arquivo em disco:

```bash
openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -enddate
```

A flag `-servername` não é opcional em um IP compartilhado: sem SNI você recebe o certificado que o servidor considera padrão, que pode não ser aquele com que você se preocupa.

Para uma resposta sim/não, pule totalmente o parsing de datas. `openssl x509 -checkend <segundos>` sai com **0** se o certificado sobrevive àquela janela e com **1** se expira dentro dela (inclusive se já expirou):

```bash
openssl x509 -in fullchain.pem -noout -checkend $((14 * 86400)) \
  || echo "expira em menos de 14 dias"
```

Esse contrato de código de saída é toda a primitiva de monitoramento. Tudo abaixo é só encanamento em volta dela.

### Pegar o caso "renovado, mas não recarregado"

**Compare o que está no disco com o que está realmente sendo servido.** É a verificação que quase ninguém roda, e ela pega justamente a falha que a automação de renovação não consegue enxergar:

```bash
served=$(openssl s_client -connect 127.0.0.1:443 -servername example.com </dev/null 2>/dev/null \
         | openssl x509 -noout -fingerprint -sha256)
ondisk=$(openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -fingerprint -sha256)

[ "$served" = "$ondisk" ] || echo "o serviço está servindo um certificado antigo — precisa recarregar"
```

Os dois comandos imprimem o mesmo formato `sha256 Fingerprint=AB:CD:...`, então uma comparação simples de strings basta. Rode alguns minutos depois da janela do seu timer de renovação.

## Quais limiares um alerta de certificado deve usar?

**Frações do tempo de vida do certificado, não contagens fixas de dias.** Uma regra de "avisar com 30 dias" fazia sentido para certificados de 90 dias. Aplicada a um de 47 dias, dispara em um certificado perfeitamente saudável; aplicada a um de 6 dias, dispara o tempo todo.

Ancore a escada no ponto de renovação. A Let's Encrypt recomenda renovar "aproximadamente a dois terços do tempo de vida do certificado atual" — ou seja, **um terço de vida restante** é quando a renovação *já deveria ter acontecido*. Tudo depois disso é evidência de que não aconteceu:

| Vida restante | O que significa | Tipo de notificação |
| --- | --- | --- |
| 1/3 | A janela de renovação abriu | Nada — isso é normal |
| 1/6 | A janela foi perdida uma vez | Push normal |
| 1/12 | A renovação está falhando, não atrasada | Urgente |
| < 1/24, expirado ou inacessível | Você está a horas de uma queda | Ligação |

Em números concretos, para um certificado de 47 dias fica mais ou menos assim: silêncio até 7,8 dias, push aos 3,9 dias, urgente aos 2 dias, ligação abaixo de 1 dia. Para um de 90 dias: 15 dias, 7,5 dias, 3,75 dias. Para um de 6 dias são horas, e aí uma escada pensada para humanos deixa de ser a ferramenta certa — apoie-se no [ACME Renewal Information](https://letsencrypt.org/2025/09/16/ari-rfc) (ARI, publicado como RFC 9773), que deixa a AC dizer ao seu cliente quando renovar, e alerte apenas em falhas repetidas do cliente.

Calcular a fração são quatro linhas, e isso deixa o mesmo script correto para todos os seus certificados, seja qual for o emissor:

```bash
to_epoch() {
  date -u -d "$1" +%s 2>/dev/null || date -u -j -f '%b %d %T %Y %Z' "$1" +%s 2>/dev/null
}

pct_left() {  # lê um PEM na entrada padrão e imprime o percentual de vida restante
  local pem nb na
  pem=$(cat)
  nb=$(to_epoch "$(printf '%s' "$pem" | openssl x509 -noout -startdate | cut -d= -f2)")
  na=$(to_epoch "$(printf '%s' "$pem" | openssl x509 -noout -enddate | cut -d= -f2)")
  echo $(( (na - $(date -u +%s)) * 100 / (na - nb) ))
}
```

A primeira forma de `date` é GNU, a segunda é BSD/macOS; o `||` escolhe a que você tiver.

## Como transformo isso em um alerta que realmente me alcança?

O Echobell transforma um webhook ou um e-mail em push normal, alerta urgente ou uma ligação de verdade, que atravessa o modo Foco e o Não perturbe (veja [como furar o modo Foco do iOS](/blog/how-to-bypass-ios-focus-mode-for-critical-alerts)). Para certificados isso importa porque o último degrau da tabela acima é justamente o que você vai pegar às 3h de um domingo.

### Passo 1 — Crie dois canais, não um

Crie um canal no app, defina o tipo de notificação como **Urgente** e chame-o de "Certificados expirando". Crie um segundo com o tipo **Ligação** e chame-o de "Certificado prestes a expirar". Copie a URL do webhook nos detalhes de cada canal; ela tem a forma `https://hook.echobell.one/t/<channel-token>`. Trate-as como segredos: quem tiver a da Ligação pode fazer seu telefone tocar ([guia de webhooks](/docs/webhook)).

Escreva os templates para que a notificação permita agir direto da tela bloqueada, sem desbloquear nada:

```
Título: TLS {{state}}: {{host}}
Corpo: faltam {{daysLeft}} dias, expira em {{notAfter}} — emitido por {{issuer}}
```

Qualquer chave JSON que você enviar vira variável ([templates](/docs/template)).

### Passo 2 — Rode a verificação

Este é o script das seções anteriores, montado e testado de ponta a ponta. Salve como `/usr/local/bin/cert-watch`:

```bash
#!/usr/bin/env bash
# cert-watch — envia um POST ao Echobell quando um certificado TLS está perto de expirar.
set -uo pipefail

HOOK="${ECHOBELL_CERT_HOOK:?defina ECHOBELL_CERT_HOOK com a URL de webhook do seu canal}"
WARN_DAYS="${WARN_DAYS:-14}"

post() {
  curl -sS -m 10 -X POST "$HOOK" \
    -H 'content-type: application/json' \
    -d "{\"host\":\"$1\",\"daysLeft\":$2,\"notAfter\":\"$3\",\"state\":\"$4\"}" \
    >/dev/null
}

days_left() {
  local end
  end=$(date -u -d "$1" +%s 2>/dev/null) ||
    end=$(date -u -j -f '%b %d %T %Y %Z' "$1" +%s 2>/dev/null) || return 1
  echo $(( (end - $(date -u +%s)) / 86400 ))
}

check() {
  local host="$1" port="$2" pem state not_after days

  pem=$(openssl s_client -connect "$host:$port" -servername "$host" \
        </dev/null 2>/dev/null | openssl x509 2>/dev/null)

  if [ -z "$pem" ]; then
    post "$host" 0 "" "unreachable"
    return
  fi

  if printf '%s' "$pem" | openssl x509 -noout -checkend 0 >/dev/null 2>&1; then
    printf '%s' "$pem" | openssl x509 -noout -checkend $((WARN_DAYS * 86400)) >/dev/null 2>&1 && return 0
    state="expiring"
  else
    state="expired"
  fi

  not_after=$(printf '%s' "$pem" | openssl x509 -noout -enddate | cut -d= -f2)
  days=$(days_left "$not_after") || days=-999
  post "$host" "$days" "$not_after" "$state"
}

for target in "$@"; do
  case "$target" in
    *:*) check "${target%:*}" "${target##*:}" ;;
    *) check "$target" 443 ;;
  esac
done
```

Ele reporta três estados — `expiring`, `expired` e `unreachable` — e fica calado quando está tudo certo. Os alvos são `host` ou `host:port`, então certificados fora da web também ficam cobertos:

```bash
ECHOBELL_CERT_HOOK="https://hook.echobell.one/t/<token>" \
  cert-watch example.com api.example.com mail.example.com:993 ldap.internal:636
```

`unreachable` é deliberadamente um alerta, e não um pulo silencioso. Uma verificação que trata "não consegui olhar" como "está tudo bem" é justamente o motivo de certificados expirarem.

### Passo 3 — Agende e alerte quando o próprio agendamento falhar

Uma vez por dia basta para certificados de 45 dias ou mais; duas vezes por dia se você usa certificados de 6 dias. Um timer do systemd:

```ini
# /etc/systemd/system/cert-watch.service
[Unit]
Description=TLS certificate expiry check

[Service]
Type=oneshot
EnvironmentFile=/etc/echobell.env
ExecStart=/usr/local/bin/cert-watch example.com api.example.com mail.example.com:993
```

```ini
# /etc/systemd/system/cert-watch.timer
[Unit]
Description=Daily TLS certificate expiry check

[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true

[Install]
WantedBy=timers.target
```

`Persistent=true` importa: sem ele, uma máquina desligada no horário previsto simplesmente pula aquela execução.

Depois feche o ciclo sobre a própria verificação. Uma unidade `Type=oneshot` que sai com código diferente de zero aciona `OnFailure=`, então um único drop-in faz uma renovação quebrada se anunciar sozinha:

```ini
# /etc/systemd/system/certbot.service.d/echobell.conf
[Unit]
OnFailure=echobell-alert@%n.service
```

```ini
# /etc/systemd/system/echobell-alert@.service
[Unit]
Description=Echobell alert for %i

[Service]
Type=oneshot
EnvironmentFile=/etc/echobell.env
ExecStart=/usr/local/bin/echobell-notify %i
```

Onde `/usr/local/bin/echobell-notify` tem três linhas:

```bash
#!/usr/bin/env bash
curl -sS -m 10 -X POST "$ECHOBELL_CERT_HOOK" \
  -H 'content-type: application/json' \
  -d "{\"host\":\"$(hostname -f)\",\"state\":\"renewal-failed\",\"unit\":\"$1\",\"daysLeft\":-1}"
```

O `certbot renew` sai com código diferente de zero quando qualquer renovação falha — exatamente o sinal que você quer e exatamente o sinal que hoje não chega a lugar nenhum. Atenção: o `--deploy-hook` do certbot só roda em caso de *sucesso*, então não serve para isso; o caminho de falha precisa vir da unidade.

### Passo 4 — Divida a escada com condições

Os dois canais recebem o mesmo payload; as [condições](/docs/conditions) decidem qual realmente dispara. No canal Urgente:

```
state == "expiring" && daysLeft > 3
```

No canal de Ligação:

```
state == "expired" || state == "unreachable" || state == "renewal-failed" || daysLeft <= 3
```

Repare que `<=` converte os dois lados com `Number()`, então um `daysLeft` enviado como string ainda é comparado numericamente. As condições não têm operador "contém", e é por isso que o script envia um campo `state` explícito em vez de um texto livre que você teria que reconhecer por padrão.

### Passo 5 — Teste antes de confiar

Aponte o script para um host com certificado sabidamente ruim e veja o alerta chegar:

```bash
ECHOBELL_CERT_HOOK="https://hook.echobell.one/t/<token>" cert-watch expired.badssl.com
```

Faça isso com o Não perturbe ativado no telefone que realmente vai receber e ligue **Repetir ligação com falha** no app, para que uma chamada suprimida pelo Foco seja tentada de novo. Um caminho de escalonamento que você nunca disparou é um palpite.

## E se meu monitoramento já verifica certificados?

**Então ligue o webhook que ele já tem a um canal e pule o script.** A maioria das ferramentas de monitoramento já conhece a data de expiração; o que costuma faltar é um caminho que sobreviva a um humano dormindo.

- O **Uptime Kuma** tem notificação de expiração de certificado embutida — aponte-a para um canal ([guia do Uptime Kuma](/docs/developer/uptime-kuma), [configuração de ligações](/blog/uptime-kuma-phone-call-alerts)).
- **Prometheus + Alertmanager** com o blackbox exporter dão `probe_ssl_earliest_cert_expiry`; crie um alerta nessa métrica e roteie para um canal ([guia do Prometheus](/docs/developer/prometheus), [ligações com o Alertmanager](/blog/alertmanager-phone-call-alerts)).
- As regras de alerta do **Grafana** fazem POST direto para um canal ([guia do Grafana](/docs/developer/grafana)).
- **Upptime** e **UptimeRobot** cobrem os endpoints públicos ([Upptime](/docs/developer/upptime), [UptimeRobot](/docs/developer/uptimerobot)).
- **Qualquer coisa que só mande e-mail** — o portal da própria AC, os avisos de ACM de um provedor de nuvem, uma PKI interna — resolve-se com uma regra de encaminhamento. Cada canal tem seu próprio endereço, e `from`, `to`, `subject`, `text` e `html` ficam disponíveis como variáveis ([gatilhos por e-mail](/docs/email-trigger)).

A única coisa que nenhuma dessas opções substitui é a verificação rodando *de fora* da máquina que serve o certificado. Se o seu monitoramento mora no mesmo host, uma falha que derruba o host leva o alerta junto.

## O que o Echobell não faz

Ser preciso aqui importa, porque gestão de certificados é uma categoria cheia de produtos que fazem muito mais do que isso.

**O Echobell faz:** transforma um webhook ou e-mail em push normal, alerta urgente ou ligação que toca; filtra com condições; formata com templates; entrega um disparo a todos os inscritos de um canal compartilhado, cada um escolhendo sua urgência.

**O Echobell não faz:**

- **Descobrir ou inventariar seus certificados.** Ele não escaneia sua rede, não varre logs de certificate transparency e não vai te contar do certificado que ninguém lembra de ter emitido. O script acima só verifica os hosts que você listar. A proliferação de certificados é um problema real, e isto não é a solução para ela.
- **Renovar nada.** Não tem cliente ACME nem acesso às suas chaves. Ele avisa que a renovação quebrou; consertar continua sendo com você.
- **Verificar certificados por conta própria.** Não há sonda hospedada. Algo que você executa — um timer, um job de CI, o monitoramento que já existe — precisa ir olhar.
- **Fornecer escalas de plantão, políticas de escalonamento ou confirmação.** Não existe "se ninguém atender em cinco minutos, ligue para o próximo". Se você precisa disso, precisa de uma plataforma de incidentes — veja a [comparação de alternativas ao Opsgenie](/blog/opsgenie-end-of-life-alternatives).
- **Garantir a entrega.** Uma ligação depende da infraestrutura de push, da rede e de um telefone carregado. Ela encurta a distância entre a quebra e a percepção; não é um controle no qual se apoiar de forma absoluta.

## Perguntas frequentes

### A Let's Encrypt realmente parou de enviar e-mails de expiração?

Sim. O serviço de avisos terminou em 4 de junho de 2025, e a Let's Encrypt apagou os endereços de e-mail guardados junto aos registros de emissão. O anúncio recomenda monitoramento de terceiros e cita o Red Sift Certificates Lite, gratuito para até 250 certificados, como uma opção. Se você não recebe um aviso de expiração da Let's Encrypt há mais de um ano, é por isso — não porque nada esteve perto de expirar.

### Quanto tempo valem os certificados TLS em 2026?

No máximo 200 dias para certificados TLS de confiança pública, desde 15 de março de 2026. O teto cai para 100 dias em 15 de março de 2027 e para 47 dias em 15 de março de 2029, conforme a cédula SC-081v3. Cada AC emite abaixo do teto por segurança — a DigiCert, por exemplo, emite certificados de 199 dias justamente "para evitar exceder a validade máxima permitida". A Let's Encrypt vai além e mais rápido no próprio cronograma, com certificados de 6 dias disponíveis desde janeiro de 2026.

### Devo alertar sobre expiração mesmo usando ARI?

Sim, mas em eventos diferentes. O ARI (RFC 9773) diz ao seu cliente ACME *quando* renovar, o que elimina por completo a falha do intervalo fixo — mas não garante que a renovação tenha sucesso, que o serviço recarregue nem que o cliente ainda esteja rodando. Alerte sobre falhas repetidas de renovação e sobre o certificado servido divergir do que está no disco, não sobre uma contagem regressiva que você já não precisa administrar.

### E os certificados que não estão em um servidor web?

Esses são justamente os que mais expiram, porque nenhum cliente ACME os observa e nenhum navegador reclama até algo quebrar. O script aceita `host:port`, então IMAP na 993, LDAPS na 636, um broker Kafka na 9093 ou uma API interna na 8443 funcionam do mesmo jeito. Certificados que nunca tocam um socket — assinatura de código, certificados de notificação push, certificados de cliente em uma frota de dispositivos — precisam que você extraia a data de onde eles vivem e a envie para o mesmo canal.

### Uma verificação diária não vira ruído?

Não se ela ficar calada quando não há nada — que é exatamente por isso que o script não publica nada acima do limiar. O desenho barulhento é o que informa "certificado OK" todos os dias: em duas semanas ninguém lê, e no dia em que parar de chegar ninguém percebe. Se quiser um heartbeat, coloque-o em um canal Normal separado e nunca no que toca. Veja [como resolver a fadiga de alertas](/blog/fix-alert-fatigue-developer-guide) para a versão geral deste argumento.

### O time inteiro pode receber o alerta de certificado?

Pode. Compartilhe o canal e cada inscrito recebe o disparo, escolhendo o próprio tipo de notificação. Uma divisão que funciona: quem está de plantão assina o canal de Ligação e os demais o Urgente, assim uma expiração às 3h acorda uma pessoa em vez de seis.

### Funciona no macOS?

Funciona, com uma ressalva: o `date` do BSD não aceita `-d`, e é por isso que `days_left` e `to_epoch` tentam primeiro a forma GNU e recorrem a `date -u -j -f`. O `openssl` do macOS é LibreSSL por padrão e suporta `-checkend` e `-fingerprint` de forma idêntica. Se você instalar o OpenSSL pelo Homebrew, nada muda.

### É seguro mandar detalhes do certificado numa notificação?

Os campos usados aqui — nome do host, data de expiração, emissor — são públicos; qualquer um lê isso do seu servidor com o mesmo comando `openssl`. Não amplie o payload com chaves privadas, caminhos internos ou qualquer coisa de um certificado em host não público além do nome. O Echobell guarda o conteúdo e o histórico das notificações apenas no seu dispositivo, mantendo no servidor só contas, canais e inscrições ([modelo de privacidade](/docs/features)) — um bom padrão, mas não um motivo para enviar mais do que o necessário.

---

## Relacionados

- [Alertas por ligação para quedas de API](/blog/phone-call-alerts-api-downtime)
- [Alertas por ligação com o Uptime Kuma](/blog/uptime-kuma-phone-call-alerts)
- [Alertas de falha em cron jobs](/blog/cron-job-failure-alerts)
- [Como resolver a fadiga de alertas: guia para desenvolvedores](/blog/fix-alert-fatigue-developer-guide)
- [Como furar o modo Foco do iOS para alertas críticos](/blog/how-to-bypass-ios-focus-mode-for-critical-alerts)
- [Guia de integração de webhooks](/docs/webhook)
- [Guia de condições](/docs/conditions)
