Dominando systemd: servicios, logs y herramientas ctl

Systemd es el sistema de inicialización y gestor de servicios usado por la mayoría de distribuciones Linux modernas (Debian, Ubuntu, Fedora, RHEL, Arch). Además de arrancar el sistema, systemd centraliza el control de casi todos los aspectos de administración a través de un conjunto de herramientas con el sufijo ctl («control»):

  • Servicios (.service) — procesos que corren en segundo plano.
  • Timers (.timer) — el reemplazo moderno de cron.
  • Sockets (.socket) — activación de servicios bajo demanda al recibir conexiones.
  • Targets — agrupaciones de unidades, equivalentes a los «runlevels» de SysV.
  • Logs (journal) — el sistema centralizado de registro de eventos.
  • Sesiones (loginctl) — gestión de sesiones de usuario.
  • Fecha/hora (timedatectl), hostname (hostnamectl), locales (localectl), red (networkctl) y DNS (resolvectl).
  • Bootloader (bootctl) y contenedores (machinectl, systemd-nspawn).

Crear servicios systemd

Estructura de un servicio

Un servicio personalizado se define en un archivo .service, típicamente dentro de /etc/systemd/system/:

[Unit]
Description=Mi servicio de ejemplo
Documentation=https://example.com/docs
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=usuario
Group=usuario
WorkingDirectory=/opt/app
ExecStart=/usr/bin/python3 /opt/app/app.py
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
Environment="ENV=produccion"
EnvironmentFile=/etc/default/app

[Install]
WantedBy=multi-user.target

Cada sección cumple un propósito distinto:

  • [Unit] — metadatos y dependencias. After controla el orden de arranque (espera a que ocurra el evento indicado), mientras Wants expresa una dependencia flexible: si la red no está disponible, el servicio arranca igual, solo que después.
  • [Service] — cómo se ejecuta el proceso. Type=simple asume que el proceso principal no hace fork; ExecStart es el comando real; Restart=on-failure hace que systemd reinicie el servicio automáticamente si termina con un código de error (no en un cierre limpio); RestartSec es la pausa antes de reintentar.
  • [Install] — define en qué target se activa el servicio al hacer enable. multi-user.target es el equivalente al runlevel 3 clásico (sistema multiusuario, sin entorno gráfico).

Procedimiento universal

Flujo estándar para crear, activar y verificar cualquier servicio nuevo:

sudo nano /etc/systemd/system/mi-servicio.service
sudo systemctl daemon-reload
sudo systemctl start mi-servicio
systemctl status mi-servicio
sudo systemctl enable mi-servicio

daemon-reload es el paso que más se olvida: systemd cachea las definiciones de las unidades en memoria, así que cualquier cambio al archivo .service no se aplica hasta ejecutar este comando. enable crea el enlace simbólico que hace que el servicio arranque automáticamente en el próximo boot — start solo lo arranca ahora, una vez.

Logs con journalctl

El journal es el sistema de logs binario centralizado de systemd — reemplaza (o complementa) a los archivos de texto tradicionales en /var/log/.

journalctl -u mi-servicio       # logs de un servicio específico
journalctl -u mi-servicio -f    # seguimiento en vivo (equivalente a tail -f)
journalctl -u mi-servicio -p err # solo mensajes de nivel error o más grave
journalctl -u mi-servicio -b    # logs desde el arranque actual del sistema
journalctl -xe                  # últimas líneas con contexto extendido, útil tras un fallo

Comandos *ctl del ecosistema systemd

systemctl — control de unidades

La herramienta principal para gestionar el estado de servicios y otras unidades:

systemctl start nombre       # arrancar ahora
systemctl stop nombre        # detener ahora
systemctl restart nombre     # detener y volver a arrancar
systemctl reload nombre      # recargar configuración sin reiniciar el proceso
systemctl enable nombre      # activar en el próximo boot
systemctl disable nombre     # desactivar en el próximo boot
systemctl status nombre      # ver estado actual y últimos logs
systemctl list-units         # listar unidades cargadas actualmente
systemctl list-unit-files    # listar todas las unidades instaladas (activas o no)
systemctl daemon-reload      # releer definiciones de unidades tras editarlas
systemctl isolate multi-user.target  # cambiar de target activo (similar a cambiar runlevel)
systemctl --failed           # listar únicamente las unidades que fallaron

journalctl — consultas adicionales al journal

journalctl                    # todo el log disponible
journalctl -f                 # seguimiento en vivo de todo el sistema
journalctl -xe                # contexto extendido de los eventos más recientes
journalctl -p err             # filtrar por severidad (err, warning, crit, etc.)
journalctl -u nombre          # logs de una unidad específica
journalctl -b                 # logs solo del arranque actual
journalctl -k                 # solo mensajes del kernel (equivalente a dmesg)
journalctl --since "2026-01-01"  # filtrar por fecha

