WakeSystem=false and use OnCalendar= triggers instead of timers. This approach ensures the system only activates when necessary for scheduled tasks, preserving battery life and reducing unnecessary CPU usage during laptop idle periods.
Understanding Systemd Idle Behavior
By default, many services are designed to run continuously or wake the system from suspend states to perform checks. On a laptop, this behavior can significantly impact battery life and thermal management. The goal is to transition from a persistent background process to an event-driven model that respects the system's power state. Systemd provides specific directives to control whether a service should trigger a system wakeup or remain dormant until explicitly needed.
Configuring Wakeup Restrictions
The first step in minimizing active time is ensuring the service does not force the hardware to leave the idle state. You must edit the service unit file to include the WakeSystem=false directive. This setting tells systemd that this particular unit is not required to bring the machine out of sleep mode. Without this flag, even a lightweight check-in script could prevent the laptop from entering deep sleep, leading to constant power drain.
[Unit]
Description=My Idle Service
After=network-online.target
[Service]
Type=oneshot
ExecStart=/path/to/your/script.sh
WakeSystem=false
[Install]
WantedBy=multi-user.target
Additionally, you should verify that your service does not rely on real-time clocks that require frequent synchronization. If the service performs network requests, ensure it uses exponential backoff or long intervals to reduce the frequency of outbound connections, which also keeps the network interface in a low-power state longer.
Implementing Calendar-Based Triggers
Rather than using a timer that counts seconds or minutes, utilize calendar specifications defined by OnCalendar=. These specifications allow you to define precise times when the service should activate, such as once every hour or at specific midday intervals. This method is more efficient than polling because it leverages the kernel's timer subsystem directly, avoiding the overhead of a constantly running daemon.
Creating the Timer Unit
You need to create a companion timer unit that references your service. This unit will handle the scheduling logic. Use the OnCalendar= directive to set a human-readable schedule. For example, if you want the service to run every 6 hours, you can specify OnCalendar=*:0/6:00. This ensures the service remains completely inactive between these specified windows.
[Unit]
Description=Timer for My Idle Service
[Timer]
OnCalendar=*:0/6:00
Persistent=true
[Install]
WantedBy=timers.target
The Persistent=true option is crucial. It ensures that if the system was off during a scheduled time, the service will run immediately upon boot rather than waiting for the next cycle. This prevents missed tasks while still maintaining the idle behavior during normal operation.
Optimizing NixOS Configuration
NixOS manages service configurations through its declarative configuration language. To implement the changes above, you must modify your configuration.nix file. Instead of manually editing files in /etc/systemd/system, use the systemd.services and systemd.timers options. This ensures your configuration persists across updates and maintains reproducibility.
{
systemd.services.my-idle-service = {
enable = true;
wantedBy = [ "multi-user.target" ];
serviceConfig.WakeSystem = false;
script = "/path/to/your/script.sh";
};
systemd.timers.my-idle-timer = {
enable = true;
wantedBy = [ "timers.target" ];
timerConfig.OnCalendar = "*:0/6:00";
timerConfig.Persistent = true;
};
}
After updating the configuration, rebuild your system using nixos-rebuild switch. This applies the new service definitions and enables the timer. Verify that the timer is active by running systemctl list-timers --all. You should see your custom timer listed with the next activation time matching your calendar specification.
Verifying and Troubleshooting
Once configured, monitor the service to ensure it behaves as expected. Use journalctl -u my-idle-service to view logs. Check that the service starts only during the designated windows and exits cleanly afterward. If the service fails to start, verify that the executable has the correct permissions and that all dependencies are met.
If you notice the system waking up unexpectedly, check other services that might be interfering. Some hardware drivers or network managers may have their own wakeup settings. Ensure that no other unit depends on your service in a way that forces it to stay active. Refer to related guides on preserving order of closing services to understand dependency chains better.
Frequently Asked Questions
How do I check if a service is currently active?
Run systemctl status my-idle-service to see the current state. If it shows inactive (dead), the service is correctly idle. Active services will display active (running) or active (exited) if they just completed a task.
Can I use relative time intervals instead of calendar dates?
Yes, you can use OnBootSec= or OnActiveSec= for relative intervals, but calendar specs are generally more predictable for daily routines. Relative times can drift slightly due to system load, whereas calendar events align precisely with clock ticks.
What happens if the system is asleep during a scheduled time?
With Persistent=true set in the timer unit, the service will trigger immediately after the system wakes up if it missed a scheduled slot. This ensures tasks are not skipped due to sleep cycles.
Does disabling WakeSystem affect other services?
No, WakeSystem=false is specific to the individual unit file. Other services can still request system wakeups if they are configured to do so. This setting only prevents this specific service from forcing the hardware out of idle states.
How do I debug why my timer isn't firing?
Check the timer status with systemctl status my-idle-timer. Look for any error messages in the journal output. Also, verify that the calendar syntax is valid by testing it with systemd-analyze calendar followed by your spec string.