Saltar al contenido

Systemd Unit Validator · Scheduling

Revisa una unidad systemd antes de que systemd la ignore.

Archivos .service, .timer y .socket, comprobados contra la lectura que hace systemd de ellos: la directiva en la sección equivocada, el typo que descarta sin decírtelo, el OnCalendar= que significa que tu timer nunca se dispara. Cada hallazgo nombra una línea, dice qué hace systemd al respecto y trae la corrección — y nada de lo que pegues sale de la pestaña.

Se ejecuta en tu navegador: nada de lo que pegas sale de esta página. Cómo lo demostramos

Se ejecuta en tu navegador 35 comprobaciones Sin root Actualizado el 31 jul 2026

Playground del 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.

El hueco

systemd no te avisa cuando te ignora.

Un archivo de unidad que carga no es un archivo de unidad que funciona. Escribe User=deploy en [Unit] en lugar de [Service] y el servicio corre como root; escribe WantedBy= ahí y systemctl enable no hace nada en silencio, así que la unidad no vuelve tras un reinicio. Ambos registran una línea —«Unknown key name … ignoring»— en un journal que nadie lee en un buen día, y ambos dejan una unidad que parece sana en systemctl status.

La herramienta de verificación que trae systemd, systemd-analyze verify, es más exhaustiva de lo que esta página será nunca — y necesita una máquina con systemd, normalmente root, y resuelve tu unidad contra los usuarios, rutas y drop-ins de ESE host. Nada de lo cual tienes mientras revisas un .service en un pull request desde un portátil.

Y luego está la salida generada. Pídele un timer a un asistente y obtienes algo fluido y plausible: RestartSecs=5 (esa directiva no existe), ExecReload= en [Unit], un WantedBy= en el bloque equivocado, o el más común de todos: OnCalendar=*/15, sintaxis de cron que systemd rechaza de plano, dejando un timer que nunca se dispara y nunca aparece en systemctl list-timers. Comprobar la afirmación lleva un segundo; saber qué comprobar es la parte difícil. Para eso están las 35 comprobaciones de aquí.

¿Migrando desde cron? El Convertidor de cron a systemd escribe por ti el par .timer y .service; después pega el resultado aquí para revisar tus ediciones a mano. Las dos herramientas comparten una única gramática de OnCalendar=, así que no pueden contradecirse sobre un horario.

El proceso

Cómo funciona.

Cuatro pasos, todos dentro de tu pestaña, repetidos mientras escribes.

  1. Analizarlo como lo hace systemd.

    Una continuación con `\` se pliega sobre la línea siguiente, y una línea de comentario dentro de esa continuación se descarta, no se pliega. `#` y `;` abren un comentario solo al principio de la línea: NO hay comentarios al final de línea, así que `RestartSec=30 # luego` de verdad deja el valor en `30 # luego`.

  2. Resolver lo que realmente quedó.

    Repetir una directiva de lista añade; repetir un escalar significa que el último gana en silencio; una asignación vacía reinicia la lista. Las reglas leen el valor con el que systemd acabaría, no el primero del archivo, y dicen qué línea perdió.

  3. Comprobar nombres, valores y semántica.

    Cada nombre se busca en la sección en la que está escrito, contra 439 directivas conocidas. Los valores solo se comprueban donde systemd es estricto: enums distinguiendo mayúsculas, booleanos sin distinguirlas y OnCalendar= contra la gramática completa de systemd.time(7).

  4. Decir qué haría systemd.

    Cada hallazgo nombra la línea física y cita la consecuencia: «se niega a cargar la unidad», «registra Unknown key name e ignora la línea». Donde esta página no puede saber algo, lo dice en lugar de adivinar.

Referencia

Qué directiva va dónde.

systemd lee una directiva solo de la sección que la posee, y compara nombres de sección y de directiva distinguiendo mayúsculas. Estas son las que aparecen en unidades reales: el validador conoce 439 nombres en total, entre cinco secciones que comprueba y seis que nombra pero no comprueba.

[Unit]

Directiva Qué hace
Description= Una línea, visible en systemctl status y en los logs.
Documentation= URIs separadas por espacios: man:, https:, file:.
After= Solo orden: arrancar después de estas, si también arrancan.
Wants= Arrastrarlas, pero no fallar si ellas fallan.
Requires= Arrastrarlas y fallar con ellas. El orden NO está implícito.
ConditionPathExists= Omitir la unidad (no fallarla) cuando la ruta no está.
StartLimitIntervalSec= Ventana del límite de arranques. Va aquí, no en [Service].
StartLimitBurst= Arranques permitidos en esa ventana antes de que systemd desista.

