systemd permite ejecutar aplicaciones con usuarios dedicados, reinicios controlados, límites de recursos, credenciales aisladas y restricciones sobre el sistema de archivos..

Una unidad bien configurada no sustituye la seguridad de la aplicación, pero reduce su superficie de acceso y facilita el diagnóstico operativo.

Cuándo conviene aplicarlo

Este enfoque resulta útil para:

  • APIs propias.
  • Workers y consumidores de colas.
  • Bots internos.
  • Agentes de monitoreo.
  • Procesos que deben iniciar con el sistema.
  • Aplicaciones instaladas directamente en una máquina virtual o servidor.

Si la aplicación ya se ejecuta dentro de un orquestador, evita duplicar innecesariamente las responsabilidades de reinicio y aislamiento.

Requisitos previos

Necesitarás:

  • Un servidor Linux con systemd.
  • El binario o script de la aplicación.
  • Un comando de validación o prueba de configuración.
  • Un usuario dedicado o DynamicUser=.
  • Rutas de estado y caché identificadas.
  • Dependencias externas conocidas.
  • Un procedimiento de actualización y rollback.

Comprueba la versión disponible:

systemd --version

No todas las directivas existen en versiones antiguas. Valida la unidad en la misma distribución donde se ejecutará.

Arquitectura de trabajo

La secuencia recomendada es:

  1. Instalar el binario con propiedad de root.
  2. Crear un usuario sin login.
  3. Definir la unidad y las rutas administradas.
  4. Incorporar las restricciones compatibles.
  5. Validar la sintaxis y la aplicación.
  6. Iniciar sin habilitar todavía el arranque automático.
  7. Comprobar estado, logs y recursos.
  8. Habilitar el servicio después de una prueba satisfactoria.

El proceso debe poder escribir únicamente en rutas expresamente previstas. Los logs deberían enviarse a stdout y stderr, que systemd-journald captura automáticamente.

Paso 1: instalar el binario de forma controlada

Crea el directorio y copia el ejecutable como root:

sudo install \
  --directory \
  --owner root \
  --group root \
  --mode 0755 \
  /opt/app

sudo install \
  --owner root \
  --group root \
  --mode 0755 \
  ./app \
  /opt/app/app

El usuario del servicio debe poder ejecutar el binario, pero no modificarlo. Para despliegues con rollback, conserva versiones separadas y utiliza un enlace current administrado exclusivamente por root.

Si se trata de un script, ExecStart debe invocar explícitamente un intérprete instalado:

ExecStart=/usr/bin/python3 /opt/app/main.py

ExecStart no interpreta tuberías, redirecciones ni sintaxis completa de shell. Evita agregar /bin/sh -c salvo que sea estrictamente necesario.

Paso 2: crear un usuario sin login

Localiza la ruta de nologin, que varía entre distribuciones:

command -v nologin

Crea el usuario y su grupo:

sudo useradd \
  --system \
  --user-group \
  --no-create-home \
  --shell "$(command -v nologin)" \
  appsvc

Comprueba el resultado:

getent passwd appsvc
getent group appsvc

No agregues este usuario a grupos privilegiados como sudo, docker o grupos con acceso amplio a dispositivos.

Para servicios simples también puede utilizarse DynamicUser=yes, combinado con StateDirectory= y RuntimeDirectory=. No lo mezcles con un usuario estático sin evaluar primero la propiedad de los datos persistentes.

Paso 3: separar configuración y secretos

Un archivo EnvironmentFile es adecuado para configuración no sensible:

sudo install \
  --directory \
  --owner root \
  --group root \
  --mode 0750 \
  /etc/app

sudo install \
  --owner root \
  --group root \
  --mode 0600 \
  ./app.env \
  /etc/app/app.env

Ejemplo:

APP_LISTEN_ADDRESS=127.0.0.1:8080
APP_LOG_LEVEL=info

No uses variables de entorno como almacén principal de contraseñas o tokens. Se heredan por el árbol de procesos y pueden quedar expuestas mediante interfaces de diagnóstico.

systemd ofrece credenciales que se entregan como archivos privados durante la activación del servicio. Documentación oficial de credenciales.

Prepara una credencial raíz:

sudo install \
  --directory \
  --owner root \
  --group root \
  --mode 0700 \
  /etc/app/credentials

sudo install \
  --owner root \
  --group root \
  --mode 0600 \
  /ruta/segura/api-token \
  /etc/app/credentials/api-token

La aplicación debería leer el token desde una ruta, no recibirlo como argumento. En sistemas compatibles también puedes utilizar systemd-creds y LoadCredentialEncrypted= para protegerlo cifrado en reposo.

Paso 4: crear una unidad completa

Guarda el siguiente contenido como app.service:

[Unit]
Description=Aplicación interna
Documentation=https://docs.ejemplo.com/app
Wants=network-online.target
After=network-online.target

StartLimitIntervalSec=60
StartLimitBurst=5

[Service]
Type=exec

