Why systemd Stops Restarting a Failed Service
Last edited on August 2, 2026

start-limit-hit is not the reason your application failed. It means systemd observed too many start attempts inside the unit’s configured interval and refused another activation. The real cause happened earlier: a bad path, missing credential, permission error, occupied port, failed dependency, process crash, timeout, or another condition made the service exit.

Treating the rate limiter as the defect leads to a familiar outage loop: increase StartLimitBurst, run reset-failed, watch the process crash again, and lose the clean timeline. A safer recovery preserves the first failure, checks the effective unit rather than one file, repairs the application or execution contract, validates the change, clears only the named unit’s counter, and proves that both the process and its workload remain healthy.

This guide applies to system services on a Linux VPS managed by systemd. Replace example.service with the exact unit name and use a maintenance window when stopping or restarting it could affect users.

start-limit-hit is the brake, not the collision

Two systemd mechanisms interact during a restart storm. Restart= decides whether a service should be started again after it exits. Unit-level start limiting counts activation attempts and blocks more starts when the configured burst is exceeded inside the interval. The current systemd service manual explicitly notes that automatic restarts remain subject to start-rate limiting.

Imagine a service with Restart=on-failure. A missing configuration file makes its process exit immediately. systemd waits for RestartSec=, starts it again, receives the same exit, and repeats. Once the manager reaches the unit’s effective StartLimitBurst= within StartLimitIntervalSec=, it stops trying. The final rate-limit message describes systemd’s decision; the first exit describes the incident.

Defaults can vary with systemd version and manager configuration, so do not build a repair around a number copied from another server. Read effective properties from the affected host:

unit=example.service
systemctl show "$unit" -p ActiveState -p SubState -p Result -p NRestarts -p Restart -p RestartUSec -p StartLimitIntervalUSec -p StartLimitBurst

NRestarts counts automatic restarts known to the manager; it does not replace the journal. Result can show the final unit result, while ExecMainCode and ExecMainStatus help identify how the main process ended.

Two-lane systemd restart-storm map separating the service's first failure from restart attempts, the start-limit gate, evidence capture, repair, reset and stable acceptance.
The process failure comes first; start-limit-hit appears later when systemd stops another activation. Clear the counter only after evidence and repair.

Preserve the failed state before clearing anything

When customer traffic is at risk, the first instinct is to keep pressing Start. Pause long enough to save a bounded evidence window. Repeated attempts can add noise, trigger rate-limited dependencies, overwrite application logs, or make a transient port conflict look permanent.

Start with read-only state:

unit=example.service
date -u
systemctl status "$unit" --no-pager -l
systemctl show "$unit" -p ActiveState -p SubState -p Result -p ExecMainCode -p ExecMainStatus -p NRestarts
journalctl -u "$unit" -b --since "-30 min" --no-pager -o short-precise

Choose the journal window from the incident, not mechanically from this example. If the failure crossed a reboot, inspect the previous boot with journalctl -u "$unit" -b -1 after confirming that persistent journal storage exists. A current boot log cannot reconstruct events that were never retained.

Next, record what systemd actually loaded:

systemctl cat "$unit"
systemctl show "$unit" -p FragmentPath -p DropInPaths -p User -p Group -p WorkingDirectory -p ExecStart

systemctl cat displays the main fragment and drop-ins on disk. The systemctl manual warns that files on disk may differ from the manager’s current view until daemon-reload is issued. Capture both the file view and effective runtime properties when a recent deployment may have changed one but not the other.

If the process is still cycling and another healthy node can carry traffic, a controlled systemctl stop can preserve resources while you investigate. Do not stop the last working instance merely to make logs quieter. First confirm redundancy, queue behavior, current sessions and recovery authority.

Follow the earliest failing attempt

The last journal lines often say “Start request repeated too quickly.” Search upward to the first failed activation in the incident window and classify what ended the process.

Exit codes point to application or launch failures

