Create Files and Directories Safely at Boot with systemd-tmpfiles

Use systemd-tmpfiles to create runtime, cache, and state directories with sane ownership and permissions at boot, while staying honest about when a service unit or the application itself is the better tool.

systemdLinux operationsBoot-time setup
Illustrated guide cover for Create Files and Directories Safely at Boot with systemd-tmpfiles
systemd • Linux operations • Boot-time setup
What you learn

How to write a small tmpfiles.d rule set, apply it safely, verify results, and connect it to a real service workflow.

Best for

Services that need a few directories created before or outside normal startup, especially when ownership or boot-time cleanup matters.

Risk to watch

tmpfiles can create empty paths and permissions. It cannot replace application-aware initialization, migrations, or data validation.

Before you begin

  • A Linux system that uses systemd.
  • sudo or root access.
  • A service user that already exists, or a clear plan for which user should own the directories.
  • A service you can test after making the change.

systemd-tmpfiles exists to create, clean, and adjust files and directories from simple configuration rules. It is often used for volatile paths under /run, but it can also manage persistent locations such as /var/cache and /var/lib when you need predictable ownership and permissions at boot.

Important: The official systemd guidance is not “use tmpfiles for everything.” For service-owned runtime, cache, state, logs, or configuration directories, RuntimeDirectory=, CacheDirectory=, StateDirectory=, and related unit settings are usually the better first choice when they fit.

Step 1: Decide whether tmpfiles is the right tool

Use tmpfiles.d when the directory setup is independent from one service start, needs boot-time behavior, or needs more flexibility than the basic unit directives give you. Good examples include:

  • A shared directory used by more than one unit.
  • A path that should exist before the service starts or after a reboot even before you manually test the service.
  • A cleanup rule or path layout that goes beyond a simple “create one directory for this unit” pattern.

Use the unit file directives instead when the directory belongs cleanly to one service and its lifetime should follow that service. For example, if a daemon only needs /run/myapp while it is running, RuntimeDirectory=myapp is usually cleaner than a separate tmpfiles.d file.

Practical rule: If the service unit can describe the directory cleanly, do that. Reach for tmpfiles when the setup is broader than the unit or needs extra boot-time control.

Step 2: Map runtime, cache, and state paths correctly

Do not dump everything into one folder and call it “data.” Pick the path by what the service is actually storing:

  • /run/myapp for runtime sockets, pid files, or other boot-ephemeral data.
  • /var/cache/myapp for rebuildable cache data.
  • /var/lib/myapp for persistent state the service actually depends on.

For this guide, assume your service runs as user and group myapp and needs all three:

/run/myapp
/var/cache/myapp
/var/lib/myapp

Make sure the service account already exists before you create ownership rules:

id myapp

If that command fails, create the service user first in the way that makes sense for your app or package. Do not continue with fake ownership values.

Step 3: Create a tmpfiles configuration

Create a dedicated config file under /etc/tmpfiles.d/:

sudo nano /etc/tmpfiles.d/myapp.conf

Add rules like these:

d /run/myapp        0750 myapp myapp -
d /var/cache/myapp  0750 myapp myapp -
d /var/lib/myapp    0750 myapp myapp -

What this means:

  • d means “create a directory if needed.”
  • 0750 sets the mode.
  • myapp myapp sets owner and group.
  • - means there is no age-based cleanup rule on that line.

This is intentionally boring. That is good. The safest tmpfiles setups are small, specific, and easy to audit later.

Do not get sloppy here: tmpfiles is not a substitute for real application setup. It can create an empty /var/lib/myapp, but it does not know whether your app needs schema creation, permissions inside nested files, a bootstrap command, or an application-level migration. Creating the directory does not prove the app is initialized correctly.

Step 4: Apply it now and verify

You do not need to reboot just to test the rules. Apply them immediately:

sudo systemd-tmpfiles --create \
  --prefix=/run/myapp \
  --prefix=/var/cache/myapp \
  --prefix=/var/lib/myapp

Then verify the directories:

ls -ld /run/myapp /var/cache/myapp /var/lib/myapp

Expected output should look roughly like this:

drwxr-x--- 2 myapp myapp ... /run/myapp
drwxr-x--- 2 myapp myapp ... /var/cache/myapp
drwxr-x--- 2 myapp myapp ... /var/lib/myapp

If you want more visibility while testing, raise the log level for a manual run:

sudo SYSTEMD_LOG_LEVEL=debug systemd-tmpfiles --create \
  --prefix=/run/myapp \
  --prefix=/var/cache/myapp \
  --prefix=/var/lib/myapp

That is much better than guessing whether the rule applied.

Step 5: Test with the service and across boot

Once the directories exist, start or restart the service and check whether it behaves normally:

sudo systemctl restart myapp.service
sudo systemctl status myapp.service --no-pager
sudo journalctl -u myapp.service -n 50 --no-pager

If the service starts cleanly, test the boot behavior too. Because /run is cleared on reboot, this is the part that matters most for runtime directories:

sudo reboot

After the system comes back, verify again:

ls -ld /run/myapp /var/cache/myapp /var/lib/myapp
sudo systemctl status myapp.service --no-pager

Success means:

  • /run/myapp was recreated automatically.
  • The persistent directories under /var still exist with the expected owner and mode.
  • The service can start without manual mkdir fixes.

Expected outcomes

When the setup is correct, you should be able to confirm all of the following:

  • The service user owns the paths it needs.
  • Runtime paths under /run come back automatically after reboot.
  • Cache and state directories under /var have stable permissions.
  • No one needs to run ad hoc startup shell commands just to make the service boot.
  • The service still performs any required application-level initialization separately.
What success looks like: Your service starts after reboot because the filesystem basics are in place, not because someone remembered a manual fix from shell history.

Troubleshooting common tmpfiles problems

The directories were not created.
Check the config filename, the rule syntax, and the exact paths. Then rerun systemd-tmpfiles with SYSTEMD_LOG_LEVEL=debug to see what it skipped or rejected.

The owner or group is wrong.
Make sure the account exists locally and that you used the correct user and group names. A typo here is enough to break the setup.

The service still fails with permission errors.
The top-level directory may be correct while nested files or application-created subdirectories are not. Also check whether the service expects write access somewhere else entirely.

The service works after manual setup but fails after reboot.
Focus on /run first. That tree is ephemeral. If the app depended on a runtime socket or pid directory that only existed from a manual test, tmpfiles or RuntimeDirectory= is exactly the fix.

I used tmpfiles, but the app still is not really initialized.
That is not a tmpfiles bug. It means the application also needs its own first-run setup, migration, or bootstrap command. Keep that logic separate and explicit.

Warning: Avoid using tmpfiles to paper over a badly packaged app. If the software cannot create or validate its own required internal structure, you still need an application-aware install or migration step.

What to do next

Once the service has the right directories at boot, the next strong Linux-native improvement is tightening the unit itself. Continue with Harden a systemd Service with systemd-analyze security and Sandboxing.