[Install]

Directiva Qué hace
WantedBy= Dónde enlaza `systemctl enable`. Timers: timers.target.
RequiredBy= Como WantedBy=, pero el target falla si esta unidad falla.
Alias= Nombre extra al que responde una vez habilitada.
Also= Otras unidades que se habilitan y deshabilitan junto a esta.

[Service]

Directiva Qué hace
Type= simple, exec, forking, oneshot, dbus, notify, notify-reload, idle.
ExecStart= Ruta absoluta. Una sola línea salvo con Type=oneshot. No es una shell.
ExecStartPre= Se ejecuta antes de ExecStart=; un fallo aborta el arranque.
ExecReload= Lo que ejecuta `systemctl reload`. Va aquí, nunca en [Unit].
ExecStop= Parada suave opcional; systemd envía las señales de kill después igualmente.
PIDFile= En la práctica obligatorio con Type=forking: nombra el proceso principal real.
Restart= no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, always.
RestartSec= Espera antes de reiniciar. NO «RestartSecs».
RemainAfterExit= Reportar un oneshot terminado como activo en vez de muerto.
User= / Group= Con qué identidad corre el proceso. [Service], nunca [Unit].
DynamicUser= UID desechable en cada arranque. Úsalo con StateDirectory=.
StateDirectory= systemd crea /var/lib/<nombre> y le arregla el dueño en cada arranque.
WorkingDirectory= Directorio de trabajo. No afecta a la búsqueda de Exec*.
Environment= Pares KEY=value; repetible, y $KEY se expande en las líneas Exec*.
EnvironmentFile= Leer pares de un archivo. Con `-` delante, tolera que falte.
TimeoutStartSec= Cuánto espera systemd a que termine el arranque.

[Timer]

Directiva Qué hace
OnCalendar= Horario de reloj de pared, sintaxis de systemd.time(7). Repetible.
OnBootSec= Monotónico: este tiempo después del arranque.
OnUnitActiveSec= Monotónico: este tiempo tras la última activación de la unidad.
AccuracySec= Holgura que systemd puede tomarse para agrupar despertares. Por defecto 1min.
RandomizedDelaySec= Dispersa el disparo para evitar una estampida.
Persistent= Solo timers de calendario: recupera una ejecución perdida con la máquina apagada.
Unit= Qué dispara. Por defecto, el .service con el mismo nombre base.
WakeSystem= Despierta la máquina de la suspensión. Requiere hardware adecuado.

[Socket]

Directiva Qué hace
ListenStream= Puerto, dirección:puerto o ruta absoluta para un socket UNIX.
ListenDatagram= El equivalente UDP / SOCK_DGRAM.
Accept= no: un servicio recibe el socket de escucha. yes: uno por conexión.
SocketMode= Permisos de un socket UNIX o FIFO, p. ej. 0660.
SocketUser= / SocketGroup= Dueño de un socket UNIX en el sistema de archivos.
Service= Qué se activa. Por defecto, el .service con el mismo nombre base.
FileDescriptorName= Nombra el FD para que un servicio con varios sockets los distinga.

Sintaxis de OnCalendar=

El único campo donde un error es invisible: un horario que systemd no puede analizar significa que la unidad no carga, así que el timer nunca se dispara Y nunca aparece como roto. El validador comprueba la gramática completa de systemd.time(7), el mismo módulo contra el que escribe el Convertidor de cron a systemd.

Forma Significado
DOW YYYY-MM-DD HH:MM:SS La forma completa. Día de la semana, fecha y segundos son opcionales; una fecha sola significa 00:00:00.
* Cualquier valor para ese componente.
a,b,c Una lista de valores.
a..b Un rango, el valor menor primero. Los días van Mon..Sun, así que Sun no puede abrir un rango.
a/n Cada n, empezando en a. El valor de inicio NO es opcional: ver más abajo.
a..b/n Cada n a lo largo de un rango.
*-*~03 Antepenúltimo día del mes (el `~` cuenta desde el final).
… UTC | … Europe/Madrid Una zona horaria al final. Requiere systemd 242 o posterior; el nombre se resuelve contra la tzdata del host.
Atajo 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

Rechazado — la repetición no tiene valor de inicio

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

Aceptado — cada 15 minutos y a las 03:00 a diario

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

El límite

Qué no comprueba a propósito.

Cada uno de estos puntos se consideró y se descartó, y la misma lista está en un comentario al principio del motor. Un validador que adivinara sería peor que uno que dice «leo un archivo, como texto».

Si existen las unidades de las que dependes

After=, Wants= y Unit= nombran otros archivos. No están delante de nosotros, así que un «unidad no encontrada» sería inventado.

