Guias

systemd: guía práctica para crear y asegurar servicios en Linux en 2026

systemd: guía práctica para crear y asegurar servicios en Linux en 2026

systemd gestiona el arranque y los servicios de la mayoría de distribuciones Linux. Esta guía cubre desde el primer unit file hasta el endurecimiento de seguridad y las consultas avanzadas al journal. Probada en Debian 12/13, Ubuntu 24.04 LTS y RHEL 9/10.

—

TL;DR — Lo que vas a tener al final

  • Tiempo de setup: 30 minutos a 2 horas dependiendo de la complejidad del entorno.
  • Requisitos: Acceso root, Linux moderno, conexión a internet para descargar dependencias.
  • Resultado: Servicio funcional y estable, documentado paso a paso.
  • Troubleshooting: Logs estándar en /var/log/, comandos de verificación al final de cada sección.

1. Qué es systemd y por qué seguir usándolo

systemd es el sistema de inicio (PID 1) y gestor de servicios presente en Debian, Ubuntu, Fedora, RHEL, Arch, openSUSE y prácticamente toda distribución moderna. La rama estable actual es v261 (publicada en 2025; mantenimiento estable en v261.x en 2026).

No es solo un sustituto de SysVinit: aporta registro centralizado (journald), dependencias declarativas, activación por socket, por temporizador y por D-Bus, y un modelo de unidades (units) extensible.

Tipos de unit que vas a tocar:

Sufijo Para qué sirve
.service Servicios de usuario o demonios
.timer Equivalente a cron, gestionado por systemd
.mount Puntos de montaje
.socket Activación por socket (inetd-style)
.path Activación por cambios en el filesystem

—

2. Anatomía de un unit file

Un unit se compone de secciones ([Unit], [Service], [Install]) y directivas clave=valor. Ejemplo mínimo viable para una API Node:


# /etc/systemd/system/mi-api.service
[Unit]
Description=Mi API en Node
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=apiuser
WorkingDirectory=/opt/mi-api
ExecStart=/usr/bin/node /opt/mi-api/server.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target

Pasos para activarlo:

El orden de los cuatro comandos no es accidental: daemon-reload registra la unidad en systemd (sin esto, los cambios en el archivo no se ven hasta el próximo reinicio), enable crea el symlink en /etc/systemd/system/multi-user.target.wants/ para que el servicio arranque en boot, start lo lanza ahora, y status confirma el estado. Correrlos en otro orden suele terminar en errores tipo Unit not found o servicios que no quedan persistentes tras un reboot.


sudo systemctl daemon-reload          # relee la unidad tras editar
sudo systemctl enable mi-api.service   # habilita en el arranque
sudo systemctl start mi-api.service    # arranca ahora
sudo systemctl status mi-api.service  # comprueba estado

Si status muestra inactive (dead) justo después de start, el binario de ExecStart falló al arrancar (permisos, ruta inexistente o Type mal elegido). Saltá al journal con journalctl -u mi-api.service -n 50 --no-pager para ver el error exacto. Si enable se queja de symlink already exists, el servicio ya estaba habilitado de un intento previo y puedes saltearte ese paso.

Type=simple es el valor por defecto: systemd considera que el proceso está vivo en cuanto se lanza. Para procesos que se bifurcan (fork), usa Type=forking; para scripts que escriben el PID en un archivo, Type=oneshot con RemainAfterExit=yes.

—

3. Las directivas [Service] que usarás a diario

.target Agrupación de units (runlevel moderno)
Directiva Efecto práctico
ExecStart= Comando principal. Obligatoria en services.
ExecReload= Comando para recargar configuración (SIGHUP-friendly).
ExecStop= Comando para parar limpiamente.
Restart= no, on-success, on-failure, on-abnormal, always.
RestartSec= Segundos a esperar antes de relanzar.
User= / Group= Ejecuta como usuario sin privilegios.
EnvironmentFile= Carga variables de entorno desde un archivo.
WorkingDirectory= cwd del proceso.

Ejemplo completo con EnvironmentFile y Restart:


