Systemd Unit Generator

Genera unidades .service listas para instalar en /etc/systemd/system.

toolboox.app/herramientas/systemd-generator

Herramienta

/etc/systemd/system/mi-app.service

Qué problema resuelve

Convertir un script en un servicio robusto con reinicio automático.

systemd gestiona servicios en la práctica totalidad de distribuciones Linux modernas mediante unidades declarativas: archivos `.service`, `.timer`, `.socket` o `.mount` que describen qué ejecutar, cuándo y con qué restricciones de reinicio y aislamiento. Frente a un script de init tradicional, una unidad bien escrita gestiona reinicios automáticos, dependencias de arranque y límites de recursos con unas pocas líneas declarativas, sin lógica de shell propensa a fallos silenciosos. Este generador de unidades systemd construye el archivo `.service` o `.timer` completo —secciones `[Unit]`, `[Service]` y `[Install]`— a partir de un formulario, evitando los errores de sintaxis que provocan que `systemctl daemon-reload` acepte una unidad rota sin avisar hasta que falla al arrancar.

Anatomía de una unidad .service: Unit, Service e Install

La sección [Unit] describe metadatos y dependencias: Description, After=network-online.target para esperar a que la red esté lista, y Requires= si el servicio no tiene sentido sin otra unidad activa. [Service] define cómo ejecutar el proceso: ExecStart con la ruta absoluta del binario (systemd no busca en el PATH del usuario), User/Group para no correr como root innecesariamente, y Restart=on-failure para reinicios automáticos ante caídas. [Install] sólo importa cuando habilitas la unidad con systemctl enable: WantedBy=multi-user.target la engancha al arranque normal del sistema en modo multiusuario sin entorno gráfico.

Tipos de servicio: simple, forking, notify y oneshot

Type=simple (el valor por defecto) asume que el proceso lanzado por ExecStart es el proceso principal y permanece en primer plano; es el más habitual para binarios modernos que no hacen fork. Type=forking es para daemons clásicos que arrancan, hacen fork a segundo plano y terminan el proceso padre; systemd necesita entonces PIDFile= para saber qué proceso vigilar. Type=notify exige que el propio programa envíe una notificación vía sd_notify() cuando ya está listo para recibir tráfico, lo que permite a systemd secuenciar correctamente servicios dependientes que de otro modo arrancarían antes de tiempo. Type=oneshot es para tareas que terminan y no dejan proceso residente, como un script de backup, y suele combinarse con RemainAfterExit=yes si otras unidades dependen de que ya se haya ejecutado.

Restart y límites: evitar el bucle de reinicio infinito

Restart=on-failure reinicia el servicio cuando termina con código de salida distinto de cero o por una señal, pero sin RestartSec y StartLimitBurst un binario que falla al arrancar puede entrar en un bucle de reinicios cada pocos milisegundos, saturando CPU y logs. RestartSec=5 introduce una espera entre intentos, y StartLimitIntervalSec=60 junto con StartLimitBurst=5 en [Unit] detiene los reintentos si el servicio falla más de cinco veces en sesenta segundos, marcando la unidad como failed en lugar de seguir reintentando indefinidamente. Este límite es el que evita que un despliegue roto convierta al servidor en un generador infinito de entradas de journal.

Aislamiento y hardening: ProtectSystem, NoNewPrivileges, PrivateTmp

systemd permite endurecer un servicio sin contenedores: ProtectSystem=strict monta el sistema de archivos raíz como sólo lectura salvo las rutas listadas en ReadWritePaths=, PrivateTmp=yes da al servicio su propio /tmp aislado del resto del sistema, y NoNewPrivileges=yes impide que el proceso escale privilegios vía setuid aunque el binario los tenga. ProtectHome=yes oculta los directorios de usuarios, relevante para servicios que no necesitan acceder a /home. Estas directivas cuestan una línea cada una y reducen de forma sustancial el radio de impacto si el proceso es comprometido, sin necesidad de levantar un contenedor completo sólo para aislar un servicio.

Timers de systemd frente a cron

