Módulo 6 · 6.3
Servicios propios y temporizadores
Módulo 6: Servicios y registros · Tiempo de lectura: 11 minutos
Objetivo de aprendizaje: Después de esta lección, podrá ejecutar su propio programa como servicio systemd con un usuario propio y algunas opciones de seguridad, y planificar tareas con un temporizador systemd o con cron.
Conceptos clave: archivo de unit, [Unit], [Service], [Install], ExecStart, User=, WorkingDirectory, EnvironmentFile, Restart=, WantedBy, NoNewPrivileges, ProtectSystem, ProtectHome, PrivateTmp, systemd-analyze security, temporizador, OnCalendar, Persistent, crontab
¿Por qué no simplemente "nohup" o "screen"?
Quien instala un programa propio en un servidor (una pequeña aplicación web, un bot, un script que hace algo cada noche) a menudo lo inicia a mano la primera vez: node server.js &, o en una sesión de screen. Eso funciona hasta que usted cierra la sesión, el servidor se reinicia o el programa se bloquea. Entonces desaparece, y nadie se da cuenta.
Un programa que debe funcionar siempre tiene que ser un servicio systemd. Así:
- se inicia solo al arrancar el sistema;
- systemd lo vuelve a iniciar si se bloquea;
- su salida va al journal (lección 6.2);
- se ejecuta con el usuario que usted elija, con los permisos que usted permita.
Un archivo de unit propio
Supongamos que tiene una pequeña aplicación Node.js en /opt/facturacion, que escucha en 127.0.0.1:3000. Más adelante, nginx o Caddy la publicarán hacia fuera (módulo 7).
Paso 1: un usuario propio sin shell (lección 3.2). La aplicación no necesita ser root ni iniciar sesión:
anna@web1:~$ sudo adduser --system --group --home /opt/facturacion --no-create-home facturacion
anna@web1:~$ sudo chown -R facturacion:facturacion /opt/facturacion/dataEl código en sí puede seguir perteneciendo a root (la aplicación no necesita modificarse a sí misma); solo la carpeta en la que escribe pertenece al usuario del servicio.
Paso 2: el archivo de unit. Las units propias se colocan en /etc/systemd/system/:
# /etc/systemd/system/facturacion.service
[Unit]
Description=Facturacion (Node.js)
After=network-online.target
Wants=network-online.target
[Service]
User=facturacion
Group=facturacion
WorkingDirectory=/opt/facturacion
EnvironmentFile=/etc/facturacion/env
ExecStart=/usr/bin/node /opt/facturacion/server.js
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/opt/facturacion/data
[Install]
WantedBy=multi-user.targetLos tres bloques:
- [Unit]: descripción y orden.
After=network-online.targetjunto conWants=hace que el servicio no se inicie hasta que la red esté disponible. - [Service]: cómo se ejecuta el programa.
ExecStartcon la ruta completa (systemd no usa una ruta de búsqueda como su shell).User=yGroup=determinan con qué usuario.EnvironmentFilelee ajustes como una contraseña de base de datos desde un archivo aparte, para que no estén en la unit. - [Install]: lo que hace
enable.WantedBy=multi-user.targetsignifica: iniciar junto con un arranque normal.
Paso 3: los secretos aparte, con permisos estrictos.
anna@web1:~$ sudo mkdir /etc/facturacion
anna@web1:~$ sudo install -m 600 -o root -g root /dev/null /etc/facturacion/env
anna@web1:~$ sudoedit /etc/facturacion/envContenido, por ejemplo, DATABASE_URL=postgres://facturas:...@127.0.0.1/facturas. systemd lee el archivo como root antes de cambiar al usuario del servicio, así que 600 de root es suficiente.
Paso 4: cargar, iniciar y comprobar.
anna@web1:~$ sudo systemctl daemon-reload
anna@web1:~$ sudo systemctl enable --now facturacion
anna@web1:~$ systemctl status facturacion
anna@web1:~$ journalctl -u facturacion -fOpciones de seguridad: poco esfuerzo, gran diferencia
Las cuatro líneas bajo RestartSec en el ejemplo limitan lo que puede hacer el servicio, también si alguien encuentra una vulnerabilidad en su aplicación:
| Opción | Efecto |
|---|---|
NoNewPrivileges=true | el proceso y sus hijos nunca pueden obtener más permisos (tampoco mediante sudo o programas setuid) |
ProtectSystem=strict | todo el sistema de archivos es de solo lectura para el servicio, salvo lo que usted permita con ReadWritePaths= |
ProtectHome=true | /home, /root y /run/user son invisibles |
PrivateTmp=true | un /tmp propio y vacío que otros programas no ven |
systemd puede evaluar su unit:
anna@web1:~$ systemd-analyze security facturacion
...
→ Overall exposure level for facturacion.service: 4.9 OK 🙂Cuanto más bajo sea el número, mejor. Un servicio sin opciones obtiene alrededor de 9 ("UNSAFE"). No hace falta llegar a cero: cada opción que añade puede romper algo que la aplicación necesita. Añádalas una por una y pruebe después de cada paso. Si algo deja de funcionar, normalmente aparece en el journal ("Read-only file system", es decir, sistema de archivos de solo lectura, o "Permission denied", permiso denegado).
Planificar tareas: temporizadores
Para algo que debe ocurrir a horas fijas (una copia de seguridad, un script de limpieza, un informe) necesita un temporizador (timer). Un temporizador systemd consta de dos units con el mismo nombre: un .service que indica qué debe hacerse y un .timer que indica cuándo.
# /etc/systemd/system/limpieza.service
[Unit]
Description=Limpiar archivos de exportación antiguos
[Service]
Type=oneshot
User=facturacion
ExecStart=/usr/bin/find /opt/facturacion/data/export -type f -mtime +30 -delete# /etc/systemd/system/limpieza.timer
[Unit]
Description=Limpiar a diario los archivos de exportación antiguos
[Timer]
OnCalendar=*-*-* 03:15:00
RandomizedDelaySec=10m
Persistent=true
[Install]
WantedBy=timers.targetType=oneshot significa: el programa se ejecuta y después termina. OnCalendar es la programación: todos los días a las 03:15. También puede escribir daily, weekly o Mon *-*-* 02:00; systemd-analyze calendar "Mon *-*-* 02:00" muestra cuándo será la próxima vez. Persistent=true es importante: si el servidor estaba apagado a las 03:15, la tarea se ejecuta igualmente nada más arrancar. RandomizedDelaySec reparte un poco el momento de inicio.
Se activa el temporizador, no el servicio:
anna@web1:~$ sudo systemctl daemon-reload
anna@web1:~$ sudo systemctl enable --now limpieza.timer
anna@web1:~$ systemctl list-timers limpieza
NEXT LEFT LAST PASSED UNIT ACTIVATES
Tue 2026-11-10 03:19:42 CET 15h left - - limpieza.timer limpieza.service
anna@web1:~$ sudo systemctl start limpieza.service # probar ahora una vez manualmente
anna@web1:~$ journalctl -u limpieza.service¿Y cron?
cron es la herramienta más antigua y sencilla, y sigue funcionando perfectamente. Una línea en el crontab de un usuario (crontab -e):
15 3 * * * /usr/local/bin/limpieza.shLos cinco campos son minuto, hora, día del mes, mes y día de la semana. Ventajas e inconvenientes, uno junto a otro:
| cron | temporizador systemd | |
|---|---|---|
| Configuración | una línea | dos archivos |
| Ejecución perdida (servidor apagado) | se omite | con Persistent=true se recupera |
| Salida y errores | por correo, si el servidor puede enviar correo; si no, se pierden | en el journal |
| Opciones de seguridad, usuario | a través del propio script | todo lo de systemd (véase más arriba) |
| Ver cuándo se ejecutó | difícil | systemctl list-timers y el journal |
Para un servidor que usted administra en serio, los temporizadores son la mejor opción, sobre todo para las copias de seguridad (lección 8.2): usted quiere tener la certeza de que se ejecutaron y de lo que dijeron. Una línea de cron que falla en silencio solo se descubre cuando necesita la copia de seguridad.
En AlmaLinux y Rocky Linux
Las units y los temporizadores funcionan exactamente igual. Una diferencia: SELinux puede bloquear un servicio propio que lee o escribe archivos en un lugar que SELinux no espera, o que escucha en un puerto poco habitual. El aviso aparece en /var/log/audit/audit.log. No desactive SELinux para solucionarlo; busque la etiqueta o la regla correcta (por ejemplo, con semanage).
Resumen
- Un programa que debe funcionar siempre tiene que ser un servicio systemd: se inicia solo, se reinicia tras un bloqueo y sus registros van al journal.
- Las units propias van en
/etc/systemd/system/, conUser=un usuario propio sin shell y los secretos en unEnvironmentFilecon permisos 600. NoNewPrivileges,ProtectSystem=strictconReadWritePaths,ProtectHomeyPrivateTmplimitan los daños en caso de vulnerabilidad;systemd-analyze securityevalúa la unit.- Un temporizador es un
.servicemás un.timerconOnCalendar;Persistent=truerecupera las ejecuciones perdidas. Active el temporizador, no el servicio. - cron sigue funcionando, pero los temporizadores ofrecen mejores registros y no pierden ejecuciones.
Cuestionario
Ejercicio
Ejercicio O6.3: Un servicio propio con temporizador. Cree en su servidor un pequeño servicio propio con temporizador, por ejemplo un script que cada noche escriba el espacio libre en disco y systemctl --failed en un archivo o en el journal. Use un usuario propio, Type=oneshot, al menos dos opciones de seguridad y Persistent=true. Pruébelo con systemctl start, revise la salida en el journal y anote la puntuación de systemd-analyze security. Pegue su .service y su .timer y responda a las preguntas de la lista de comprobación.
Ejercicio O6.3: Un servicio propio con temporizador
Este ejercicio lo hace usted mismo; nadie lo revisa. Marque lo que haya hecho y señale el ejercicio como realizado.
Con Persistent=true, la tarea se ejecuta igualmente después del arranque. Una puntuación inferior a 6 está bien para un primer servicio; permisos de escritura solo mediante ReadWritePaths.
Sus marcas y notas se quedan solo en este navegador; no las enviamos a ningún sitio. Si ha iniciado sesión, en Mis cursos solo guardamos que ha realizado el ejercicio.
Para saber más
- Manual de DirectAdmin 15.1: Trabajo Cron: ejecutar tareas automáticamente
- Manual de
systemd.exec(5)(User=, ProtectSystem=, NoNewPrivileges=): https://manpages.debian.org/trixie/systemd/systemd.exec.5.en.html - Manual de
systemd.timer(5)(OnCalendar=, Persistent=): https://manpages.debian.org/trixie/systemd/systemd.timer.5.en.html - Manual de
systemd.time(7)(formatos de programación horaria): https://manpages.debian.org/trixie/systemd/systemd.time.7.en.html
