Domínios e certificados (gatekeeper)

Toda rota que o runner cria com domínio é HTTPS. O que pode esperar é o certificado: pedir um certificado ao Let's Encrypt para um domínio que ainda não aponta para o servidor só gasta a cota de tentativas e falha. Até lá a rota usa o certificado padrão do Traefik, que é autoassinado (o navegador avisa).

A partir da v2.172.0, a decisão é do runner, e não de um nome fixo no Traefik:

  • o resolver é escolhido pelo tipo de desafio e provider que o Traefik em execução oferece (http-01, tls-alpn-01, dns-01/cloudflare…), nunca pelo nome (myresolver num servidor pode ser letsencrypt em outro);
  • um gatekeeper no cron reativa a rota quando o domínio passa a apontar, sem deploy;
  • runner route status diz, por domínio, o que está acontecendo e por quê.

Sem declarar nada: o comportamento de sempre

App sem routing.tls no manifesto segue exatamente como antes:

  • no deploy, o portão de DNS segura o certificado quando o nome não existe na zona ou aponta para outro IP;
  • no cron, o tls-watch força a nova tentativa quando o DNS passa a existir.

Para o runner cuidar do certificado com o gatekeeper, declare a estratégia.

Declarar a estratégia

Na instância (ou no app, para todas as instâncias):

instances:
  production:
    domain: app.exemplo.com
    routing:
      tls: acme-dns          # auto | acme-http | acme-tls | acme-dns | origin-cert | proxy | none
      provider: cloudflare   # obrigatório com acme-dns
      dns_gate: check        # check (padrão) | skip
      proxy: cloudflare      # opcional: cloudflare | generic
tls O que faz
auto Escolhe pelo modo do domínio (ver abaixo).
acme-http Let's Encrypt pelo desafio HTTP-01. O domínio precisa chegar a este servidor pela internet.
acme-tls Desafio TLS-ALPN-01. Nunca funciona atrás de proxy (o proxy termina o TLS).
acme-dns Desafio DNS-01 pelo provider da zona. Emite antes de o domínio apontar, e serve domínio privado.
origin-cert Certificado que você instalou no Traefik (store). Sem ACME.
proxy O proxy termina o TLS; a origem fica com o certificado padrão.
none Rota sem TLS.

Regras checadas ao carregar o manifesto:

  • acme-dns sem provider bloqueia o deploy;
  • acme-tls com proxy declarado, provider com outra estratégia e tls_options com tls: none dão aviso.

O resolver: tipo + provider

O runner lê o config estático do Traefik em execução e monta o mapa {nome, tipo, provider} de cada resolver. Veja o mapa do seu servidor:

runner traefik info
#   cert_resolvers   : cfresolver (dns-01/cloudflare), myresolver (http-01)
  • Par ausente (você pediu acme-dns com route53 e o Traefik só tem Cloudflare): nenhum outro resolver entra no lugar. A rota sobe HTTPS com o certificado padrão e o motivo resolver_ausente diz o que falta.
  • Dois resolvers para o mesmo par: traefik.cert_resolver do config.yml desempata. Sem ele, ordem alfabética, com aviso.
  • Config estático ilegível ou não encontrado: vale o traefik.cert_resolver do config.yml, assumido HTTP-01, com aviso.

O modo do domínio

Modo Como o runner vê acme-http / acme-tls acme-dns
direto o A/AAAA é o IP deste servidor emite emite se o NS da zona é do provider
proxy IPs da Cloudflare, ou routing.proxy acme-tls nunca; acme-http só depois da sonda de origem idem
privado IP privado, CGNAT ou link-local não emite (use acme-dns) idem
pendente não resolve, NXDOMAIN ou outro IP segura até apontar idem (emite antes da virada)

O NS da zona é consultado pelo DNS público, sem token.

Com auto:

  • direto → acme-http, ou acme-tls se o Traefik só tiver esse;
  • privado → acme-dns com o provider;
  • proxy → acme-dns se houver resolver do provider; senão acme-http com a sonda.