Un .timer empareja con una unidad .service del mismo nombre base y la dispara según OnCalendar= (sintaxis similar a cron pero más legible, por ejemplo OnCalendar=*-*-* 03:00:00 para las 3 de la madrugada todos los días) o OnBootSec=/OnUnitActiveSec= para intervalos relativos. La ventaja sobre cron es que el timer se integra con el resto de systemd: Persistent=true ejecuta el job pendiente si la máquina estuvo apagada en el momento programado, cada ejecución queda registrada en el journal con su propio estado de éxito o fallo, y systemctl list-timers muestra de un vistazo la próxima ejecución de todos los timers activos, algo que cron no ofrece sin herramientas adicionales.

Diagnosticar un servicio que no arranca

El primer comando ante un fallo es systemctl status nombre.service, que muestra el estado (active, failed, activating), el PID principal si lo hay, y las últimas líneas de log incrustadas. Para el histórico completo, journalctl -u nombre.service filtra únicamente los mensajes de esa unidad, y añadiendo -f se sigue en tiempo real como con tail -f. journalctl -u nombre.service --since "10 min ago" acota la ventana temporal cuando el log es extenso. Si el fallo es de sintaxis en el propio archivo de unidad y no del programa, systemd-analyze verify nombre.service detecta directivas mal escritas o secciones inexistentes antes incluso de intentar arrancarlo.

Recargar la configuración sin reiniciar el sistema

Después de crear o modificar un archivo en /etc/systemd/system/, systemctl daemon-reload es obligatorio para que systemd relea las unidades desde disco; sin este paso, systemctl restart seguirá usando la definición cacheada en memoria y los cambios no se aplicarán aunque el archivo ya esté actualizado. systemctl enable nombre.service crea el symlink en el target correspondiente para que arranque en el próximo boot, pero no lo arranca ahora; systemctl enable --now nombre.service hace ambas cosas en un solo comando, que es el patrón más habitual al desplegar un servicio nuevo.

Salidas reales de ejemplo

