Using ConditionPathExists and Friends in systemd Units

Systemd can do more than just start and restart services. With unit conditions like ConditionPathExists, you can make services environment-aware: skip jobs on cloned hosts, only run backups when a volume is mounted, or gate first-boot tasks — all without shell wrappers.

Table of Contents

When you first start working with systemd, you usually focus on the basics: ExecStart, Restart, maybe WantedBy. But systemd has a powerful feature that often flies under the radar: unit conditions.

Unit conditions let you say things like:

  • “Only run this service if a certain file exists.”
  • “Don’t start this unit if we’re in a container.”
  • “Skip this job if a particular mount point is missing.”

In this post, we’ll focus on ConditionPathExists and a few related options, and show practical examples of how you can use them in real infrastructure — including a simple clone guard pattern based on a marker file such as:


What are systemd conditions?

Conditions are directives under the [Unit] section of a systemd unit file. They’re evaluated before the unit is started. If any condition fails, systemd skips the unit entirely.

Key points:

  • They don’t run a shell or external script (except ExecCondition, which is separate and lives under [Service]).
  • They’re cheap and fast.
  • They’re perfect for environment-aware behavior (VM vs bare metal, production vs clone, etc.).

If a condition fails, systemd logs that the unit was skipped, not “failed” — which is often exactly what you want.


ConditionPathExists basics

The most common file-based condition is:

This means:

Only start this unit if /some/path exists.

You can invert it with !:

Meaning:

Only start this unit if /etc/host_is_clone does not exist.

This is a great pattern for clone guards: drop a file in cloned environments to prevent certain services from running (backups, monitoring jobs, email senders, etc.).

Example: Clone guard for an endpoint monitor

Imagine a monitoring script that should never run on a cloned VM (like a staging copy of production).

  • On production: /etc/host_is_clone does not exist → service runs as normal.
  • On a cloned system: create /etc/host_is_clone → systemd sees the condition fail and skips the service whenever something tries to start it (including timers).

The timer doesn’t need to be aware of the clone state at all.


Other useful path conditions

There are several related path conditions you can mix and match.

ConditionPathExistsGlob=

Like ConditionPathExists, but supports globbing:

Only run this unit if any matching file exists.

Good for: enabling services only if there’s at least one config snippet present.


ConditionPathIsDirectory=

Checks if the path exists and is a directory:

Good for: services that only make sense when a data directory exists (e.g., data nodes, worker roles).


ConditionPathIsMountPoint=

Checks if the path is a mount point:

Don’t start the backup job unless the backup volume is actually mounted.

Great for avoiding the classic mistake of backing up to /backup that silently became part of / because the mount failed.


ConditionPathIsReadWrite=

Checks whether a filesystem path is writable:

Useful when booting in read-only or rescue scenarios where some services should not start if the filesystem is not writable.


Beyond paths: other handy conditions

While we’re here, a few non-path conditions that are often useful in real systems:

ConditionVirtualization=

Control services depending on whether you’re in a VM, container, or bare metal:

Only start this service outside of containers.

Or:

Only start if running under KVM.


ConditionFirstBoot=

Run something only on the very first boot of the system:

Paired with WantedBy=multi-user.target, this makes a nice first-boot setup service that initializes config, generates keys, etc., and then never runs again.


ConditionACPower=

For laptops or edge devices:

Only run this job when plugged into AC.

Great for heavy maintenance tasks or backups on battery-powered hardware.


Conditions vs ExecCondition

You’ll also see ExecCondition mentioned in systemd docs, but it lives under [Service], not [Unit]. For example:

Differences vs ConditionPathExists:

  • ExecCondition runs an actual command. If it exits with status 0, service continues. Non-zero means ExecStart is skipped.
  • When an ExecCondition fails, the unit is typically marked as failed in systemd.
  • ConditionPathExists is internal to systemd and doesn’t require external commands; failed conditions mark the unit as skipped, not failed.

Rule of thumb:

  • Use Condition* directives when you can. They’re clean, fast, and don’t clutter logs with “failures” for expected skip cases.
  • Use ExecCondition when you truly need custom logic (complex checks, scripts, multiple files, etc.).

Practical examples

Here are a few scenarios you might encounter in hosting or managed-services environments.

1. Backup job only when backup volume is mounted

Even if a timer triggers this daily, nothing will run if /backup isn’t mounted — which helps avoid writing backups to the wrong place.


2. Disable email sending on clones

On production, there’s no /etc/host_is_clone file, so emails flow. On a copied VM used for testing, you touch /etc/host_is_clone and know that even if the service or its timer is active, it will never process outbound mail.


3. Run database migrations only on first boot

This helps bake migrations into image provisioning while ensuring they only run once.


4. Optional services based on config presence

You can ship the unit to all hosts, but it only runs on those where the config file has been deployed.


Observing conditions in action

When conditions cause a unit to be skipped, you’ll see it in systemctl status and the journal.

Example:

Might show something like:

ConditionPathExists=!/etc/host_is_clone was not met

And nothing else happens — which is exactly what you want for clone guards and environment-specific jobs.


Wrapping up

Systemd conditions are a small feature with a big impact:

  • They let you build environment-aware services without shell wrappers.
  • They keep logs cleaner by marking units as skipped, not failed, when they’re not supposed to run.
  • They are perfect for patterns like:
    • Clone guards (ConditionPathExists=!/etc/host_is_clone)
    • Backup safety (ConditionPathIsMountPoint=/backup)
    • First-boot initialization (ConditionFirstBoot=yes)
    • Virtualization- or battery-aware behavior (ConditionVirtualization=, ConditionACPower=)

If you’re already using systemd timers and services, sprinkling in a few well-chosen conditions can make your infrastructure more robust, safer, and easier to reason about — especially in environments with clones, staging copies, and mixed roles.

Have a project or a problem?

Talk with a senior engineer for practical recommendations—no obligation.

Leave a comment

Leave a Reply

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

Related Posts

Categories

Get a free consultation from Reliable Penguin

Submit the form—or for immediate service call 866-649-7984.