Pular para o conteúdo

Systemd Unit Validator · Scheduling

Verifique uma unidade systemd antes que o systemd a ignore.

Arquivos .service, .timer e .socket, conferidos contra a leitura que o systemd faz deles: a diretiva na seção errada, o erro de digitação que ele descarta sem avisar, o OnCalendar= que significa que seu timer nunca dispara. Cada achado nomeia uma linha, diz o que o systemd faz a respeito e traz a correção — e nada do que você cola sai da aba.

Roda no seu navegador — nada do que você cola sai desta página. Como comprovamos isso

Roda no seu navegador 35 verificações Sem root Atualizado em 31 de jul. de 2026

Playground do Systemd Unit Validator

Examples

WantedBy= lives in [Install] and is what systemctl enable reads to create its symlinks. Type= tells systemd how to decide the service is up. Persistent=true makes a calendar timer run immediately at boot if it missed its window.

Scope

Detected: —

unit file input

Results update as you type — press Enter to run now.

Press Esc to release keyboard focus from the editor; /Ctrl + Enter checks and leaves the editor. Tap a Line badge to jump to that line. Nothing you paste is uploaded.

Findings

Paste a .service, .timer or .socket file above — or tap an example — to see line-numbered findings here, each with the reason it matters and the fix.

A lacuna

O systemd não avisa quando ignora você.

Um arquivo de unidade que carrega não é um arquivo de unidade que funciona. Escreva User=deploy em [Unit] em vez de [Service] e o serviço roda como root; escreva WantedBy= ali e o systemctl enable não faz nada em silêncio, então a unidade não volta depois de um reboot. Os dois registram uma linha — “Unknown key name … ignoring” — em um journal que ninguém lê num dia bom, e os dois deixam uma unidade que parece saudável no systemctl status.

A ferramenta de verificação que o systemd traz, systemd-analyze verify, é mais completa do que esta página jamais será — e ela precisa de uma máquina com systemd, quase sempre root, e resolve sua unidade contra os usuários, caminhos e drop-ins DAQUELE host. Nada disso está disponível enquanto você revisa um .service em um pull request, num notebook.

E há a saída gerada. Peça um timer a um assistente e você recebe algo fluente e plausível: RestartSecs=5 (essa diretiva não existe), ExecReload= em [Unit], um WantedBy= no bloco errado, ou o mais comum de todos: OnCalendar=*/15, sintaxe de cron que o systemd rejeita de saída, deixando um timer que nunca dispara e nunca aparece no systemctl list-timers. Conferir a afirmação leva um segundo; saber o que conferir é a parte difícil. É para isso que servem as 35 verificações daqui.

Migrando do cron? O Conversor de cron para systemd escreve o par .timer e .service para você — depois cole o resultado aqui para conferir suas edições manuais. As duas ferramentas compartilham uma única gramática de OnCalendar=, então não podem se contradizer sobre um agendamento.

O pipeline

Como funciona.

Quatro passos, todos dentro da sua aba, refeitos enquanto você digita.

  1. Analisar como o systemd analisa.

    Uma continuação com `\` se dobra na linha seguinte — e uma linha de comentário dentro dessa continuação é descartada, não dobrada junto. `#` e `;` abrem comentário apenas no início da linha: NÃO existe comentário no fim da linha, então `RestartSec=30 # depois` realmente deixa o valor como `30 # depois`.

  2. Resolver o que de fato valeu.

    Repetir uma diretiva de lista acrescenta; repetir um escalar significa que o último vence em silêncio; uma atribuição vazia reinicia a lista. As regras leem o valor com que o systemd terminaria, não o primeiro do arquivo — e dizem qual linha perdeu.

  3. Verificar nomes, valores e semântica.

    Cada nome é buscado na seção em que foi escrito, contra 439 diretivas conhecidas. Valores são verificados só onde o systemd é rígido: enums diferenciando maiúsculas, booleanos sem diferenciar, e OnCalendar= contra a gramática completa do systemd.time(7).

  4. Dizer o que o systemd faria.

    Cada achado nomeia a linha física e cita a consequência — “recusa carregar a unidade”, “registra Unknown key name e ignora a linha”. Onde esta página não pode saber algo, ela diz isso em vez de adivinhar.

