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.
How to write a small tmpfiles.d rule set, apply it safely, verify results, and connect it to a real service workflow.
Services that need a few directories created before or outside normal startup, especially when ownership or boot-time cleanup matters.
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. sudoor 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.
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.
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/myappfor runtime sockets, pid files, or other boot-ephemeral data./var/cache/myappfor rebuildable cache data./var/lib/myappfor 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/myappMake sure the service account already exists before you create ownership rules:
id myappIf 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.confAdd 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:
dmeans “create a directory if needed.”0750sets the mode.myapp myappsets 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.
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/myappThen verify the directories:
ls -ld /run/myapp /var/cache/myapp /var/lib/myappExpected 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/myappIf 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/myappThat 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-pagerIf 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 rebootAfter the system comes back, verify again:
ls -ld /run/myapp /var/cache/myapp /var/lib/myapp
sudo systemctl status myapp.service --no-pagerSuccess means:
/run/myappwas recreated automatically.- The persistent directories under
/varstill exist with the expected owner and mode. - The service can start without manual
mkdirfixes.
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
/runcome back automatically after reboot. - Cache and state directories under
/varhave 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.
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.
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.
