Ir al contenido

IDEAS PRÁCTICAS · ASIMOV INGENIERÍA

Cómo crear un demonio (daemon) en Linux — guía práctica y amigable

Table of contents

No titles in the blog post

¿Qué es un demonio (daemon)?

Un daemon es un programa que se ejecuta en segundo plano, típicamente sin interacción directa con un usuario, y suele iniciarse en el arranque del sistema o cuando lo necesite otro proceso. Ejemplos: servidores web, servicios de impresión, agentes de sincronización, colectores de métricas, etc.

Pantalla de computadora con código resaltado
Imagen ilustrativa · Mohammad Rahmani / Unsplash.

¿Por qué crear un daemon y cuándo usarlo?

  • Necesitas un servicio siempre disponible (monitoreo, recolección de datos, servidor pequeño).

  • Quieres que algo se ejecute automáticamente al inicio y se reinicie si falla.

  • Requieres control de arranque/parada, logs centralizados y separación de privilegios.

Si sólo vas a ejecutar un script puntual o un cron job ocasional, quizá un daemon sea overkill. Pero para servicios duraderos, es la opción correcta.

Enfoque recomendado (hoy): usa systemd

En la mayoría de distribuciones modernas (Debian, Ubuntu, Fedora, Arch...), el gestor de servicios es systemd. La práctica recomendada no es implementar manualmente el doble-fork clásico, sino escribir un programa claro y añadirle una unidad (.service) para que systemd lo maneje (arranque, reinicios, logs).

Ejemplo completo: daemon en Python + unit file systemd

1) Script Python simple (/usr/local/bin/mi_demonio.py)

#!/usr/bin/env python3
import time
import signal
import sys
import logging
LOGFILE = "/var/log/mi_demonio.log"
logging.basicConfig(filename=LOGFILE, level=logging.INFO,
                    format="%(asctime)s %(levelname)s %(message)s")
running = True
def handle_sigterm(signum, frame):
    global running
    logging.info("Recibido SIGTERM, cerrando...")
    running = False
signal.signal(signal.SIGTERM, handle_sigterm)
signal.signal(signal.SIGINT, handle_sigterm)
def main():
    logging.info("mi_demonio arrancó")
    while running:
        # Aquí va la lógica principal del daemon:
        # p. ej. leer sensores, procesar cola, exponer puerto, etc.
        logging.info(" trabajando... (ejemplo)")
        time.sleep(10)
    logging.info("mi_demonio deteniéndose")
if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        logging.exception("Error inesperado: %s", e)
        sys.exit(1)
  • Hazlo ejecutable: sudo chmod +x /usr/local/bin/mi_demonio.py

  • Regla: evita daemonizar (double-fork) en el código; deja que systemd supervise el proceso.

2) Unit file systemd (/etc/systemd/system/mi_demonio.service)

[Unit]
Description=Mi demonio de ejemplo
After=network.target
[Service]
Type=simple
User=miusuario
Group=miusuario
ExecStart=/usr/bin/env python3 /usr/local/bin/mi_demonio.py
Restart=on-failure
RestartSec=5
StandardOutput=syslog
StandardError=syslog
# Optional: limitar recursos
# MemoryMax=200M
# CPUQuota=50%
[Install]
WantedBy=multi-user.target
  • Type=simple asume que el proceso corre en primer plano (correcto para el script anterior).

  • Ajusta User/Group a un usuario sin privilegios creado para el servicio.

3) Instalar y probar

sudo systemctl daemon-reload
sudo systemctl enable mi_demonio.service     # arranque automático
sudo systemctl start mi_demonio.service
sudo systemctl status mi_demonio.service
# Ver logs:
journalctl -u mi_demonio.service -f

Si prefieres C: ejemplo mínimo (no daemonizar manualmente)

Es aconsejable tampoco hacer double-fork: compila un binario que corra en primer plano y deja que systemd lo maneje.

// mi_demonio.c (compilar con: gcc -o mi_demonio mi_demonio.c)
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdatomic.h>
static _Atomic int running = 1;
void sigint_handler(int s){
    running = 0;
}
int main(void){
    signal(SIGINT, sigint_handler);
    signal(SIGTERM, sigint_handler);
    while(running){
        // trabajo
        sleep(5);
    }
    return 0;
}
Empaqueta con un unit file similar.

Buenas prácticas al diseñar un daemon

  1. Corre en primer plano: facilita supervisión por systemd.

  2. Manejo de señales (SIGTERM, SIGINT) para un apagado limpio.

  3. Logging estructurado: usa journald (escribe en stdout/stderr) o archivos rotados; evita llenar /var/log sin rotación.

  4. No privileges by default: ejecuta con un usuario dedicado y mínimo privilegio.

  5. Control de recursos: configura límites en systemd (MemoryMax, CPUQuota).

  6. Reintentos controlados: Restart=on-failure + RestartSec.

  7. Health checks: implementa endpoints o ready-files si necesitas checks externas.

  8. Actualizaciones seguras: usa prácticas de despliegue atómicas (p. ej. systemctl stop → actualizar binario → systemctl start).

  9. Supervisión y métricas: exponer métricas Prometheus o logs estructurados ayuda mucho.

Depuración y testing

  • sudo journalctl -u mi_demonio.service -b para ver logs desde el boot.

  • systemctl status mi_demonio.service muestra última salida y código de retorno.

  • Para pruebas locales, ejecuta el binario/script directamente en tu shell para ver stdout/stderr.

  • Usa systemd-run --user --unit=test mi_comando para pruebas temporales.

Seguridad

  • Evita correr como root. Si necesitas privilegios, usa capacidades puntuales (p. ej. AmbientCapabilities=) o setcap en binarios.

  • Restringe el acceso a ficheros, sockets y puertos.

  • Considera PrivateTmp=true, NoNewPrivileges=true, ProtectSystem=full en el unit file para aislar el servicio.

  • Mantén dependencias actualizadas y valida entradas externas (si tu daemon abre un puerto).

Ejemplo de opciones de hardening en unit:

ProtectSystem=full
ProtectHome=yes
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true

Windows? (breve)

En Windows el equivalente son los Windows Services. Se crean típicamente en C#/C++ o con sc.exe create y se manejan desde el Service Control Manager. Para Python existe pywin32 y utilidades como nssm para ejecutar scripts como servicio. Pero el flujo, seguridad y herramientas son diferentes; dímelo si quieres un ejemplo para Windows.

Errores comunes

  • Programas que se daemonizan manualmente y luego systemd no puede hacer KillMode/restart correctamente.

  • Logs que crecen sin rotación.

  • Ejecutar como root innecesariamente.

  • No manejar señales → procesos que no terminan bien con systemctl stop.

Conclusión

Crear un daemon hoy significa:

  1. Implementar una aplicación que corra de forma confiable en primer plano (manejo de errores y señales).

  2. Proveer un unit file de systemd para gestión (arranque, reinicio, logs, límites).

  3. Pensar en seguridad, logs y actualizaciones desde el inicio.