Referência

Qual diretiva vai onde.

O systemd lê uma diretiva apenas da seção que a possui, e compara nomes de seção e de diretiva diferenciando maiúsculas. Estas são as que aparecem em unidades reais — o validador conhece 439 nomes no total, entre cinco seções que ele verifica e seis que ele nomeia mas não verifica.

[Unit]

Diretiva O que faz
Description= Uma linha, exibida pelo systemctl status e nos logs.
Documentation= URIs separadas por espaço — man:, https:, file:.
After= Só ordem: iniciar depois destas, se elas também estiverem iniciando.
Wants= Puxar estas, mas não falhar se elas falharem.
Requires= Puxar e falhar junto com elas. A ordem NÃO está implícita.
ConditionPathExists= Pular a unidade (não falhar) quando o caminho não existe.
StartLimitIntervalSec= Janela do limite de partidas. Fica aqui, não em [Service].
StartLimitBurst= Partidas permitidas nessa janela antes de o systemd desistir.

[Install]

Diretiva O que faz
WantedBy= Onde o `systemctl enable` cria o link. Timers: timers.target.
RequiredBy= Como WantedBy=, mas o target falha se esta unidade falhar.
Alias= Nome extra ao qual ela responde depois de habilitada.
Also= Outras unidades habilitadas e desabilitadas junto com esta.

[Service]

Diretiva O que faz
Type= simple, exec, forking, oneshot, dbus, notify, notify-reload, idle.
ExecStart= Caminho absoluto. Uma linha só, exceto com Type=oneshot. Não é shell.
ExecStartPre= Roda antes do ExecStart=; uma falha aborta a partida.
ExecReload= O que o `systemctl reload` executa. Fica aqui, nunca em [Unit].
ExecStop= Parada suave opcional; o systemd manda os sinais de kill depois de todo jeito.
PIDFile= Na prática obrigatório com Type=forking: aponta o processo principal real.
Restart= no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, always.
RestartSec= Espera antes de reiniciar. NÃO “RestartSecs”.
RemainAfterExit= Reportar um oneshot concluído como ativo em vez de morto.
User= / Group= Com qual identidade o processo roda. [Service], nunca [Unit].
DynamicUser= UID descartável por partida. Use junto com StateDirectory=.
StateDirectory= O systemd cria /var/lib/<nome> e ajusta o dono em cada partida.
WorkingDirectory= Diretório de trabalho. Não afeta a busca dos Exec*.
Environment= Pares KEY=value; repetível, e $KEY expande nas linhas Exec*.
EnvironmentFile= Ler pares de um arquivo. Com `-` na frente, a ausência é tolerada.
TimeoutStartSec= Quanto o systemd espera a partida terminar.

[Timer]

Diretiva O que faz
OnCalendar= Agenda de relógio, sintaxe do systemd.time(7). Repetível.
OnBootSec= Monotônico: este tempo depois do boot.
OnUnitActiveSec= Monotônico: este tempo depois da última ativação da unidade.
AccuracySec= Folga que o systemd pode usar para agrupar despertares. Padrão 1min.
RandomizedDelaySec= Espalha o disparo para evitar uma avalanche.
Persistent= Só para timers de calendário: recupera uma execução perdida com a máquina desligada.
Unit= O que é disparado. Por padrão, o .service de mesmo nome-base.
WakeSystem= Acorda a máquina da suspensão. Exige hardware compatível.

[Socket]

Diretiva O que faz
ListenStream= Porta, endereço:porta, ou caminho absoluto para um socket UNIX.
ListenDatagram= O equivalente UDP / SOCK_DGRAM.
Accept= no: um serviço recebe o socket de escuta. yes: um por conexão.
SocketMode= Permissões de um socket UNIX ou FIFO, por exemplo 0660.
SocketUser= / SocketGroup= Dono de um socket UNIX no sistema de arquivos.
Service= O que é ativado. Por padrão, o .service de mesmo nome-base.
FileDescriptorName= Nomeia o FD para um serviço com vários sockets distingui-los.

