Fail Closed: Best Practices for Service Startup Behavior and Logging

A fail-closed approach to service startup failures, paired with a Windows Event Log structure that keeps operators informed.

A service that can’t start correctly has exactly two honest choices: refuse to run, or run in a way that’s clearly broken. There’s a third option — quietly running in a degraded, misleading state — and it’s the one that causes the most damage, because it looks fine from the outside while being unusable on the inside.

Here’s an approach built around two goals: fail closed when something’s actually wrong, and make sure operators find out immediately when it happens.

The fail-closed principle

When a service can’t start in a correct and secure operational mode, it shouldn’t keep running. Instead, it should detect the blocking condition during startup, log the problem, and stop itself gracefully.

Typical conditions that should block startup:

  • An expired or invalid TLS certificate
  • A missing private key
  • An unreachable database backend
  • Missing or corrupted configuration
  • Missing permissions
  • An unreachable dependency service

Why stopping beats limping along

If a service stays running but silently closes its network port or disables functionality instead, three bad things happen at once: IT operators see it as running, monitoring systems may report it as healthy, and the actual root cause stays hidden from everyone. That’s a “zombie process” — technically alive, functionally useless, and actively misleading anyone checking on it.

Stopping the service instead means the Windows Service Control Manager reflects the real state, monitoring systems catch the failure immediately, and operators get alerted without having to dig for the actual problem first.

Two places to log, two different audiences

Operational problems belong in two places, serving two different needs:

Logging locationPurpose
Windows Event LogOperational monitoring — what IT operators and monitoring systems watch
Service log filesDetailed technical diagnostics — what engineers need to actually debug the issue

A quick primer on Windows Event Log concepts

A log (or channel) is where events get stored — visible in Event Viewer as things like Windows Logs > Application or Applications and Services Logs > OpenSSH > Operational.

A provider (shown as Source in Event Viewer) identifies which component raised an event. An event is uniquely identified by the pair (Provider, Event ID) — for example, Source: OpenSSH, Event ID: 4 under the log OpenSSH/Operational.

Give each service its own provider

Each service should register its own provider name — CentralServer, SshService, SnmpService, MqttService, and so on. This gives you clear ownership, easy filtering in monitoring systems (Provider = SshService), and independent event numbering per service.

Event ID conventions worth adopting

Keep IDs stable after release. Operators and monitoring systems build queries and alerts around specific event IDs — changing them silently breaks that tooling.

IDs are scoped per provider, not globally. CentralServer using event ID 1001 and SshService also using 1001 is not a conflict — uniqueness only needs to hold within a single provider.

Adopt a numbering range per category, starting each provider at 1000:

RangeCategory
1000–1999Service lifecycle
2000–2999Security / certificates
3000–3999Network / listener
4000–4999Storage / database

A concrete example:

Provider: SshService
EventID: 2001
Level: Error
Message:
TLS certificate expired. SSH listener cannot be started.
Service will stop.

Where the events should live

Follow the same pattern Microsoft uses for its own services (OpenSSH being a good reference point) — dedicated channels under Applications and Services Logs:

Applications and Services Logs
    Product
        Admin
        Operational

Admin carries critical, actionable issues: startup failure, an expired certificate, an unreachable database, invalid configuration, permission problems. These are the events that require someone to actually do something.

Operational carries normal state changes: service started successfully, service stopped normally, a dependency connection established, a listener opened. Useful context, not an alert.

Putting it together: a startup failure

If a service fails to start because of an expired certificate, the event lands in Product/Admin:

Provider: SshService
EventID: 2001
Level: Error

The TLS certificate used by the SSH service has expired.
The SSH listener was not started and the service is stopping.

Thumbprint: <certificate thumbprint>
Store: LocalMachine\My

Action: Renew or replace the certificate and restart the service.

Don’t wait for the failure — warn ahead of it

Certificate expiry shouldn’t be a surprise. Services should proactively report upcoming expiration: if a certificate’s remaining validity drops below 90 days, log a daily Admin warning event.

Provider: SshService
EventID: 2100
Level: Warning

The TLS certificate used by the SSH service will expire in 45 days.
Thumbprint: <certificate thumbprint>
Expiration date: <date>

That gives operators a real window to renew before it turns into an outage, instead of finding out the moment the service stops.

Summary

  • Services must fail closed: if correct operation isn’t possible, log the problem and stop — don’t limp along in a misleading state.
  • Log operational health to the Windows Event Log; log detailed diagnostics to service-specific log files.
  • Give each service its own provider, scope event IDs per provider, and organize channels under Applications and Services Logs > Product > {Admin, Operational}.

None of this is exotic — it’s the same pattern well-behaved Windows services have followed for years. The payoff is that operational issues become visible immediately, through the same tools (Event Viewer, SIEM, SCOM) IT teams already watch, instead of requiring someone to notice a service quietly not doing its job.