User=appsvc
Group=appsvc
WorkingDirectory=/opt/app

ExecStart=/opt/app/app
Restart=on-failure
RestartSec=5s
TimeoutStartSec=30s
TimeoutStopSec=30s

EnvironmentFile=/etc/app/app.env

LoadCredential=api-token:/etc/app/credentials/api-token
Environment=APP_TOKEN_FILE=%d/api-token

StateDirectory=app
StateDirectoryMode=0750
RuntimeDirectory=app
RuntimeDirectoryMode=0750
CacheDirectory=app
CacheDirectoryMode=0750

UMask=0077

NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes

ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes

RestrictSUIDSGID=yes
LockPersonality=yes
CapabilityBoundingSet=

RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6

MemoryMax=512M
TasksMax=128
LimitNOFILE=8192

StandardOutput=journal
StandardError=journal
SyslogIdentifier=app

[Install]
WantedBy=multi-user.target

Instálala con propiedad de root:

sudo install \
  --owner root \
  --group root \
  --mode 0644 \
  app.service \
  /etc/systemd/system/app.service

Type=exec permite que el arranque falle si systemd no puede ejecutar realmente el binario, por ejemplo, porque la ruta no existe o el usuario no está disponible.

Paso 5: administrar las rutas de escritura

StateDirectory=app crea /var/lib/app y asigna la propiedad al usuario del servicio. RuntimeDirectory=app crea /run/app, mientras que CacheDirectory=app administra /var/cache/app.

Estas rutas continúan siendo escribibles aunque se utilice:

ProtectSystem=strict

Esto suele ser preferible a mantener una lista extensa de ReadWritePaths=. Opciones de ejecución y sandbox de systemd.

Si la aplicación necesita otra ruta, agrégala de forma específica:

ReadWritePaths=/srv/app/uploads

No habilites /var, /opt o /etc completos. Comprueba primero la propiedad y el propósito de cada directorio.

Paso 6: adaptar el endurecimiento a la aplicación

Las opciones de aislamiento deben habilitarse progresivamente:

  • PrivateDevices=yes puede impedir el acceso a GPU, cámara o dispositivos seriales.
  • RestrictAddressFamilies= debe incluir las familias de sockets realmente utilizadas.
  • ProtectHome=yes impide leer rutas de usuarios.
  • CapabilityBoundingSet= vacío elimina capacidades Linux adicionales.
  • ProtectSystem=strict vuelve el sistema de archivos de solo lectura.
  • Algunas aplicaciones JIT pueden ser incompatibles con restricciones adicionales como MemoryDenyWriteExecute=yes.

No copies una lista de endurecimiento sin probar el servicio. Una puntuación más alta no compensa una aplicación que deja de funcionar o pierde datos.

Si la aplicación necesita escuchar en un puerto inferior a 1024, suele ser preferible utilizar un proxy. Como alternativa, concede únicamente CAP_NET_BIND_SERVICE, sin ejecutar todo el proceso como root.

Paso 7: validar antes de cargar la unidad

Comprueba el archivo:

sudo systemd-analyze verify \
  /etc/systemd/system/app.service

Valida permisos y rutas:

namei -l /opt/app/app
sudo -u appsvc test -x /opt/app/app
sudo -u appsvc test -d /opt/app

Si la aplicación ofrece una validación, ejecútala con el usuario real:

sudo -u appsvc \
  /opt/app/app --check-config

Sustituye --check-config por el comando admitido. No inventes una validación basada solo en iniciar el servicio si la aplicación ofrece un mecanismo específico.

Paso 8: aplicar y comprobar

Recarga la configuración:

sudo systemctl daemon-reload

Inicia el servicio sin habilitar todavía el arranque:

sudo systemctl start app.service
sudo systemctl is-active app.service

Revisa el estado:

sudo systemctl status \
  --no-pager \
  --full \
  app.service

Consulta los logs del arranque actual:

sudo journalctl \
  --unit app.service \
  --boot \
  --lines 100 \
  --no-pager

Sigue los eventos:

sudo journalctl \
  --unit app.service \
  --follow

Después de comprobar la aplicación, habilita el arranque automático:

sudo systemctl enable app.service

Separar start de enable evita dejar activado al inicio un servicio que todavía no pasó las pruebas.

Paso 9: medir recursos y exposición

Consulta los valores efectivos:

sudo systemctl show app.service \
  --property User \
  --property Group \
  --property MainPID \
  --property MemoryCurrent \
  --property MemoryPeak \
  --property TasksCurrent \
  --property NRestarts

Evalúa el aislamiento:

sudo systemd-analyze security \
  --no-pager \
  app.service

systemd-analyze security es una evaluación heurística de las restricciones aplicadas. No analiza vulnerabilidades del binario, permisos de negocio, protocolos ni secretos. Referencia de systemd-analyze.

Los límites MemoryMax=, TasksMax= y CPUQuota= se implementan mediante los controles de recursos de systemd y cgroups. Deben definirse después de medir el consumo normal y los picos. Controles de recursos de systemd.