Sintaxe de OnCalendar=

O único campo em que um erro fica invisível: um agendamento que o systemd não consegue analisar significa que a unidade não carrega, então o timer nunca dispara E nunca aparece como quebrado. O validador confere a gramática completa do systemd.time(7) — o mesmo módulo contra o qual o Conversor de cron para systemd escreve.

Forma Significado
DOW YYYY-MM-DD HH:MM:SS A forma completa. Dia da semana, data e segundos são opcionais; uma data sozinha significa 00:00:00.
* Qualquer valor para esse componente.
a,b,c Uma lista de valores.
a..b Um intervalo, menor valor primeiro. Os dias vão de Mon a Sun, então Sun não pode abrir um intervalo.
a/n A cada n, começando em a. O valor inicial NÃO é opcional — veja abaixo.
a..b/n A cada n ao longo de um intervalo.
*-*~03 Antepenúltimo dia do mês (o `~` conta a partir do fim).
… UTC | … America/Sao_Paulo Um fuso no fim. Exige systemd 242 ou mais novo; o nome é resolvido contra a tzdata do host.
Atalho Equivale a
minutely *-*-* *:*:00
hourly *-*-* *:00:00
daily *-*-* 00:00:00
monthly *-*-01 00:00:00
weekly Mon *-*-* 00:00:00
yearly *-01-01 00:00:00
annually *-01-01 00:00:00
quarterly *-01,04,07,10-01 00:00:00
semiannually *-01,07-01 00:00:00

Rejeitado — a repetição não tem valor inicial

INI
[Timer]
OnCalendar=*/15
OnCalendar=0 3 * * *

Aceito — a cada 15 minutos e às 03:00 todos os dias

INI
[Timer]
OnCalendar=*-*-* *:00/15:00
OnCalendar=*-*-* 03:00:00

O limite

O que ele deliberadamente não verifica.

Cada um destes pontos foi considerado e descartado, e a mesma lista está em um comentário no topo do motor. Um validador que adivinha seria pior do que um que diz “eu leio um arquivo, como texto”.

Se as unidades das quais você depende existem

After=, Wants= e Unit= nomeiam outros arquivos. Eles não estão na nossa frente, então um “unidade não encontrada” seria inventado.

Se um caminho, um usuário ou uma capability existe

ExecStart=/usr/local/bin/x é verificado por ser absoluto, não por estar lá. O resto só a sua máquina sabe.

A sintaxe de intervalos de tempo

A gramática do systemd para `5min`, `1h30s` e `2 weeks` é generosa. Reimplementá-la produziria mais falsos positivos do que acertos.

Drop-ins e a correspondência com o nome do arquivo

Uma sobreposição em .d/ e a questão de o nome do arquivo casar com o que Unit= espera estão fora de um único arquivo colado.

Ordem sem dependência

After= sem Wants= é frequentemente deliberado. Sinalizar isso seria opinião disfarçada de achado.

Valores em [Mount], [Path], [Swap], [Automount], [Slice] e [Scope]

Os NOMES das diretivas são conhecidos — então `What=` em [Service] é atribuído corretamente — mas os valores não são verificados, e os resultados dizem isso em voz alta.

ProtectSystem=, ProtectHome=, RestrictNamespaces= e companhia

Aceitam valores nomeados OU um booleano, então uma verificação rígida rejeitaria entradas legítimas. São tratados como strings opacas de propósito.

PrivateTmp= como booleano puro

O systemd 257 adicionou `PrivateTmp=disconnected`. Um validador que chamasse um valor legítimo de erro seria exatamente o falso positivo contra o qual esta ferramenta existe.

O próximo disparo de um timer

Isso exige um relógio e um banco de fusos horários. Uma “próxima execução” impressa como fato e silenciosamente errada é pior do que resposta nenhuma.

Qualquer coisa sobre o sistema em execução

Sem systemctl, sem journal, sem árvore de drop-ins, sem tzdata. Um arquivo, lido como texto — e é por isso que não precisa de root e não envia nada.

