--- title: "systemd en profundidad: units, targets y dependencias" description: "Crea servicios, targets, instancias, sockets y timers de systemd en Ubuntu 24.04, entiende Wants, Requires, After y PartOf, y depura units con journalctl." category: "Internos de Linux" language: es source: https://cubepath.com/es/docs/internos-de-linux/systemd-en-profundidad-units-targets-dependencias updated: 2026-09-25 --- # systemd en profundidad: units, targets y dependencias 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`, `stop` y `status`. ## 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`: ```bash systemctl cat ssh.service ``` ```output # /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á: ```bash sudo mkdir -p /srv/demo-web echo 'hola desde systemd' | sudo tee /srv/demo-web/index.html ``` Crea la unit: ```bash sudo nano /etc/systemd/system/demo-web.service ``` ```ini [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.target` solo ordena el arranque. - `[Service]` define el proceso. `Type=exec` da el servicio por iniciado cuando el binario se ha ejecutado correctamente. `DynamicUser=yes` crea un usuario temporal sin privilegios para el proceso, y las líneas `Protect*` hacen el sistema de ficheros de solo lectura para él. - `[Install]` solo se usa al ejecutar `enable`: crea un enlace para que `multi-user.target` arranque el servicio en cada inicio. Valida el fichero, recarga systemd y arranca el servicio: ```bash 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: ```bash systemctl status demo-web.service --no-pager curl http://127.0.0.1:8080/ ``` ```output ● 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: ```bash 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: ```ini [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: ```bash systemctl list-dependencies demo-web.service --no-pager systemctl list-dependencies --reverse demo-web.service --no-pager ``` ```output 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: ```bash sudo nano /etc/systemd/system/demo.target ``` ```ini [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: ```bash sudo systemctl edit demo-web.service ``` Escribe en el espacio indicado por el editor: ```ini [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: ```bash sudo systemctl enable --now demo.target sudo systemctl stop demo.target systemctl is-active demo-web.service ``` ```output inactive ``` Vuelve a arrancar todo con `sudo systemctl start demo.target`. Para consultar o cambiar el target con el que arranca el sistema: ```bash systemctl get-default ``` ```output 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 `nombre@texto.service`, systemd sustituye `%i` por `texto`. Es útil para varios trabajadores iguales con distinto parámetro. Crea la plantilla: ```bash sudo nano /etc/systemd/system/demo-worker@.service ``` ```ini [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: ```bash sudo systemctl daemon-reload sudo systemctl enable --now demo-worker@1.service demo-worker@2.service systemctl list-units 'demo-worker@*' --no-pager ``` ```output UNIT LOAD ACTIVE SUB DESCRIPTION demo-worker@1.service loaded active running Trabajador de demostración 1 demo-worker@2.service 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: ```bash sudo nano /etc/systemd/system/demo-eco.socket ``` ```ini [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: ```bash sudo nano /etc/systemd/system/demo-eco@.service ``` ```ini [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: ```bash sudo systemctl daemon-reload sudo systemctl enable --now demo-eco.socket echo 'hola eco' | nc -N 127.0.0.1 9999 ``` ```output hola eco ``` Comprueba el socket y cuántas conexiones ha aceptado: ```bash systemctl status demo-eco.socket --no-pager | grep -E 'Active|Listen|Accepted' ``` ```output 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: ```bash sudo nano /etc/systemd/system/demo-limpieza.service ``` ```ini [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: ```bash sudo nano /etc/systemd/system/demo-limpieza.timer ``` ```ini [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: ```bash systemd-analyze calendar '*-*-* 03:30:00' ``` ```output 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: ```bash 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: ```bash systemd-analyze systemd-analyze blame --no-pager | head -n 5 ``` ```output 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: ```bash systemd-analyze critical-chain demo.target ``` Para eliminar las units de prueba al terminar, deshabilítalas y borra sus ficheros: ```bash sudo systemctl disable --now demo.target demo-web.service 'demo-worker@1.service' 'demo-worker@2.service' 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.