Si existe una ruta, un usuario o una capability

ExecStart=/usr/local/bin/x se comprueba por ser absoluta, no por estar ahí. El resto solo lo sabe tu máquina.

La sintaxis de los intervalos de tiempo

La gramática de systemd para `5min`, `1h30s` y `2 weeks` es generosa. Reimplementarla daría más falsos positivos que aciertos.

Drop-ins y coincidencia con el nombre de archivo

Una sobrescritura en .d/ y si el nombre del archivo coincide con lo que espera Unit= quedan fuera de un único archivo pegado.

Orden sin dependencia

After= sin Wants= es deliberado a menudo. Marcarlo sería una opinión disfrazada de hallazgo.

Valores en [Mount], [Path], [Swap], [Automount], [Slice] y [Scope]

Sus NOMBRES de directiva sí se conocen —así que `What=` en [Service] se atribuye bien— pero sus valores no se comprueban, y los resultados lo dicen en voz alta.

ProtectSystem=, ProtectHome=, RestrictNamespaces= y compañía

Aceptan valores con nombre O un booleano, así que una comprobación estricta rechazaría entradas válidas. Se tratan como cadenas opacas a propósito.

PrivateTmp= como booleano puro

systemd 257 añadió `PrivateTmp=disconnected`. Un validador que llamara error a un valor válido sería justo el falso positivo contra el que existe esta herramienta.

El próximo disparo de un timer

Eso necesita un reloj y una base de datos de zonas horarias. Un «próxima ejecución» impreso como hecho y silenciosamente equivocado es peor que ninguna respuesta.

Cualquier cosa del sistema en marcha

Sin systemctl, sin journal, sin árbol de drop-ins, sin tzdata. Un archivo, leído como texto: por eso no necesita root y no sube nada.

Límites, dichos y no escondidos: el validador lee hasta 200.000 caracteres, guarda como máximo 20 hallazgos por comprobación y 200 en total, renderiza como máximo 50 filas por severidad y te dice la cifra real siempre que se aplique un tope. Una directiva no reconocida es siempre una nota, nunca un error.

Siguiente paso

Pega el informe en la revisión.

«Copiar informe» te da la ejecución completa en texto plano: una línea por hallazgo, con el número de línea, el id de la comprobación y la corrección. Y luego sigue: convierte una línea de crontab en un timer real, o lee primero un horario de cron en lenguaje 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

Tus preguntas, respondidas.

Toca una pregunta para desplegar la respuesta.

35 comprobaciones sobre un análisis real del archivo, en cuatro grupos. Estructura: un encabezado de sección inválido, un [unit] en minúsculas, una sección desconocida o repetida, una línea sin «=», una asignación escrita antes de la primera sección. Nombres: una directiva en la sección equivocada (User= en [Unit]), un typo que systemd descarta (ExecStrat=), un nombre obsoleto, un nombre que la tabla simplemente no conoce. Valores: un enum incorrecto —systemd los compara distinguiendo mayúsculas—, un booleano que no lo es, una ruta Exec* que no es absoluta, sintaxis de shell en una línea Exec* que systemd no interpretará, un especificador % desconocido y un «comentario» al final de línea que en realidad forma parte del valor. Semántica: un servicio sin ExecStart=, dos líneas ExecStart= cuando Type no es oneshot, Type=forking sin PIDFile=, Restart=always con Type=oneshot, un timer sin disparador, un OnCalendar= que systemd no puede analizar, Persistent= en un timer monotónico, un socket sin nada donde escuchar, un [Install] ausente y un target de sistema en una unidad de usuario.

No, y no puede serlo. systemd-analyze verify se ejecuta en una máquina que tiene systemd, necesita root para casi todo lo que está en la ruta de búsqueda del sistema y, sobre todo, resuelve tu unidad contra ESA máquina: sus usuarios, sus rutas, sus drop-ins, sus otras unidades. Eso lo convierte en la herramienta más exhaustiva y en la herramienta equivocada para el momento en que de verdad quieres la comprobación: revisar un .service en un pull request desde un portátil macOS, o verificar una unidad que un asistente acaba de escribirte. Esta página lee un archivo como texto, en tu navegador, y es explícita sobre la diferencia: todo lo que no puede saber solo con el texto está listado más abajo en lugar de adivinado.

No. El parser y las 35 comprobaciones son JavaScript ejecutándose en tu pestaña: no hay servidor, no hay llamada a una API y no hay logging, así que se suben 0 bytes. Aquí eso importa: los archivos de unidad llevan nombres de host internos, rutas privadas, nombres de cuentas de servicio, ubicaciones de EnvironmentFile= y de vez en cuando una credencial que nadie debería haber puesto en un valor.