Limites declarados em vez de escondidos: o validador lê até 200.000 caracteres, guarda no máximo 20 achados por verificação e 200 no total, renderiza no máximo 50 linhas por severidade e informa a contagem real sempre que um limite se aplica. Uma diretiva não reconhecida é sempre uma nota, nunca um erro.

Próximo passo

Cole o relatório na revisão.

“Copiar relatório” entrega a execução inteira em texto puro — uma linha por achado, com o número da linha, o id da verificação e a correção. Depois siga adiante: transforme uma linha de crontab em um timer de verdade, ou leia primeiro um agendamento cron em português claro.

report.txt
Systemd Unit Validator — 2 errors, 1 warning across 8 lines
Scope: system

ERRORS (2)
  L3 wrong-section: WantedBy= belongs in [Install], not [Unit]. — fix: Move the line into the `[Install]` section.
  L8 typo-directive: ExecStrat= is not a systemd directive — did you mean ExecStart=? — fix: Write `ExecStart=`.

WARNINGS (1)
  missing-install: No [Install] section, so “systemctl enable” has nothing to do. — fix: Add `[Install]` with `WantedBy=multi-user.target` (or `default.target` for a user unit).

Checked client-side at opscanopy.com/systemd-unit-validator/ — 0 bytes uploaded.

FAQ

Suas perguntas, respondidas.

Toque em uma pergunta para expandir a resposta.

35 verificações sobre uma análise real do arquivo, em quatro grupos. Estrutura: um cabeçalho de seção inválido, um [unit] em minúsculas, uma seção desconhecida ou repetida, uma linha sem “=”, uma atribuição escrita antes da primeira seção. Nomes: uma diretiva na seção errada (User= em [Unit]), um erro de digitação que o systemd descarta (ExecStrat=), um nome obsoleto, um nome que a tabela simplesmente não conhece. Valores: um enum errado — o systemd compara esses valores diferenciando maiúsculas —, um booleano que não é booleano, um caminho Exec* que não é absoluto, sintaxe de shell em uma linha Exec* que o systemd não vai interpretar, um especificador % desconhecido e um “comentário” no fim da linha que na verdade faz parte do valor. Semântica: um serviço sem ExecStart=, duas linhas ExecStart= quando Type não é oneshot, Type=forking sem PIDFile=, Restart=always com Type=oneshot, um timer sem gatilho, um OnCalendar= que o systemd não consegue analisar, Persistent= em um timer monotônico, um socket sem nada para escutar, um [Install] ausente e um target de sistema em uma unidade de usuário.

Não, e não pode ser. O systemd-analyze verify roda em uma máquina que tem systemd, precisa de root para quase tudo que está no caminho de busca do sistema e, principalmente, resolve a sua unidade contra AQUELA máquina: os usuários dela, os caminhos dela, os drop-ins dela, as outras unidades dela. Isso o torna a ferramenta mais completa — e a ferramenta errada para o momento em que você realmente quer a verificação: revisar um .service em um pull request num notebook macOS, ou conferir uma unidade que um assistente acabou de escrever para você. Esta página lê um arquivo como texto, no seu navegador, e é explícita sobre a diferença: tudo que ela não pode saber só pelo texto está listado abaixo em vez de adivinhado.

Não. O parser e as 35 verificações são JavaScript rodando na sua aba — não há servidor, não há chamada de API e não há log, então 0 bytes são enviados. Aqui isso pesa: arquivos de unidade carregam hostnames internos, caminhos privados, nomes de contas de serviço, locais de EnvironmentFile= e, de vez em quando, uma credencial que ninguém deveria ter colocado em um valor.

A causa mais comum, de longe, é um OnCalendar= que o systemd não consegue analisar porque sintaxe de cron foi colada ali. Duas formas explicam quase todos os casos. Primeira, um “*/15” puro: uma repetição de calendário no systemd exige um valor inicial explícito, então “*-*-* *:00/15:00” (ou a forma curta “*:00/15:00”) é aceito e “*/15” é rejeitado por inteiro. Segunda, um agendamento crontab completo de cinco campos (“0 3 * * *”): eventos de calendário do systemd são “DOW YYYY-MM-DD HH:MM:SS”, não cinco campos, e esta ferramenta reconhece o formato e te manda para o conversor. Depois disso, procure um timer sem gatilho nenhum e um .timer sem Unit= cujo .service tenha outro nome. Uma unidade que o systemd se recusa a carregar nunca aparece em systemctl list-timers — e é por isso que nada parece errado.

