Skip to content
LinuxDeep Dive Published Updated 5 min readViews unavailable

XDG Base Directories: A Practical Contract for Linux User Data

Separate Linux application configuration, data, state, cache, and runtime files using XDG conventions, with safe migration and session-level diagnostics.

The XDG Base Directory Specification gives applications a shared contract for locating per-user files. It is not merely a cleanup convention for dotfiles: configuration, durable application data, restartable state, disposable cache, and short-lived session objects have different backup, portability, and access-control requirements. Putting all five in one directory makes those boundaries harder to reason about.

The specification is published by the freedesktop.org project, which explicitly describes its documents as interoperability specifications rather than standards issued by a formal standards body. Compatibility therefore depends on applications and desktop environments implementing the relevant parts. A conforming environment does not automatically relocate every legacy program’s files.

The directory classes are different lifecycle promises

When the variables are unset or empty, the specification defines these defaults:

Variable Default Intended contents
XDG_CONFIG_HOME $HOME/.config User-specific configuration
XDG_DATA_HOME $HOME/.local/share User-specific data that is useful beyond one process run
XDG_STATE_HOME $HOME/.local/state Persistent but non-portable state, such as histories or a window layout to restore
XDG_CACHE_HOME $HOME/.cache Re-creatable, non-essential cached data
XDG_RUNTIME_DIR Set by the login environment User-private, short-lived sockets and other runtime objects

XDG_CONFIG_DIRS and XDG_DATA_DIRS are search lists for system-wide defaults and data, respectively. Their defaults are /etc/xdg and /usr/local/share:/usr/share. The user-level directory has higher priority than the system search list. Within a multi-directory list, order matters: the first applicable definition wins. The paths in these variables must be absolute; a relative value is invalid and should be ignored. Do not confuse these base directories with the separate XDG User Directories mechanism that names locations such as Documents and Downloads.

The distinction between data and state is useful when a home directory moves between machines. A downloaded asset or user-installed template may belong in data; an editor’s recent-file history may be state that can be retained locally but should not necessarily follow the user to every host. A cache should be safe to delete and rebuild. If losing a file would lose user work, it is not a cache merely because it is large.

XDG_RUNTIME_DIR has stricter requirements than the other locations. The specification requires a local filesystem, ownership by the user, access limited to that user with mode 0700, and a lifetime bounded by the user’s login sessions; its contents must not survive reboot or a complete logout/login cycle. It is intended for communication and synchronization objects, not bulky files. A service manager or login stack normally provisions this directory. Pointing it at a hand-created /tmp directory is not an equivalent substitute: it can get ownership, privacy, lifecycle, filesystem capabilities, and collision handling wrong.

Diagnose the environment before changing it

Inspect the values actually inherited by the process that is failing. A terminal opened inside a desktop session and a system service may have different environments:

for name in XDG_CONFIG_HOME XDG_DATA_HOME XDG_STATE_HOME XDG_CACHE_HOME XDG_RUNTIME_DIR XDG_CONFIG_DIRS XDG_DATA_DIRS; do
  case "$name" in
    XDG_CONFIG_HOME) value=${XDG_CONFIG_HOME-} ;;
    XDG_DATA_HOME) value=${XDG_DATA_HOME-} ;;
    XDG_STATE_HOME) value=${XDG_STATE_HOME-} ;;
    XDG_CACHE_HOME) value=${XDG_CACHE_HOME-} ;;
    XDG_RUNTIME_DIR) value=${XDG_RUNTIME_DIR-} ;;
    XDG_CONFIG_DIRS) value=${XDG_CONFIG_DIRS-} ;;
    XDG_DATA_DIRS) value=${XDG_DATA_DIRS-} ;;
  esac
  printf '%-20s %s\n' "$name" "${value:-<unset or empty; specification default may apply>}"
done

For a simple, safe view without evaluating variable names, inspect the session environment directly:

env | sort | grep '^XDG_'

An unset variable does not necessarily mean the application has no directory: some variables have specification-defined defaults. Conversely, a variable that looks correct in an interactive shell does not prove a service or graphical launcher received it. Check the environment at the component boundary where the application starts before changing shell startup files.

Do not set every XDG variable globally just to make a particular tool move its files. Some applications support only a subset, have application-specific overrides, or retain paths discovered earlier in the session. Changing a home base can also invalidate symlinks, session sockets, open-file paths, or backup assumptions. Prefer the application’s documented setting, then confirm its behavior with a fresh process and a disposable profile.

Migrate dotfiles without losing state

Start with an inventory and a backup. Move one application at a time, and prefer its documented migration procedure. Configuration may contain credentials, database state, plugin metadata, or absolute paths that are not safe to copy indiscriminately. Preserve ownership and permissions, avoid copying a live database while its process is writing, and keep the original until the new location has been exercised across a restart.

For software that explicitly follows the convention, the path should usually be application-scoped, for example ~/.config/example-app/ rather than one flat shared file. Do not invent an application identifier if the program documents a different name. Never make the runtime directory a backup target: its content is intentionally transient. Back up durable data and state according to the application’s own recovery model, while treating cache as replaceable only after verifying that it contains no unique user data.

When developing an application, use the XDG variables or a platform library rather than hard-coding ~/.config. Resolve defaults when the process starts, require absolute values, create only the application-specific child directory, and handle missing or unwritable locations as normal errors. A user may place a home directory on a network filesystem or redirect configuration to another volume; code should not silently fall back to a path with weaker permissions or a different lifecycle.

Review the result as an interface contract

After a migration, verify which paths the application actually opened, test a clean logout and login for runtime-object cleanup, and confirm that deleting a known disposable cache does not remove user data. For a multi-user machine, check ownership and modes instead of relying on directory names as a security boundary. For a roaming home, decide deliberately which subtrees should be shared and which should remain machine-local.

The main operational benefit is clarity: configuration can be versioned selectively, durable data can be backed up, state can be restored when useful, cache can be rebuilt, and session sockets can stay private and ephemeral. That clarity only holds when applications respect the categories and operators verify how the actual session supplies them.

Related:

Sources:

Comments