Overriding systemd’s Default Status Behavior: How to Force Execution of Custom Status Scripts


2 views

When migrating from older Ubuntu versions (using SysV init) to systemd-based systems (Ubuntu 16.04+), many administrators encounter a frustrating behavior: systemd often overrides custom status commands defined in legacy init scripts. This occurs because:

  • systemd has its own service status detection mechanism
  • The legacy init script compatibility layer doesn't always respect custom status commands
  • Systemd prioritizes its native unit file configuration over script contents

The proper way to handle this is to create a native systemd service unit file. Here's an example that respects your custom status command:

[Unit]
Description=My Custom Service
After=network.target

[Service]
Type=forking
ExecStart=/etc/init.d/my_service start
ExecStop=/etc/init.d/my_service stop
ExecReload=/etc/init.d/my_service restart
ExecStatus=/etc/init.d/my_service status  # This is the critical line
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

If you must keep using the init script while forcing systemd to execute your custom status command, you have several options:

Option 1: Use the Direct Script Path

While systemctl status will still show systemd's view, you can force execution of your status command with:

/etc/init.d/my_service status

Option 2: Create a Status Exec Directive

Modify your systemd service file to include:

ExecStatus=/path/to/your/script status

Then create a helper script:

#!/bin/bash
systemctl status my_service.service | head -n 3
/etc/init.d/my_service status
exit 0

Ubuntu's systemd actually maintains backward compatibility through the service command. If your script follows LSB headers correctly, try:

sudo service my_service status --full

For scripts that must work across both init systems, consider this hybrid approach:

#!/bin/bash

# Systemd detection
if pidof systemd &>/dev/null; then
    case "$1" in
        status)
            # Your custom status implementation
            if [ -f /var/run/my_service.pid ]; then
                echo "Running (PID $(cat /var/run/my_service.pid))"
            else
                echo "Stopped"
            fi
            exit 0
            ;;
        *)
            # Delegate other commands to systemctl
            systemctl "$1" my_service
            ;;
    esac
else
    # Original SysV init implementation
    case "$1" in
        # ... original case statements ...
    esac
fi
  • Always test changes in a development environment first
  • The Type=forking directive is often needed for legacy compatibility
  • Ensure your status command returns proper exit codes (0 for running, 3 for stopped)
  • Consider migrating fully to systemd unit files for better long-term maintainability

When migrating from Ubuntu 8.04 to 16.04, many developers encounter a frustrating behavior: systemd replaces the custom status command from legacy init scripts with its own standardized output. This becomes particularly problematic when:

  • Existing monitoring tools parse specific status output formats
  • You need backward compatibility across different Linux versions
  • The service's status logic contains business-specific checks

systemd implements LSB compliance differently than traditional init systems. When it encounters an init script:

1. It first checks for native systemd unit files
2. For legacy scripts, it provides "compatibility" through systemd-sysv-generator
3. The generator creates transient units that don't fully respect all script commands

Instead of fighting systemd, create a dedicated unit file that delegates status checks to your script:

# /etc/systemd/system/my_service.service
[Unit]
Description=My Custom Service
After=network.target

[Service]
Type=forking
ExecStart=/etc/init.d/my_service start
ExecStop=/etc/init.d/my_service stop
ExecReload=/etc/init.d/my_service restart
ExecStatus=/etc/init.d/my_service status  # This is the key line
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Create a status override file:

# /etc/systemd/system/my_service.service.d/status.conf
[Service]
ExecStart=
ExecStart=/etc/init.d/my_service start
ExecStatus=/etc/init.d/my_service status

Then run:

sudo systemctl daemon-reload
sudo systemctl reset-failed my_service.service

For quick debugging, bypass systemd entirely:

sudo /etc/init.d/my_service status
# Or using the direct path:
sudo /path/to/actual/executable --status-flag

Check if your status command now works through systemd:

systemctl status my_service.service --no-pager
# Should show your custom output instead of systemd's standard format
  • Always test after modifying unit files: systemctl daemon-reload
  • Consider migrating fully to systemd for better integration
  • Document the status output format for future maintenance