loginctl — sesiones de usuario

Gestiona sesiones activas, útil en servidores multiusuario o para depurar sesiones colgadas:

loginctl list-sessions           # ver todas las sesiones activas
loginctl session-status 3        # detalle de una sesión específica
loginctl terminate-session 3     # cerrar una sesión a la fuerza
loginctl list-users              # usuarios con sesión activa
loginctl user-status usuario     # detalle de sesiones de un usuario
loginctl enable-linger usuario   # permite que los servicios de usuario (systemd --user) sigan corriendo aunque cierre sesión

timedatectl — fecha, hora y NTP

timedatectl                                  # ver configuración actual
timedatectl set-time "2026-06-30 10:00:00"   # fijar hora manualmente
timedatectl set-timezone America/Guayaquil   # cambiar zona horaria
timedatectl set-ntp true                     # activar sincronización automática por NTP

hostnamectl — nombre del equipo

hostnamectl                      # ver hostname y datos del sistema (kernel, arquitectura, etc.)
hostnamectl set-hostname servidor  # cambiar el hostname de forma persistente

localectl — idioma y teclado

localectl                          # ver configuración de locale actual
localectl set-locale LANG=es_EC.UTF-8  # cambiar idioma del sistema
localectl set-keymap us            # cambiar distribución de teclado

networkctl — estado de interfaces de red

Solo aplica en sistemas que usan systemd-networkd como gestor de red (no todos — muchas distros usan NetworkManager o ifupdown en su lugar):

networkctl              # listar interfaces y su estado
networkctl status eth0  # detalle de una interfaz específica
networkctl list         # listado resumido
networkctl reload       # releer configuración de red

resolvectl — resolución DNS

resolvectl status              # ver servidores DNS configurados por interfaz
resolvectl query google.com    # hacer una consulta DNS de prueba
resolvectl flush-caches        # limpiar la caché de resolución DNS
resolvectl dns eth0 8.8.8.8    # fijar un DNS específico para una interfaz

machinectl — contenedores y máquinas virtuales ligeras

Gestiona contenedores creados con systemd-nspawn:

machinectl list          # ver contenedores registrados
machinectl start debian  # arrancar un contenedor
machinectl stop debian   # detenerlo
machinectl shell debian  # abrir una shell dentro del contenedor

bootctl — gestor de arranque (systemd-boot)

bootctl status   # ver estado del bootloader
bootctl install  # instalar systemd-boot en la partición EFI
bootctl update   # actualizar los binarios del bootloader

systemd-analyze — rendimiento de arranque

Herramienta de diagnóstico para entender qué tarda en el proceso de boot:

systemd-analyze                  # tiempo total de arranque, desglosado por fase
systemd-analyze blame            # qué servicios tardaron más en iniciar
systemd-analyze critical-chain   # cadena de dependencias que determinó el tiempo total
systemd-analyze plot > boot.svg  # generar un gráfico visual del arranque

systemd-run — ejecutar comandos como unidades temporales

Permite lanzar cualquier comando como si fuera un servicio administrado por systemd, sin crear un archivo .service permanente — útil para pruebas o tareas puntuales que necesitan aislamiento de recursos:

systemd-run --unit=mi-tarea echo "hola"
systemd-run --scope sleep 10

systemd-cgls y systemd-cgtop — jerarquía de cgroups

systemd-cgls    # árbol de cgroups activos (qué proceso pertenece a qué unidad)
systemd-cgtop   # uso de recursos en tiempo real, agrupado por cgroup

systemd-escape — codificar nombres para unidades

Convierte rutas o cadenas con caracteres especiales al formato seguro que systemd usa internamente para nombrar unidades:

systemd-escape "ruta rara"
systemd-escape --path "/home/usuario/archivo"

systemd-inhibit — evitar suspensión/apagado durante una tarea

systemd-inhibit sleep 30
systemd-inhibit --why="Proceso crítico" comando

systemd-notify — comunicación entre el proceso y systemd

Usado dentro de scripts o servicios Type=notify para avisarle a systemd que el proceso ya está listo, o para reportar su estado actual:

systemd-notify --ready
systemd-notify --status="Inicializando..."

systemd-sysusers — creación declarativa de usuarios del sistema

systemd-sysusers
systemd-sysusers /etc/sysusers.d/archivo.conf

systemd-tmpfiles — gestión de archivos y directorios temporales

systemd-tmpfiles --create   # crear archivos/directorios definidos en configuración
systemd-tmpfiles --clean    # limpiar archivos temporales expirados
systemd-tmpfiles --remove   # eliminar los definidos en configuración