systemctl status de un servicio activo
● miapp.service - Servicio API de mi aplicación
     Loaded: loaded (/etc/systemd/system/miapp.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2024-05-31 09:12:04 UTC; 2h 14min ago
   Main PID: 48213 (node)
      Tasks: 11 (limit: 4678)
     Memory: 82.4M
        CPU: 3.912s
     CGroup: /system.slice/miapp.service
             └─48213 /usr/bin/node /opt/miapp/dist/server.js

may 31 09:12:04 srv1 systemd[1]: Started Servicio API de mi aplicación.
may 31 09:12:05 srv1 node[48213]: Server listening on port 3000

Fíjate en el campo Active (running) con el tiempo desde el arranque y en Main PID, que identifica el proceso vigilado por systemd.

journalctl -u tras un reinicio en bucle detenido por StartLimitBurst
may 31 10:02:11 srv1 systemd[1]: miapp.service: Main process exited, code=exited, status=1/FAILURE
may 31 10:02:11 srv1 systemd[1]: miapp.service: Failed with result 'exit-code'.
may 31 10:02:16 srv1 systemd[1]: miapp.service: Scheduled restart job, restart counter is at 5.
may 31 10:02:16 srv1 systemd[1]: miapp.service: Start request repeated too quickly.
may 31 10:02:16 srv1 systemd[1]: miapp.service: Failed with result 'start-limit-hit'.
may 31 10:02:16 srv1 systemd[1]: Failed to start Servicio API de mi aplicación.

El mensaje 'start-limit-hit' confirma que StartLimitBurst y StartLimitIntervalSec han evitado un bucle de reinicios infinito.

systemd-analyze verify detectando un error de unidad
$ systemd-analyze verify /etc/systemd/system/miapp.service
miapp.service:12: Unknown key name 'ExecStar' in section 'Service', ignoring.
miapp.service: Service has no ExecStart= setting, which is only allowed for Type=oneshot services.

El verificador señala la línea concreta con la directiva mal escrita, sin necesidad de intentar arrancar el servicio.

systemctl list-timers de un timer diario
NEXT                         LEFT       LAST                         PASSED     UNIT               ACTIVATES
Sat 2024-06-01 03:00:00 UTC  16h left   Fri 2024-05-31 03:00:00 UTC  7h ago     backup.timer       backup.service

1 timers listed.

Muestra la próxima ejecución programada, la última completada y si el timer está activo (LEFT/PASSED).

Tipos de unidad Type= en la sección [Service]

TypeComportamiento esperado del procesoEjemplo típico
simplePermanece en primer plano tras ExecStartAPIs Node.js, binarios Go compilados
forkingHace fork y el padre termina; requiere PIDFileDaemons clásicos tipo nginx en modo daemon
notifyAvisa a systemd vía sd_notify() cuando está listoServicios que deben secuenciarse con precisión
oneshotEjecuta y termina, sin proceso residenteScripts de backup o migraciones puntuales

El tipo correcto depende de cómo se comporta el binario al arrancar.

cron frente a systemd timer

Característicacronsystemd timer
Registro de ejecuciónSólo si el propio script escribe logIntegrado en journalctl automáticamente
Recuperar ejecuciones perdidasNo, sin herramientas externasSí, con Persistent=true
Dependencias con otros serviciosNo soportadoSí, vía After=/Requires=
Ver próxima ejecuciónNo sin parsear el crontab manualmentesystemctl list-timers

Ambos programan tareas periódicas, pero el timer se integra con el resto del ecosistema systemd.

Casos de uso comunes

  • Desplegar una API Node.js o Python como servicio persistente con reinicio automático ante caídas
  • Sustituir una entrada de cron por un timer con registro de ejecuciones en journalctl
  • Endurecer un servicio expuesto a internet con ProtectSystem, PrivateTmp y NoNewPrivileges
  • Encadenar servicios dependientes (base de datos antes que la API) con After= y Requires=
  • Ejecutar un script de backup nocturno con Type=oneshot y Persistent=true por si el servidor está apagado

Buenas prácticas

  • Ejecuta siempre `systemctl daemon-reload` tras editar una unidad, antes de reiniciar el servicio
  • Define `RestartSec` y `StartLimitBurst` para evitar bucles de reinicio que saturen CPU y journal
  • No corras servicios como root si no es imprescindible; usa `User=`/`Group=` dedicados
  • Añade `ProtectSystem=strict`, `PrivateTmp=yes` y `NoNewPrivileges=yes` como hardening por defecto
  • Usa rutas absolutas en `ExecStart`; systemd no hereda el `PATH` de tu shell interactiva
  • Prefiere timers de systemd sobre cron cuando necesites registro de ejecuciones o dependencias con otros servicios

Preguntas frecuentes

¿Por qué mi servicio sigue con la configuración antigua tras editar el archivo?

Porque falta `systemctl daemon-reload`. systemd carga las unidades en memoria al arrancar y sólo relee el archivo del disco cuando se lo pides explícitamente con ese comando; sin él, `restart` usa la definición cacheada.

¿Qué diferencia hay entre systemctl enable y systemctl start?

`start` arranca el servicio ahora mismo, en la sesión actual, pero no sobrevive a un reinicio del servidor. `enable` crea el symlink que lo activará en el próximo arranque, pero no lo inicia inmediatamente. `enable --now` combina ambos.

¿Cómo limito la memoria que puede usar un servicio?

Con `MemoryMax=` en la sección `[Service]` (por ejemplo `MemoryMax=512M`). Si el proceso supera ese límite, el kernel lo mata mediante el OOM killer del cgroup asociado a la unidad, y el evento queda registrado en journalctl.

¿Puedo ver qué unidades tardan más en arrancar el sistema?

Sí, `systemd-analyze blame` lista todas las unidades ordenadas por tiempo de inicialización, y `systemd-analyze critical-chain` muestra la cadena de dependencias que más retrasa el arranque completo.

¿Qué pasa si dos unidades declaran After= mutuamente?

systemd detecta la dependencia circular y lo registra como error en el log de arranque, resolviendo el ciclo de forma no determinista o dejando alguna unidad sin arrancar. Es un error de diseño de las unidades que hay que corregir eliminando una de las dos referencias.