code=exited, status=1/FAILURE says the process returned a non-zero exit status; it does not explain why. Read application output immediately above it. Common causes include a syntax error, missing configuration, invalid flag, failed migration, unavailable dependency, or an address already in use.

An exit such as status=203/EXEC belongs to the manager’s execution stage. Check the ExecStart= path, executable bit, shebang interpreter, mount options, architecture and service user access. status=217/USER points toward user or credential setup. systemd’s symbolic status names are more useful than guessing from the number alone.

Signals and timeouts need a different trail

code=killed, signal=SEGV suggests a crash, while signal=KILL can come from an administrator, a timeout escalation, a cgroup memory limit, or the kernel’s out-of-memory response. Correlate the service journal with kernel and manager messages for the same timestamp instead of assuming every SIGKILL is an OOM event.

Timeout results deserve the same care. A start timeout may mean the application never became ready, a Type=notify service failed to send readiness, or a dependency call hung. Raising TimeoutStartSec= can hide a deadlock; prove that startup is legitimately slow before extending it.

A shell test is not the service environment

“It works when I run the command” is evidence, but only for your shell identity and environment. A system service may use User=, Group=, WorkingDirectory=, EnvironmentFile=, filesystem sandboxing, capability limits, private temporary directories and a different PATH.

Never solve that mismatch by running the production workload as root. Compare the unit’s declared context, verify permissions on only the required paths, and use the application’s supported diagnostic mode under the intended service account when available. Least privilege is part of the execution contract, not an obstacle to remove during an outage.

Repair the effective unit, not a vendor file

Package updates can replace files under /usr/lib/systemd/system or /lib/systemd/system. Local changes belong in a drop-in under /etc/systemd/system/<unit>.d/ unless the package or application documents another method. The current systemd unit manual explains how drop-ins are merged after the main fragment and how /etc takes precedence over runtime and vendor paths.

Use the editor systemd provides:

sudo systemctl edit example.service

A narrowly scoped recovery drop-in might adjust only the restart behavior:

[Unit]
StartLimitIntervalSec=5min
StartLimitBurst=4

[Service]
Restart=on-failure
RestartSec=15s

Those values are examples, not universal recommendations. Four attempts in five minutes may be sensible for a dependency that usually recovers in seconds and dangerous for a job that sends email, charges a card, runs a migration, or writes non-idempotent state on every start. Choose the interval, burst and delay from failure cost, dependency recovery time and alerting—not from a copied “high limit.”

Before editing, save the current drop-in and incident timestamp in a protected change record. If rollback is needed, restore the previous drop-in or remove only the file created for this change, run daemon-reload, and return the service through the same acceptance checks. systemctl revert can remove local overrides, but its scope may be broader than one change; inspect systemctl cat and the documented effect before using it.

Validate before you release another start

Static validation cannot prove that an application will serve traffic, but it catches missing executables, unknown directives and dependency errors before another restart storm. Resolve the loaded fragment path and verify it:

unit=example.service
fragment=$(systemctl show "$unit" -p FragmentPath --value)
sudo systemd-analyze verify "$fragment"

The systemd-analyze manual says verify loads unit files and prints detected warnings. Review the output in context: a warning from a referenced vendor unit is not automatically caused by your change, while an unknown option in the new drop-in is a blocker.

Once the files are correct, load the new definition, clear the failed state for only this unit, and start it:

unit=example.service
sudo systemctl daemon-reload
sudo systemctl reset-failed "$unit"
sudo systemctl start "$unit"

reset-failed clears the failed state and resets the unit’s start-rate counter; it does not repair or start the service. Keeping it after diagnosis and validation prevents the counter from being used as a substitute for root-cause work.

Watch the first minutes without attaching a command that exits after one successful status:

unit=example.service
systemctl show "$unit" -p ActiveState -p SubState -p Result -p NRestarts
journalctl -u "$unit" -b --since "-5 min" --no-pager -o short-precise