systemd-nspawn — contenedores ligeros

systemd-nspawn -D /var/lib/machines/debian
systemd-nspawn -M mi-maquina

Timers systemd

Los timers son la alternativa moderna a cron, con la ventaja de integrarse con el journal (logs unificados) y con dependencias de otras unidades. Un timer siempre va acompañado de un servicio con el mismo nombre base.

Timer (backup.timer):

[Unit]
Description=Backup diario

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

Persistent=true es clave: si el equipo estuvo apagado cuando debía ejecutarse el timer, lo corre en cuanto vuelve a encender, en vez de esperar al siguiente ciclo — evita perder ejecuciones por reinicios o cortes de energía.

Servicio asociado (backup.service):

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh

Type=oneshot le indica a systemd que el proceso termina por sí solo tras completar su tarea (no es un servicio de larga duración) — es el tipo correcto para scripts que corren, hacen su trabajo, y salen.

Activación:

systemctl enable --now backup.timer
systemctl list-timers

Depuración avanzada

Secuencia típica para diagnosticar un servicio que no arranca o falla:

systemctl status mi-servicio                          # estado y últimas líneas de log
journalctl -u mi-servicio -p err -b                    # solo errores, desde el último boot
journalctl -u mi-servicio -n 20                        # últimas 20 líneas de log
journalctl _PID=$(pidof mi-servicio)                   # logs filtrados por PID específico
systemctl list-dependencies mi-servicio                # árbol de dependencias
systemd-analyze verify /etc/systemd/system/mi-servicio.service  # validar sintaxis del archivo
sudo -u usuario /usr/bin/python3 /opt/app/app.py        # ejecutar manualmente como el mismo usuario, para ver el error real
ls -l /opt/app/app.py                                   # confirmar que el archivo existe y tiene permisos correctos
ls -l /usr/bin/python3                                  # confirmar que el binario existe en la ruta esperada
cat /etc/default/app                                    # revisar variables de entorno cargadas
systemctl daemon-reload                                 # por si el problema es una definición desactualizada

Interpretación completa de systemctl status

Ejemplo de salida real:

○ rory-indexer.service - Rory Indexer RAG
     Loaded: loaded (/etc/systemd/system/rory-indexer.service; static)
     Active: inactive (dead) since Tue 2026-06-30 03:24:53 -05; 5h 29min ago
TriggeredBy: ● rory-indexer.timer
    Process: 2967487 ExecStart=/opt/rory-indexer/bin/python3 /opt/rory-indexer/scripts/indexer_v2.py (code=exited, status=0/SUCCESS)
   Main PID: 2967487 (code=exited, status=0/SUCCESS)
        CPU: 1min 18.622s

Primera línea

Muestra el símbolo de estado visual (● activo, ○ inactivo, ✗ falló, ▲ iniciando), el nombre técnico del servicio y su descripción legible.

Loaded

Indica si el archivo .service está cargado, su ruta completa, y el estado de instalación: enabled (arranca en boot), disabled (no arranca en boot pero se puede iniciar manualmente), static (no tiene sección [Install], solo se activa como dependencia de otra unidad), o masked (bloqueado explícitamente, no se puede iniciar ni siquiera manualmente).

Active

El estado real de ejecución: active (running), inactive (dead), failed, activating o deactivating (transiciones en curso), y reloading.

TriggeredBy

Indica qué otra unidad activó este servicio — típicamente un Timer, un Socket, o un Path (activación al detectar cambios en un archivo/directorio).

Process

Detalle del último proceso ejecutado: PID, comando exacto que se corrió, y el código de salida (exit code) — clave para saber si terminó bien (status=0/SUCCESS) o con error.

Main PID

El proceso principal identificado por el servicio, el que systemd supervisa para determinar si sigue vivo.

CPU

Tiempo total de CPU consumido por el servicio desde que arrancó.

Logs

Las últimas líneas del journal relacionadas con esta unidad, mostradas automáticamente al final del status.

Buenas prácticas

  • Usar siempre rutas absolutas en ExecStart — systemd no hereda el $PATH de una shell de usuario.
  • Restart=on-failure como valor por defecto razonable, para que el servicio se recupere solo ante caídas inesperadas.
  • After=network-online.target si el servicio depende de conectividad de red al arrancar (evita condiciones de carrera con la red, como las que resuelven un resolv.conf vacío en contenedores Docker al bootear antes que la red).
  • EnvironmentFile= para mantener configuraciones y secretos fuera del archivo .service en sí, en un archivo separado con permisos restringidos.
  • Monitorear en vivo con journalctl -u servicio -f al depurar o al desplegar cambios.
  • Nunca olvidar systemctl daemon-reload después de editar cualquier archivo .service o .timer.