Por ejemplo:

MemoryHigh=384M
MemoryMax=512M
CPUQuota=150%
TasksMax=128

CPUQuota=100% equivale aproximadamente a la capacidad de un CPU, no al 100 % de toda una máquina multicore.

Paso 10: comprobar reinicios controlados

Con:

Restart=on-failure
RestartSec=5s

systemd reinicia el proceso después de una salida no satisfactoria o determinadas señales. Un systemctl stop intencional no dispara ese reinicio.

Las directivas:

StartLimitIntervalSec=60
StartLimitBurst=5

evitan un bucle ilimitado. Si se supera el límite, la unidad entra en estado failed.

Diagnostica antes de reactivarla:

sudo systemctl status app.service
sudo journalctl -u app.service --since '-10 minutes'

Después de corregir la causa:

sudo systemctl reset-failed app.service
sudo systemctl start app.service

No aumentes StartLimitBurst para ocultar un fallo persistente. Eso eleva el consumo y puede saturar dependencias externas.

Paso 11: probar parada y rollback

En staging, comprueba:

sudo systemctl stop app.service
sudo systemctl is-active app.service

Confirma que:

  • El proceso reciba SIGTERM.
  • Termine antes de TimeoutStopSec.
  • Libere sockets y locks.
  • No pierda datos pendientes.
  • No vuelva a iniciarse por Restart=on-failure.

Para rollback:

  1. Detén el servicio.
  2. Restaura la versión anterior del binario o enlace current.
  3. Restaura la unidad anterior si también cambió.
  4. Ejecuta systemd-analyze verify.
  5. Ejecuta daemon-reload.
  6. Inicia y repite las pruebas.

Nunca reemplaces un binario en ejecución con permisos del propio usuario del servicio.

Problemas frecuentes

El servicio no puede escribir

Consulta las rutas administradas:

sudo systemctl show app.service \
  --property StateDirectory \
  --property RuntimeDirectory \
  --property CacheDirectory

Agrega una ruta concreta mediante ReadWritePaths= solo si no corresponde a StateDirectory, RuntimeDirectory, CacheDirectory o LogsDirectory.

El servicio entra en un bucle de reinicio

Revisa el primer error, no únicamente el último:

sudo journalctl \
  --unit app.service \
  --since '-15 minutes' \
  --no-pager

Corrige la causa y utiliza reset-failed. No elimines los límites de arranque.

Funciona manualmente, pero falla con systemd

La ejecución manual puede heredar PATH, directorio actual, permisos y variables de la shell. Define rutas absolutas y reproduce la prueba como appsvc.


Ciclo de Validacion

No encuentra el secreto

Comprueba que la aplicación lea el archivo indicado por APP_TOKEN_FILE. La variable contiene una ruta, no el secreto.

No imprimas la credencial ni ejecutes systemd-creds cat durante diagnósticos normales.

El servicio no inicia después de endurecerlo

Desactiva temporalmente una directiva cada vez en staging. Revisa accesos a home, dispositivos, sockets, escritura y llamadas al kernel antes de decidir qué excepción necesita.

Advertencias de seguridad

  • No ejecutes aplicaciones propias como root sin justificación.
  • Mantén unidad y binario bajo propiedad de root.
  • No guardes secretos directamente en la unidad.
  • Usa EnvironmentFile solo para configuración no sensible.
  • Declara rutas de escritura mínimas.
  • No habilites aislamiento sin pruebas.
  • Protege y respalda las credenciales necesarias para recuperación.
  • Controla la retención y el espacio de journald.
  • Revisa los permisos de sockets Unix creados por la aplicación.

Las credenciales de systemd se entregan como archivos privados durante la activación, no se propagan como variables por todo el árbol de procesos y pueden cifrarse mediante systemd-creds. Credenciales de sistema y servicios.

Buenas prácticas para producción

  • Versiona las unidades junto con la aplicación.
  • Valida con systemd-analyze verify.
  • Inicia y comprueba antes de ejecutar enable.
  • Utiliza StateDirectory= para datos persistentes.
  • Envía logs estructurados a stdout y stderr.
  • Configura límites a partir de métricas reales.
  • Mantén reinicios y backoff acotados.
  • Conserva una versión anterior para rollback.
  • Prueba la parada ordenada.
  • Revisa periódicamente systemd-analyze security.
  • Documenta cada excepción de sandbox.
  • Ejecuta las pruebas con el usuario real del servicio.

Cloud Serversby Donweb

Todo el poder de la nube a tus proyectos y aplicaciones.
Alojamiento ultra rápido, escalable y con alta disponibilidad.

  • Performance que te sorprenderá
  • Rápida escalabilidad y sin limitaciones
  • Arquitectura de alta disponibilidad
  • Soporte experto y ejecutivos de cuenta
  • Pagos en tu moneda y facturación local

Descubre la mejor solución de Cloud Hosting