Your application crashes at 3 AM. Without service management, it stays down until someone notices. With Systemd and Monit, it restarts in seconds — automatically, silently, with a log entry you can review in the morning.

This guide covers both tools in depth: Systemd for service lifecycle management, and Monit for resource-aware monitoring and recovery.

## Systemd: The Service Manager

Systemd is the init system on virtually every modern Linux distribution — Ubuntu, Debian, Fedora, CentOS, Arch. It manages the entire lifecycle of your services: starting them at boot, restarting them on failure, and stopping them cleanly on shutdown.

### Writing a Service Unit

A Systemd unit file defines how your application runs. Here's a production-ready example for a Node.js application:

```
[Unit]
Description=My Node.js API
After=network.target postgresql.service
Wants=postgresql.service

[Service]
Type=simple
User=deploy
Group=deploy
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/node dist/server.js
Restart=on-failure
RestartSec=5
StartLimitBurst=5
StartLimitIntervalSec=60

# Environment
Environment=NODE_ENV=production
EnvironmentFile=/var/www/myapp/.env

# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/var/www/myapp/uploads /var/www/myapp/logs

# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp

[Install]
WantedBy=multi-user.target
```

Save this to `/etc/systemd/system/myapp.service`, then enable and start it:

```
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
```

### Key Directives Explained

**`Restart=on-failure`** vs `Restart=always`: Use `on-failure` for most applications. `always` restarts even on clean exits (exit code 0), which can mask bugs where your app exits intentionally.

**`RestartSec=5`** : Wait 5 seconds between restart attempts. Without this, Systemd restarts immediately, which can hammer databases or APIs that aren't ready yet.

**`StartLimitBurst=5` and `StartLimitIntervalSec=60`** : If the service fails 5 times within 60 seconds, Systemd stops trying. This prevents infinite restart loops from consuming resources. Check what went wrong with `journalctl -u myapp --since "5 minutes ago"`.