[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/var/lib/app
EnvironmentFile=/etc/app/app.env
ExecStart=/usr/local/bin/app --config /etc/app/app.yml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
TimeoutStopSec=30

—

4. Logs con journalctl: la herramienta que sustituye a tail

El binario journald captura stdout/stderr y mensajes syslog de todos los servicios. Para consultarlo:

Estos seis flags cubren el 90% de las consultas al journal: -u filtra por unidad (clave cuando tienes varios servicios), -f sigue en vivo (reemplazo directo de tail -f), --since permite ventanas temporales con strings humanos, -p err descarta ruido informativo, -b arranca desde el último boot, y --list-boots te deja saltar a arranques anteriores para investigar fallos que ya no están en memoria.


journalctl -u mi-api.service              # solo este servicio
journalctl -u mi-api.service -f           # en vivo, como tail -f
journalctl -u mi-api.service --since "1 hour ago"
journalctl -u mi-api.service -p err       # solo errores y críticos
journalctl -b                            # desde el último arranque
journalctl --list-boots                  # ver todos los arranques
journalctl --since "2026-08-01" --until "2026-08-03"

Si -u mi-api.service devuelve No entries, el servicio probablemente nunca arrancó: verifica con systemctl status mi-api.service y revisa que el nombre de la unidad coincida exactamente (incluyendo sufijo .service). Si --since "1 hour ago" no encuentra nada pero sabes que hay logs, el reloj del sistema puede estar desincronizado — timedatectl te lo confirma. Si el log se corta a mitad de línea, agregá --no-pager o pipeá a less.

Trucos útiles:

  • -o json o -o json-pretty para parsear logs con jq.
  • _PID=1234 para filtrar por PID.
  • journalctl -u a.service -u b.service sigue varios servicios a la vez.
  • Por defecto, los logs no sobreviven a un reinicio. Para hacerlos persistentes:

Crear /var/log/journal antes de reiniciar systemd-journald es clave: sin ese directorio, el journal no tiene dónde escribir logs persistentes y los datos de boot se siguen perdiendo. restart re-lee la config y empieza a volcar al disco, pero los logs anteriores a este punto no se recuperan.


sudo mkdir -p /var/log/journal
sudo systemctl restart systemd-journald

Si mkdir falla por permisos, no estás en una shell con sudo; corré sudo -i primero para tener un shell limpio con PATH correcto. Si después de reiniciar el journal los logs siguen sin persistir tras un reboot, revisa que /var/log/journal no esté en una partición que se monte con noexec o nosuid — eso puede romper el journal. verifica con ls -ld /var/log/journal que systemd-journald sea dueño del directorio (uid/gid root:root con permisos 2755).

Atención: en sistemas con journal persistente, los logs pueden crecer sin control. Limita su tamaño con SystemMaxUse= en /etc/systemd/journald.conf.

—

5. Timers: cron moderno y observabilidad gratis

Los .timer sustituyen a cron y se benefician del journal. Ejemplo: backup diario a las 03:30.


# /etc/systemd/system/backup.timer
[Unit]
Description=Backup diario a las 03:30
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
Unit=backup.service
[Install]
WantedBy=timers.target

# /etc/systemd/system/backup.service
[Unit]
Description=Backup a S3
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh

Activación del timer: daemon-reload registra el .timer en systemd (sin esto, los archivos nuevos no existen para el sistema), enable --now crea el symlink en timers.target.wants/ y arranca el timer en el mismo paso (atajo para no tener que correr enable y start por separado), y list-timers --all te muestra cuándo va a disparar la próxima vez — clave para confirmar que el OnCalendar= quedó bien parseado.


sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers --all    # ver próxima ejecución

Si enable --now falla con Failed to enable unit: Unit backup.timer is masked, hay otro .timer con el mismo nombre activo o uno quedó mask-eado por una sesión previa: corré sudo systemctl unmask backup.timer antes. Si list-timers --all no muestra tu timer tras activarlo, te falta el daemon-reload (systemd cachea las unidades hasta que recargás). Si OnCalendar=--* 03:30:00 no dispara a la hora esperada, revisa la zona horaria con timedatectl — systemd usa localtime del sistema.

Persistent=true ejecuta la unidad perdida si el equipo estuvo apagado a la hora programada. Equivale a anacron y funciona en servidores que no están siempre encendidos.

—

6. Drop-ins: modificar units sin tocar el archivo original

Los drop-ins permiten sobreescribir o añadir directivas en archivos gestionados por paquetes. El sistema busca *.conf en /etc/systemd/system/.d/ y /usr/lib/systemd/system/.d/.

Caso típico: añadir variables de entorno a un servicio del sistema:

systemctl edit abre el editor por defecto ($EDITOR o vi/nano) con un esqueleto vacío bajo # systemctl edit nginx.service y guarda el drop-in en /etc/systemd/system/nginx.service.d/override.conf. Cualquier directiva que pongas ahí se mergea con el unit original sin tocarlo, así que las actualizaciones de paquetes no pisan tus cambios.


sudo systemctl edit nginx.service

Si edit no abre el editor, no tienes EDITOR ni VISUAL definido: exportá EDITOR=vim (o nano) antes de invocar. Si al guardar se queja de Failed to prepare temporary file, el directorio /etc/systemd/system/.d/ no tiene permisos para tu usuario — usa sudoedit en su lugar o creá el directorio a mano con sudo mkdir -p y sudo chmod 755. Si tus directivas no parecen aplicarse, recuerda que necesitas daemon-reload después de editar.

Esto abre un editor con un esqueleto y guarda el drop-in en /etc/systemd/system/nginx.service.d/override.conf. Para sobreescribir directivas que estaban antes (no añadir), usa:


[Service]
Environment="HTTPS=on"

Y luego sudo systemctl daemon-reload && sudo systemctl restart nginx.service.

Para revertir un edit, simplemente sudo systemctl revert nginx.service.

—

7. Errores frecuentes y cómo depurarlos

StandardOutput= / StandardError= Redirige a journal, syslog, file: o null.
Síntoma Causa probable
systemctl status muestra inactive (dead) El servicio ejecutó y salió. Revisa ExecStart.
failed with result 'exit-code' Script mal llamado, error en el binario.
failed with result 'signal' El proceso murió por SIGSEGV/SIGKILL.
203/EXEC Archivo ExecStart no es ejecutable o no existe.
status=n/a repetido Type=simple pero el proceso hace fork. Usa forking.

Depuración rápida:

Este trío es el flujo mínimo de diagnóstico cuando un service no arranca: restart fuerza un intento limpio desde el estado actual (sin recargar unit files), journalctl -n 50 muestra las últimas 50 líneas sin paginar (clave para copiar el error y buscarlo), y systemctl show -p filtra las propiedades efectivas del unit — útil para confirmar qué directivas se aplicaron tras un drop-in.


sudo systemctl restart mi-api.service
journalctl -u mi-api.service -n 50 --no-pager
systemctl show mi-api.service -p ExecStart,ExecStop,Restart

Si restart falla con Job for mi-api.service failed because the control process exited, hay un error de sintaxis en el .service o el binario de ExecStart no es ejecutable — corré systemd-analyze verify /etc/systemd/system/mi-api.service para que apunte exactamente a la línea problemática. Si journalctl no muestra nada, el servicio puede tener StandardOutput=null o SyslogIdentifier mal seteado: revisa con systemctl cat. Si show -p ExecStart devuelve vacío, te falta el daemon-reload después de la última edición.

—

8. Hardening de units en 2026

systemd tiene directivas de seguridad que aplican seccomp, cgroups, namespaces y capabilities sin necesidad de AppArmor o SELinux. Para servicios caseros y SaaS pequeños, esto es generalmente suficiente.


[Service]
# Limita el sistema de archivos
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectHostname=true
ProtectClock=true
ProtectProc=invisible
# Limita la red
PrivateNetwork=false   # true si el servicio no necesita red
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
# Capabilities mínimas
CapabilityBoundingSet=
AmbientCapabilities=
NoNewPrivileges=true
# Aislamiento de proceso
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
# Recursos
LimitNOFILE=65536
CPUQuota=75%
MemoryMax=512M

systemd-analyze ayuda a verificar y analizar:

systemd-analyze security aplica un puntaje heurístico a las directivas del unit: cada Protect, Restrict, Private* y CapabilityBoundingSet= suman o restan puntos según el riesgo que cubren. El output agrupa los checks en categorías (Namespace, Capabilities, Network, Filesystem, etc.) y lista qué directivas faltan para subir el puntaje.


systemd-analyze security mi-api.service    # imprime puntaje y sugerencias
systemd-analyze verify /etc/systemd/system/mi-api.service

Si security devuelve OVERALL EXPOSURE LEVEL: 9.5 UNSAFE, no entres en pánico: muchas directivas de hardening no aplican a tu caso (ej: PrivateNetwork=true rompe servicios que escuchan en puerto). Lo importante es leer cada sugerencia y evaluar si tiene sentido para tu service, no perseguir un número bajo. Si verify reporta Unknown lvalue 'ProtectFoo' o similar, tienes una versión vieja de systemd que no soporta la directiva — revisa con systemctl --version y subí el paquete si puedes.

El puntaje de systemd-analyze security no es un benchmark inmutable: las recomendaciones se ajustan con cada versión. Lo importante es usar las directivas correctas y no acumular servicios con RootDirectory=/ sin aislar.

Notas prácticas:

  • Si tu servicio necesita escribir logs, monta un %t o /var/log con ReadWritePaths=/var/log/mi-api.
  • NoNewPrivileges=true rompe setuid/setgid binarios. Solo quítalo si tu servicio lo requiere.
  • MemoryDenyWriteExecute=true puede romper binarios que usan JIT (Java, Node con V8 moderno). Pruébalo antes de dejarlo fijo.

—

9. Plantilla lista para copiar y pegar


# /etc/systemd/system/mi-app.service
[Unit]
Description=Mi aplicación
Documentation=https://docs.example.com
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/app
EnvironmentFile=/etc/app/app.env
ExecStart=/usr/local/bin/app --config /etc/app/app.yml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
# Logs
StandardOutput=journal
StandardError=journal
SyslogIdentifier=mi-app
# Aislamiento
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
PrivateDevices=true
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
ReadWritePaths=/var/lib/app /var/log/app
[Install]
WantedBy=multi-user.target

—

10. Conclusión

systemd sigue siendo el sistema de inicio estándar de Linux en 2026. Cubre desde el caso más simple (ExecStart=/bin/true) hasta un servicio endurecido con seccomp, cgroups y namespaces. Para el día a día, acostúmbrate a tres comandos: systemctl status, journalctl -u y systemd-analyze security. El primero te dice qué pasó, el segundo te da contexto, el tercero te dice qué te falta por asegurar.

—

Preguntas frecuentes

¿Por qué systemd: guía práctica para crear y asegurar servicios en Linux en 2026 es relevante para sysadmins Linux?

Forma parte del flujo diario de administración. Los conceptos explicados se aplican directamente a servidores en producción y resuelven problemas reales que enfrentan sysadmins hispanohablantes.

¿Qué necesito para empezar con systemd: guía práctica para crear?

Distribución Linux actualizada (Debian, Ubuntu, RHEL, Fedora o Arch), acceso root, y conexión a internet. Para producción: entorno de staging separado antes de aplicar cambios.

¿Cuáles son los errores más comunes con systemd: guía práctica para crear?

Los tres más frecuentes: (1) saltar la lectura de logs, (2) aplicar cambios directos en producción sin probar, (3) olvidar permisos y ownership. La documentación y un plan de rollback siempre.

¿Cuándo usar systemd: guía práctica para crear y cuándo una alternativa?

Esta solución es ideal para el caso descrito. Alternativas como Docker, Ansible o configuración manual pueden ser mejores según contexto: portabilidad, escala y reproducibilidad son los factores clave.

¿Cómo migro desde una configuración previa a systemd: guía práctica para crear?

Documentar el estado actual, probar la nueva configuración en staging, validar funcionalmente, y aplicar en producción con rollback plan listo. Los posts de SoyLinux incluyen troubleshooting para los problemas más comunes.

Lee también

Alfonso Moreno | Sysadmin Senior

Soy Alfonso, sysadmin senior con RHCE y más de 10 años administrando sistemas Linux para empresas y proyectos personales. Me especializo en infraestructura, redes, virtualización, monitoring, automation y self-hosting. Sobre SoyLinux.com SoyLinux.com es un blog técnico en español sobre Linux, software libre y self-hosting. Lo mantengo en mi tiempo libre. Áreas de expertise - Linux server administration: RHEL, CentOS, Rocky, Alma, Fedora, Debian, Ubuntu - Network services: Postfix, BIND, Samba, NFS, Squid - Virtualización y containers: KVM, Proxmox, Docker, Podman - Monitoring: Prometheus, Grafana, Zabbix - Automation: Ansible, Bash scripting - Self-hosting: Vaultwarden, Nextcloud, Jellyfin, Immich, Homelab Contacto - Email: [email protected]

One thought on “systemd: guía práctica para crear y asegurar servicios en Linux en 2026”

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

address already in use El puerto está ocupado. Comprueba con ss -tlnp.