Porque a configuração simplesmente não surte efeito. O systemd lê WantedBy= apenas de [Install]; em qualquer outro lugar ele registra “Unknown key name 'WantedBy' in section 'Unit', ignoring.” e segue adiante. A unidade carrega, inicia na mão, parece saudável — e o systemctl enable não faz nada em silêncio, então ela nunca volta depois de um reboot. Nesta página, erro significa que o systemd recusa a unidade OU descarta a configuração por completo; aviso significa que a configuração se aplica e é uma armadilha. Uma configuração descartada pertence ao primeiro grupo, porque o arquivo já não descreve o que a máquina vai fazer.

Porque esta página traz uma tabela de diretivas — 439 nomes — e a sua máquina traz um systemd. Cada versão adiciona diretivas novas, então um nome que esta tabela não conhece pode ser perfeitamente válido onde você vai executá-lo. Chamar isso de erro treinaria você a ignorar o relatório inteiro, que é a única coisa que um linter não sobrevive. Um nome que passa raspando por uma diretiva real é diferente: ali a evidência basta para afirmar que o systemd está jogando a linha fora, e ISSO é reportado como erro, com a correção.

Três coisas, todas sobre qual gerenciador vai carregar o arquivo. No escopo de usuário, um WantedBy= que nomeia um target de sistema (multi-user.target, graphical.target) é sinalizado: o gerenciador de usuário não tem esse target, então o systemctl --user enable não tem o que linkar e a unidade nunca inicia — default.target é o que você quer, e timers.target existe lá de verdade. O escopo também viaja no link de compartilhamento, para que um link enviado reproduza os achados que você viu, e não outros. Todo o resto — a análise, os nomes, os valores — é idêntico, porque o systemd lê o arquivo do mesmo jeito nos dois casos.

Ele lê exatamente um arquivo, então: um template é analisado normalmente e %i é sinalizado como nota (só tem valor quando o arquivo é name@.service iniciado como name@instance.service); um fragmento drop-in é analisado bem por si só, mas ninguém aqui sabe o que ele está sobrescrevendo, então um arquivo parcial vai parecer uma unidade sem ExecStart=; e uma unidade nomeada em After=, Wants= ou Unit= nunca é resolvida, porque aquele arquivo não está na nossa frente. Adivinhar nos três casos seria uma resposta confiante e errada, o que é pior do que resposta nenhuma.

Até 200.000 caracteres, cerca de quatro mil linhas — duas ordens de magnitude além de qualquer arquivo de unidade real. Acima disso ele recusa com uma mensagem em vez de congelar sua aba, porque uma entrada desse tamanho é um dump do journal, não uma unidade. Os achados também têm limite: 20 por verificação e 200 no total, e o painel informa o limite e a contagem real sempre que um deles se aplica. A lista de resultados tem um limite separado de 50 linhas por severidade, novamente com o número real.

More free, private DevOps tools.

O Systemd Unit Validator é uma das ferramentas do OpsCanopy — uma copa crescente de validadores, conversores e testadores que rodam no navegador e nunca tocam um servidor.

39 free tools, every one offline-capable — opscanopy.com works with no signup and nothing uploaded.

Relacionado: Conversor de cron para systemd para escrever o timer em primeiro lugar, Testador de expressões cron para ler o lado cron em português claro, o Dockerfile Linter e o GitLab CI Validator para a mesma checagem “pegar antes de subir” em outras configurações, e o Env Example Checker para as variáveis que um EnvironmentFile= deveria fornecer — ou percorra o diretório completo de ferramentas.

Sem afiliação nem endosso do projeto systemd. Nomes de diretivas e formatos servem apenas para descrever o que esta ferramenta verifica; confirme sempre uma unidade contra a versão do systemd que vai carregá-la.