**`After=` and `Wants=`** : `After` controls startup order (start my app after PostgreSQL). `Wants` creates a soft dependency (start PostgreSQL if it's not running, but don't fail if it can't).

### Security Hardening

The `ProtectSystem=strict` and `NoNewPrivileges=true` directives are often overlooked but important:

```
# Prevent privilege escalation
NoNewPrivileges=true

# Mount the filesystem read-only except for specified paths
ProtectSystem=strict
ReadWritePaths=/var/www/myapp/uploads

# Hide /home, /root, and /run/user from the service
ProtectHome=true

# Private /tmp (isolated from other services)
PrivateTmp=true
```

These don't affect functionality but significantly reduce the blast radius if your application is compromised.

### Debugging with journalctl

Systemd captures all stdout/stderr output through the journal. This is far more reliable than logging to files — journal entries are indexed, rotatable, and queryable:

```
# Follow live logs
journalctl -u myapp -f

# Logs from the last hour
journalctl -u myapp --since "1 hour ago"

# Logs from the last boot only
journalctl -u myapp -b

# Show only errors
journalctl -u myapp -p err

# Check why a service failed
systemctl status myapp
```

### Socket Activation

For services that receive infrequent traffic, Systemd can listen on the socket and only start the application when a connection arrives:

```
# myapp.socket
[Socket]
ListenStream=3000

[Install]
WantedBy=sockets.target
```

This reduces memory usage on servers running many services, since idle applications consume zero resources until needed.

## Monit: Resource-Aware Monitoring

Systemd handles start/stop/restart well, but it doesn't monitor resource usage. Your application might be running but consuming 95% of available memory, or stuck in an infinite loop burning CPU. That's where Monit comes in.

Monit watches processes, resources, files, and network services — and takes action based on thresholds you define.

### Installation

```
# Ubuntu/Debian
sudo apt install monit

# CentOS/RHEL
sudo yum install monit

# Start and enable
sudo systemctl enable monit
sudo systemctl start monit
```

### Monitoring a Service

Create a configuration file in `/etc/monit/conf.d/myapp`:

```
check process myapp matching "node dist/server.js"
    start program = "/usr/bin/systemctl start myapp"
    stop program = "/usr/bin/systemctl stop myapp"

    # Restart if the HTTP health check fails
    if failed
        host 127.0.0.1
        port 3000
        protocol http
        request "/health"
        with timeout 10 seconds
    then restart

    # Resource thresholds
    if cpu > 80% for 5 cycles then alert
    if cpu > 95% for 3 cycles then restart
    if memory > 500 MB for 5 cycles then alert
    if memory > 800 MB for 3 cycles then restart

    # Restart limits (prevent flapping)
    if 5 restarts within 5 cycles then unmonitor

    group application
```

The `matching` directive is more robust than `pidfile` — it finds the process by command name, so you don't need to manage PID files.

### Health Check Monitoring

The HTTP health check is the most valuable Monit feature. Rather than just checking whether the process exists, it verifies the application is actually responding:

```
if failed
    host 127.0.0.1
    port 3000
    protocol http
    request "/health"
    status = 200
    content = "ok"
    with timeout 10 seconds
then restart
```

Your `/health` endpoint should check downstream dependencies too — database connectivity, cache availability, disk space:

```
// Express health check endpoint
app.get('/health', async (req, res) => {
  try {
    await db.query('SELECT 1');
    res.json({ status: 'ok', uptime: process.uptime() });
  } catch (err) {
    res.status(503).json({ status: 'error', message: err.message });
  }
});
```

### Email Alerts

Monit can send email notifications when thresholds are hit or services restart:

```
set mailserver smtp.gmail.com port 587
    username "alerts@yourdomain.com"
    password "app-specific-password"
    using tls

set alert ops@yourdomain.com

# Per-check alert overrides
check process myapp matching "node dist/server.js"
    alert ops@yourdomain.com only on { timeout, resource, nonexist }
```

### Monit Web Dashboard

Monit includes a built-in web interface for checking service status:

```
set httpd port 2812
    use address 127.0.0.1 # Only listen on localhost
    allow admin:secretpassword
```

Access it at `http://localhost:2812`. In production, put it behind a reverse proxy with proper authentication rather than exposing it directly.

## Using Both Together

The recommended setup: **Systemd manages the service lifecycle, Monit monitors health and resources.** Monit delegates start/stop commands to Systemd, so there's no conflict:

```
# Monit uses systemctl for start/stop
start program = "/usr/bin/systemctl start myapp"
stop program = "/usr/bin/systemctl stop myapp"
```

This gives you:

- **Systemd** : Boot-time startup, dependency ordering, security sandboxing, journal logging
- **Monit** : HTTP health checks, CPU/memory thresholds, email alerts, web dashboard

### Monitoring Multiple Services

A typical production stack might have several services to monitor:

```
# /etc/monit/conf.d/stack

check process nginx with pidfile /run/nginx.pid
    start program = "/usr/bin/systemctl start nginx"
    stop program = "/usr/bin/systemctl stop nginx"
    if failed port 80 protocol http then restart

check process postgresql with pidfile /var/run/postgresql/14-main.pid
    start program = "/usr/bin/systemctl start postgresql"
    stop program = "/usr/bin/systemctl stop postgresql"
    if failed port 5432 protocol pgsql then restart
    if cpu > 70% for 3 cycles then alert

check process redis with pidfile /run/redis/redis-server.pid
    start program = "/usr/bin/systemctl start redis"
    stop program = "/usr/bin/systemctl stop redis"
    if failed port 6379 then restart
    if memory > 1 GB then alert
```

## Integrating with DeployHQ

When deploying through [DeployHQ](https://www.deployhq.com), include your service configuration files in the repository and use [deployment scripts](https://www.deployhq.com/blog/what-is-a-deployment-script) to apply them:

```
# SSH command (runs on the server after file transfer)

# Copy service file if changed
sudo cp config/myapp.service /etc/systemd/system/myapp.service
sudo systemctl daemon-reload

# Graceful restart (finish current requests before stopping)
sudo systemctl reload-or-restart myapp

# Verify the service is healthy
sleep 3
if ! systemctl is-active --quiet myapp; then
    echo "ERROR: Service failed to start after deployment"
    sudo journalctl -u myapp --since "1 minute ago" --no-pager
    exit 1
fi

# Reload Monit to pick up any config changes
sudo monit reload
```

The health check after restart is critical — without it, a deployment that breaks your app will report success. For more on this pattern, see our guide to [zero-downtime deployments](https://www.deployhq.com/blog/zero-downtime-deployments-keeping-your-application-running-smoothly).

[DeployHQ](https://www.deployhq.com) also provides [automated post-deploy health checks](https://www.deployhq.com/features/deployment-checks) at the deployment layer — HTTP probes and SSH commands run after every release, marking the deploy as failed if the checks don't pass. That layer pairs well with Monit's process-level monitoring: [DeployHQ](https://www.deployhq.com) verifies the release itself, Monit keeps the service healthy thereafter.

## Systemd Timers (Replacing Cron)

Systemd timers are a modern replacement for cron jobs, with better logging and dependency management:

```
# /etc/systemd/system/myapp-cleanup.timer
[Unit]
Description=Clean up old uploads daily

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target
```

```
# /etc/systemd/system/myapp-cleanup.service
[Unit]
Description=Clean up old uploads

[Service]
Type=oneshot
User=deploy
ExecStart=/var/www/myapp/scripts/cleanup.sh
```

Enable with `sudo systemctl enable --now myapp-cleanup.timer`. Check scheduled timers with `systemctl list-timers`.

## Troubleshooting

### Service Won't Start

```
# Check the status and recent logs
systemctl status myapp
journalctl -u myapp -n 50 --no-pager

# Common causes:
# - Wrong ExecStart path (check with `which node`)
# - Permission issues (check User/Group directives)
# - Port already in use (check with `ss -tlnp | grep 3000`)
# - Missing environment variables (check EnvironmentFile path)
```

### Monit Reports Does Not Exist

```
# Check if the process matching pattern is correct
pgrep -f "node dist/server.js"

# Test Monit configuration
sudo monit -t

# Check Monit's view of the service
sudo monit status myapp
```

### Restart Loop

If Systemd keeps restarting a service that immediately crashes:

```
# Check the start limit
systemctl show myapp | grep -E "StartLimit|Result"

# Reset the failure counter
sudo systemctl reset-failed myapp

# Temporarily disable restart to debug
sudo systemctl edit myapp
# Add: [Service]
# Restart=no
# Then start manually and watch logs
```

For a comprehensive approach to deployment reliability, see our [deployment checklist](https://www.deployhq.com/blog/the-ultimate-deployment-checklist-ensuring-smooth-and-successful-releases) and [deployment monitoring guide](https://www.deployhq.com/blog/monitoring-your-deployments-with-deployhq-ensuring-smooth-releases).

* * *

_[DeployHQ](https://www.deployhq.com) automates your entire deployment workflow — from Git push to production, including build scripts, file transfer, and SSH commands that restart your services. [Start deploying for free](https://www.deployhq.com/signup)._

_Have questions? Reach out at [support@deployhq.com](mailto:support@deployhq.com) or find us on [X/Twitter](https://x.com/deployhq)._