A sonda de origem (domínio atrás de proxy)

Todo domínio com acme-http ou auto ganha, no deploy, o header de resposta X-Runner-Gate. Assim, se o proxy for ligado depois, a sonda já encontra o header. O valor é um HMAC com a chave mestra do runner; ele não revela nada. O middleware fica num arquivo próprio do file provider (runner-gate--<app>--<instância>.yml), que só sai no destroy.

O gatekeeper faz GET https://<domínio>/ pela internet. Se o header volta certo, o proxy entrega nesta origem e o certificado é pedido. Senão, o motivo diz o que achou:

Motivo O que significa
origem_diferente o proxy entrega em outro lugar (outra app, outro servidor)
proxy_estrito_sem_cert_valido 525/526: o proxy exige certificado válido na origem — use acme-dns ou origin-cert
proxy_flexible_com_redirect redirecionamento em loop: o proxy fala HTTP com a origem — ponha em modo Full, ou use acme-dns
sonda_sem_resposta o domínio não respondeu

O gatekeeper no cron

A cada runner fetch --deploy (o cron de 5 min), depois do tls-watch:

  1. para cada app com routing.tls, confere os domínios que ainda não têm o certificado ativo;
  2. domínio que ficou pronto recebe o resolver sem deploy:
    • modo file: o route file da instância é reescrito com o certResolver;
    • modo labels: o container não é recriado. Um router <router>-tls no file provider (<app>__<instância>__tls.yml) espelha o router dos labels, com o mesmo serviço (@docker), o resolver e prioridade maior. Cada deploy apaga esse arquivo, e os labels novos (middlewares, regra) voltam a valer.
  3. pendente ou falho é reconsultado com espera crescente: 5 min, 15 min, 1 h e depois 1 h fixo. App com deploy em andamento fica para a passada seguinte.

Na saída do fetch:

[gatekeeper] 2 domínio(s) com estratégia declarada · 1 conferido(s) agora · 1 ativado(s) · 0 pendente(s) · 0 falho(s)

Cada mudança gera os eventos dominio_ativado e dominio_pendente. Eles vão para o notify e para logs/gatekeeper-events.jsonl. O estado por domínio fica em <state_dir>/gatekeeper/<app>.json.

Ver e ativar na hora

runner route status loja            # por domínio: modo, estado, motivo, resolver, certificado
runner route status loja -o json
runner route activate loja          # confere agora e aplica o que já aponta
runner route activate loja --domain loja.exemplo.com
runner route activate loja --skip-dns-gate

No certificado padrão, o status diz isso com todas as letras:

    ⚠ certificado padrão do Traefik (autoassinado): dns_pendente: o DNS ainda não aponta para este servidor — o navegador vai avisar até o gatekeeper emitir

O mesmo texto aparece no preflight e em dominios[].certificate_note do snapshot e do status --json.

Pular a checagem

Para pedir o certificado na hora, sem conferir o DNS (o domínio fica ignorado):

  • por instância: routing.dns_gate: skip;
  • num deploy: runner deploy <app> --skip-dns-gate;
  • numa ativação: runner route activate <app> --skip-dns-gate;
  • global: RUNNER_SKIP_DNS_GATE=1.

Skip não inventa resolver: par ausente continua resolver_ausente. E acme-tls atrás de proxy continua sem emitir.

No wizard

Depois do domínio, o wizard mostra:

  • o IP público do servidor e o registro a criar;
  • os NS da zona, com o provider identificado;
  • os resolvers do Traefik.

Ele pergunta como o domínio chega ao servidor: direto, atrás da Cloudflare, atrás de outro proxy, privado, ou não declarar. Depois recomenda a estratégia, avisa se falta o par no Traefik e confere o DNS ao vivo. Você escolhe entre seguir pendente (o gatekeeper ativa quando apontar), esperar ou seguir com skip.

Ver também

By Borlot.com.br on 25/10/2026