Then test the workload from outside its process boundary. Check the listening socket or local health endpoint, the public request path, a representative read, and a safe write path when the service owns business transactions. active (running) is necessary, not sufficient. A process can stay alive while returning errors, waiting on a dead dependency, or serving stale state.

Match the restart policy to the failure model

Restart automation should buy time for transient faults without concealing deterministic ones. The service manual recommends on-failure for long-running services in many cases, but the right choice follows process semantics.

Workload behavior Useful starting policy Why it fits Main risk to control
Long-running daemon should remain available Restart=on-failure Restarts non-zero exits, signals and timeouts while respecting a clean stop Fast deterministic crash can still storm until the start limit intervenes
Daemon must run continuously, even after clean exit Restart=always Treats every exit as unexpected service loss Planned or intentional clean exits also restart
Only abnormal signals or timeouts should recover Restart=on-abnormal Leaves ordinary exit statuses for an operator or orchestrator Application-level failures returning non-zero may remain down
One-shot maintenance or non-idempotent job Often Restart=no Avoids replaying side effects automatically Recovery requires explicit job-level retry and deduplication

Disabling start limiting with StartLimitIntervalSec=0 removes the manager’s protective brake. That can be justified for a carefully designed service with its own bounded exponential backoff, but it is a poor first response to an unknown crash. A loop that consumes CPU, floods logs, hammers an API or repeats side effects can continue indefinitely.

Where supported by the installed systemd version, newer restart features may add backoff or debug behavior. Check the local manual and version before using them; portable guidance should not assume every maintained distribution ships the same manager release.

FAQ

Does systemctl reset-failed start the service?

No. systemctl reset-failed example.service clears systemd’s failed state and start-rate counter for that unit. A separate systemctl start example.service is still required, and it should follow root-cause repair and unit validation.

Can I disable systemd start limiting?

Yes. StartLimitIntervalSec=0 disables start-rate limiting for the unit, but it also removes protection against a rapid deterministic crash loop. Keep bounded limiting unless the service has a proven, safer retry mechanism and monitoring can detect endless failure.

Should a server daemon use Restart=always or Restart=on-failure?

Use Restart=on-failure when clean exits should remain stopped; use Restart=always only when any exit means the daemon must return. Neither setting fixes bad configuration, missing dependencies or non-idempotent startup behavior.

Why does ExecStart work in my shell but fail under systemd?

Your shell may have a different user, working directory, environment, PATH, capabilities and filesystem access. Compare systemctl show and systemctl cat with the application’s requirements, then test under the intended service identity without weakening permissions globally.

Is systemctl daemon-reload enough after editing a unit?

No. daemon-reload makes systemd reread unit definitions, but it does not restart an already running service. Decide whether the application can reload its own configuration or needs a controlled restart, then verify workload behavior afterward.

What is the difference between reload, daemon-reload, and restart?

systemctl reload asks a service to reload application configuration, daemon-reload reloads systemd unit definitions, and restart stops then starts the unit. Support for application reload is service-specific; Voxfor’s validated Caddy reload workflow shows why configuration validation and request-path proof still matter.

Close the incident with evidence, not a green dot

A repaired service should survive beyond one successful start. Record the original Result, exit status or signal, first meaningful application error, effective fragment and drop-ins, approved change, validation output, restart-counter behavior and external acceptance result. Keep UTC timestamps so application, kernel, proxy and monitoring events can be correlated later.

Define a short observation window from the workload’s normal cycle. A queue worker may need to process representative jobs; a web service needs fresh requests through the real proxy; a scheduled service must reach its next activation. Confirm that NRestarts stays stable and that the original failure does not return.

Production owners can assign Linux operations to Voxfor when service monitoring, change approval and recovery evidence should not depend on one developer being online. Whether the work is managed internally or delegated, the return-to-service rule stays the same: repair the first failure, preserve the limiter as protection, and close only after the workload proves recovery.

Leave a Reply

Your email address will not be published. Required fields are marked *