La causa más común, con mucha diferencia, es un OnCalendar= que systemd no puede analizar porque se pegó ahí sintaxis de cron. Dos formas explican casi todos los casos. Primera, un «*/15» a secas: una repetición de calendario en systemd necesita un valor de inicio explícito, así que «*-*-* *:00/15:00» (o la forma corta «*:00/15:00») se acepta y «*/15» se rechaza por completo. Segunda, un horario crontab entero de cinco campos («0 3 * * *»): los eventos de calendario de systemd son «DOW YYYY-MM-DD HH:MM:SS», no cinco campos, y esta herramienta reconoce la forma y te manda al convertidor. Después de eso, busca un timer sin ningún disparador y un .timer sin Unit= cuyo .service se llame de otra manera. Una unidad que systemd se niega a cargar nunca aparece en systemctl list-timers, y por eso nada parece estar mal.

Porque el ajuste no surte efecto en absoluto. systemd lee WantedBy= solo de [Install]; en cualquier otro sitio registra «Unknown key name 'WantedBy' in section 'Unit', ignoring.» y sigue adelante. La unidad carga, arranca a mano, parece sana… y systemctl enable no hace nada en silencio, así que nunca vuelve tras un reinicio. En esta página, error significa que systemd rechaza la unidad O descarta el ajuste por completo; advertencia significa que el ajuste se aplica y es una trampa. Un ajuste descartado pertenece al primer grupo, porque el archivo ya no describe lo que hará la máquina.

Porque esta página trae una tabla de directivas —439 nombres— y tu máquina trae un systemd. Cada versión añade directivas nuevas, así que un nombre que esta tabla no conoce puede ser perfectamente válido donde vas a ejecutarlo. Llamarlo error te entrenaría para ignorar todo el informe, que es lo único que un linter no sobrevive. Un nombre que se parece mucho a una directiva real es otra cosa: ahí la evidencia basta para afirmar que systemd está tirando la línea, y ESO se reporta como error con la corrección.

Tres cosas, todas sobre qué gestor cargará el archivo. En ámbito de usuario se marca un WantedBy= que nombra un target de sistema (multi-user.target, graphical.target): el gestor de usuario no tiene ese target, así que systemctl --user enable no tiene nada que enlazar y la unidad nunca arranca —default.target es lo que quieres, y timers.target sí existe ahí. El ámbito también viaja en el enlace para compartir, de modo que un enlace que envías reproduce los hallazgos que viste y no otros. Todo lo demás —el análisis, los nombres, los valores— es idéntico, porque systemd lee el archivo igual en ambos casos.

Lee exactamente un archivo, así que: una plantilla se analiza con normalidad y %i se marca como nota (solo tiene valor cuando el archivo es name@.service y se arranca como name@instance.service); un fragmento drop-in se analiza bien por sí solo, pero aquí nadie sabe qué está sobrescribiendo, así que un archivo parcial parecerá una unidad a la que le falta ExecStart=; y una unidad nombrada en After=, Wants= o Unit= nunca se resuelve, porque ese archivo no está delante de nosotros. Adivinar en los tres casos sería una respuesta segura de sí misma y equivocada, que es peor que ninguna respuesta.

Hasta 200.000 caracteres, unas cuatro mil líneas: dos órdenes de magnitud más que cualquier archivo de unidad real. Por encima de eso se niega con un mensaje en lugar de congelar tu pestaña, porque una entrada así es un volcado del journal, no una unidad. Los hallazgos también tienen tope: 20 por comprobación y 200 en total, y el panel indica el tope y la cifra real cuando alguno se aplica. La lista de resultados tiene un tope aparte de 50 filas por severidad, otra vez con el número real.

More free, private DevOps tools.

El Systemd Unit Validator es una de las herramientas de OpsCanopy — una copa creciente de validadores, convertidores y testers que se ejecutan en el navegador y nunca tocan un servidor.

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

Relacionado: Convertidor de cron a systemd para escribir el timer de entrada, Tester de expresiones cron para leer el lado cron en lenguaje claro, el Dockerfile Linter y el GitLab CI Validator para la misma comprobación «pillarlo antes de enviarlo» en otra configuración, y el Env Example Checker para las variables que un EnvironmentFile= debería aportar — o recorre el directorio completo de herramientas.

Sin afiliación ni respaldo del proyecto systemd. Los nombres de directivas y formatos solo describen lo que comprueba esta herramienta; confirma siempre una unidad contra la versión de systemd que la va a cargar.