systemd es el gestor de servicios y proceso de arranque (PID 1) de Ubuntu, Debian, Rocky Linux y la mayoría de distribuciones actuales. Todo lo que gestiona se describe como una unit: servicios, sockets, temporizadores, puntos de montaje y grupos de units llamados targets. En este tutorial construirás en Ubuntu 24.04 una pequeña aplicación compuesta por varias units para ver en la práctica cómo funcionan los tipos de unit, las dependencias, los targets, las instancias, la activación por socket y los timers.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS (vale igual en Debian 12 o Rocky Linux 9), por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudo. - Soltura básica con
systemctl start,stopystatus.
Conceptos: tipos de unit y dónde viven
Cada unit es un fichero de texto con formato INI cuya extensión indica el tipo:
| Tipo | Extensión | Para qué sirve |
|---|---|---|
| Service | .service | Ejecutar y vigilar un proceso |
| Socket | .socket | Escuchar en un socket y arrancar un servicio al recibir conexiones |
| Timer | .timer | Lanzar otra unit según un calendario o un intervalo |
| Target | .target | Agrupar units y marcar puntos de sincronización |
| Mount | .mount | Montar un sistema de ficheros |
| Path | .path | Activar una unit cuando cambia un fichero |
| Slice | .slice | Agrupar procesos en cgroups para repartir recursos |
systemd busca las units en varios directorios con esta prioridad (de mayor a menor):
/etc/systemd/system/: units y ajustes del administrador. Aquí trabajarás./run/systemd/system/: units creadas en tiempo de ejecución./usr/lib/systemd/system/: units que instalan los paquetes. No las edites, se sobrescriben al actualizar.
Para ver el fichero completo de una unit, incluidos los ficheros de ajuste que la modifican, usa systemctl cat:
systemctl cat ssh.service
# /usr/lib/systemd/system/ssh.service
[Unit]
Description=OpenBSD Secure Shell server
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target auditd.service
...
Paso 1: Crear un servicio
Vas a crear un servidor web mínimo con el módulo http.server de Python, que viene instalado en Ubuntu. Prepara el directorio que servirá:
sudo mkdir -p /srv/demo-web
echo 'hola desde systemd' | sudo tee /srv/demo-web/index.html
Crea la unit:
sudo nano /etc/systemd/system/demo-web.service
[Unit]
Description=Servidor web de demostración
After=network.target
[Service]
Type=exec
ExecStart=/usr/bin/python3 -m http.server 8080 --bind 127.0.0.1 --directory /srv/demo-web
DynamicUser=yes
Restart=on-failure
RestartSec=5s
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
Lo más importante de cada sección:
[Unit]describe la unit y sus relaciones con otras.After=network.targetsolo ordena el arranque.[Service]define el proceso.Type=execda el servicio por iniciado cuando el binario se ha ejecutado correctamente.DynamicUser=yescrea un usuario temporal sin privilegios para el proceso, y las líneasProtect*hacen el sistema de ficheros de solo lectura para él.[Install]solo se usa al ejecutarenable: crea un enlace para quemulti-user.targetarranque el servicio en cada inicio.
Valida el fichero, recarga systemd y arranca el servicio:
sudo systemd-analyze verify /etc/systemd/system/demo-web.service
sudo systemctl daemon-reload
sudo systemctl enable --now demo-web.service
systemd-analyze verify no imprime nada si la unit es correcta. Comprueba el estado y que responde:
systemctl status demo-web.service --no-pager
curl http://127.0.0.1:8080/
● demo-web.service - Servidor web de demostración
Loaded: loaded (/etc/systemd/system/demo-web.service; enabled; preset: enabled)
Active: active (running) since Thu 2026-09-25 10:20:11 UTC; 3s ago
Main PID: 2154 (python3)
hola desde systemd
La salida estándar del proceso va al journal. Consulta los accesos:
journalctl -u demo-web.service -n 5 --no-pager
Paso 2: Entender las dependencias
systemd separa dos ideas que es fácil confundir: la dependencia (qué otras units se activan junto a esta) y el orden (cuál arranca antes). Una directiva de dependencia sin After= o Before= arranca ambas units en paralelo.
| Directiva | Efecto |
|---|---|
Wants= | Activa también la otra unit; si falla, esta sigue adelante. Es la opción recomendada por defecto. |
Requires= | Activa la otra unit; si no arranca, esta tampoco. Si se detiene explícitamente, esta también. |
BindsTo= | Como Requires=, y además esta se detiene si la otra deja de estar activa por cualquier motivo. |
PartOf= | Solo propaga stop y restart desde la otra unit hacia esta. No activa nada. |
Conflicts= | Arrancar una detiene la otra. |
After= / Before= | Solo orden de arranque (y el inverso en la parada). |
Un ejemplo habitual: una aplicación que necesita PostgreSQL debe declarar las dos cosas, porque Requires= sin After= podría arrancar la aplicación antes de que la base de datos acepte conexiones:
[Unit]
Requires=postgresql.service
After=postgresql.service
Para ver el árbol de dependencias de una unit, y a la inversa, quién depende de ella:
systemctl list-dependencies demo-web.service --no-pager
systemctl list-dependencies --reverse demo-web.service --no-pager
demo-web.service
● └─multi-user.target
La salida inversa muestra que multi-user.target la arrastra, gracias al enlace que creó enable.
Paso 3: Agrupar servicios en un target
Un target es una unit sin proceso que agrupa otras. multi-user.target y graphical.target son los equivalentes modernos a los antiguos runlevels 3 y 5. Puedes crear tus propios targets para arrancar y parar una aplicación compuesta con un solo comando.
Crea un target para la aplicación de demostración:
sudo nano /etc/systemd/system/demo.target
[Unit]
Description=Aplicación de demostración completa
Wants=demo-web.service
After=demo-web.service
[Install]
WantedBy=multi-user.target
Añade a demo-web.service la directiva PartOf= para que parar o reiniciar el target se propague al servicio. En lugar de editar el fichero, usa un fichero de ajuste (drop-in), que es la forma correcta de modificar units, incluidas las de los paquetes:
sudo systemctl edit demo-web.service
Escribe en el espacio indicado por el editor:
[Unit]
PartOf=demo.target
systemctl edit guarda el cambio en /etc/systemd/system/demo-web.service.d/override.conf y recarga systemd. Habilita el target y prueba la propagación:
sudo systemctl enable --now demo.target
sudo systemctl stop demo.target
systemctl is-active demo-web.service
inactive
Vuelve a arrancar todo con sudo systemctl start demo.target. Para consultar o cambiar el target con el que arranca el sistema:
systemctl get-default
graphical.target
En servidores sin entorno gráfico es habitual fijarlo en multi-user.target con sudo systemctl set-default multi-user.target.
Paso 4: Lanzar varias instancias con una plantilla
Una unit cuyo nombre acaba en @ es una plantilla. Al arrancar [email protected], systemd sustituye %i por texto. Es útil para varios trabajadores iguales con distinto parámetro. Crea la plantilla:
sudo nano /etc/systemd/system/[email protected]
[Unit]
Description=Trabajador de demostración %i
PartOf=demo.target
[Service]
Type=exec
DynamicUser=yes
ExecStart=/bin/sh -c 'while true; do echo "trabajador %i activo"; sleep 30; done'
Restart=on-failure
[Install]
WantedBy=demo.target
Arranca dos instancias y compruébalas:
sudo systemctl daemon-reload
sudo systemctl enable --now [email protected] [email protected]
systemctl list-units 'demo-worker@*' --no-pager
UNIT LOAD ACTIVE SUB DESCRIPTION
[email protected] loaded active running Trabajador de demostración 1
[email protected] loaded active running Trabajador de demostración 2
Como WantedBy=demo.target, las instancias habilitadas arrancan y se detienen junto al target.
Paso 5: Activar un servicio por socket
Con la activación por socket, systemd abre el puerto y solo arranca el servicio cuando llega una conexión. Así el servicio no consume recursos mientras nadie lo usa y el puerto está disponible desde el primer momento del arranque. Crea un servidor de eco que devuelve lo que recibe. Primero el socket:
sudo nano /etc/systemd/system/demo-eco.socket
[Unit]
Description=Socket del servidor de eco
[Socket]
ListenStream=127.0.0.1:9999
Accept=yes
[Install]
WantedBy=sockets.target
Con Accept=yes, systemd arranca una instancia del servicio por cada conexión, así que el servicio debe ser una plantilla con el mismo nombre base:
sudo nano /etc/systemd/system/[email protected]
[Unit]
Description=Servidor de eco para una conexión
[Service]
ExecStart=/usr/bin/cat
StandardInput=socket
DynamicUser=yes
StandardInput=socket conecta la entrada y la salida del proceso a la conexión, así que cat devuelve al cliente todo lo que recibe. Habilita solo el socket:
sudo systemctl daemon-reload
sudo systemctl enable --now demo-eco.socket
echo 'hola eco' | nc -N 127.0.0.1 9999
hola eco
Comprueba el socket y cuántas conexiones ha aceptado:
systemctl status demo-eco.socket --no-pager | grep -E 'Active|Listen|Accepted'
Active: active (listening) since Thu 2026-09-25 10:31:40 UTC; 20s ago
Listen: 127.0.0.1:9999 (Stream)
Accepted: 1; Connected: 0;
Para un servicio de larga duración que gestione todas las conexiones él mismo (por ejemplo un servidor web con soporte de sd_listen_fds), usa Accept=no y un servicio normal con el mismo nombre que el socket.
Paso 6: Programar tareas con un timer
Los timers sustituyen a cron con ventajas: la salida queda en el journal, las ejecuciones perdidas mientras el equipo estaba apagado se recuperan con Persistent=true y puedes ver la próxima ejecución con un comando. Un timer siempre activa otra unit, normalmente un servicio oneshot. Crea el servicio:
sudo nano /etc/systemd/system/demo-limpieza.service
[Unit]
Description=Limpieza de ficheros temporales antiguos de la demo
[Service]
Type=oneshot
ExecStart=/usr/bin/find /var/tmp -maxdepth 1 -name 'demo-*' -mtime +7 -delete
Crea el timer con el mismo nombre base:
sudo nano /etc/systemd/system/demo-limpieza.timer
[Unit]
Description=Limpieza diaria de la demo
[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=15min
Persistent=true
[Install]
WantedBy=timers.target
Antes de activarlo, comprueba cómo interpreta systemd la expresión de calendario:
systemd-analyze calendar '*-*-* 03:30:00'
Original form: *-*-* 03:30:00
Normalized form: *-*-* 03:30:00
Next elapse: Fri 2026-09-26 03:30:00 UTC
From now: 17h left
Activa el timer y lanza el servicio una vez a mano para probarlo:
sudo systemctl daemon-reload
sudo systemctl enable --now demo-limpieza.timer
sudo systemctl start demo-limpieza.service
systemctl list-timers demo-limpieza.timer --no-pager
Otras expresiones útiles son hourly, daily, weekly, Mon..Fri *-*-* 08:00 o, para intervalos relativos, OnBootSec=5min combinado con OnUnitActiveSec=1h.
Paso 7: Analizar el arranque
systemd registra cuánto tarda cada unit en arrancar. Para ver el tiempo total y las units más lentas:
systemd-analyze
systemd-analyze blame --no-pager | head -n 5
Startup finished in 2.101s (kernel) + 6.842s (userspace) = 8.943s
graphical.target reached after 6.801s in userspace.
3.012s cloud-init.service
1.204s systemd-networkd-wait-online.service
...
blame suma tiempos que ocurren en paralelo, así que para saber qué retrasa de verdad el arranque usa la cadena crítica:
systemd-analyze critical-chain demo.target
Para eliminar las units de prueba al terminar, deshabilítalas y borra sus ficheros:
sudo systemctl disable --now demo.target demo-web.service '[email protected]' '[email protected]' demo-eco.socket demo-limpieza.timer
sudo rm -r /etc/systemd/system/demo* /srv/demo-web
sudo systemctl daemon-reload
Solución de problemas
Unit demo-web.service not found. Ejecuta sudo systemctl daemon-reload después de crear o editar una unit y comprueba el nombre con systemctl list-unit-files 'demo*'.
El servicio arranca y cae en bucle. Consulta los errores con journalctl -u demo-web.service -b --no-pager -n 50. Si systemd deja de reintentarlo con el mensaje Start request repeated too quickly, ha alcanzado el límite StartLimitBurst; corrige la causa y ejecuta sudo systemctl reset-failed demo-web.service.
Un cambio en la unit no se aplica. systemd trabaja con la copia cargada en memoria. Tras editar, ejecuta daemon-reload y reinicia el servicio. systemctl status avisa con Warning: The unit file ... changed on disk cuando falta la recarga.
DynamicUser=yes y el servicio no puede escribir. El usuario dinámico no tiene permisos en tu sistema de ficheros. Usa StateDirectory=nombre, que crea /var/lib/nombre con los permisos correctos, o ReadWritePaths= para una ruta concreta.
Conclusión
Has creado un servicio endurecido, un target que lo agrupa, una plantilla con varias instancias, un servicio activado por socket y un timer, y has visto la diferencia entre dependencias y orden de arranque. Como siguientes pasos, limita la CPU y la memoria de tus servicios con directivas como MemoryMax= y CPUQuota=, revisa la puntuación de seguridad de cada servicio con systemd-analyze security y migra tus tareas de cron a timers.
