XDG Base Directories Explained: Why Your Home Folder Is a Mess

XDG Base Directories Explained: Why Your Home Folder Is a Mess

A fresh home directory is tidy. After two years it has forty hidden files and directories in it, and no way to tell which hold your data, which hold settings, and which are caches you could delete without consequence. The XDG Base Directory Specification is the convention that fixes that, and the reason it has not fully worked is that it is a convention.

The four categories

VariableDefaultHolds
XDG_CONFIG_HOME~/.configSettings you would edit or version control
XDG_DATA_HOME~/.local/shareData the application manages
XDG_STATE_HOME~/.local/stateState that persists but is not data, such as logs and history
XDG_CACHE_HOME~/.cacheAnything regenerable
XDG_RUNTIME_DIR/run/user/$UIDSockets and lock files, session lifetime
# what is actually set
env | grep ^XDG
echo "${XDG_CONFIG_HOME:-$HOME/.config}"

Most of these are unset on a normal system, and that is correct. The specification defines the defaults, so an application is supposed to fall back when the variable is absent. You only set them if you want a different location.

There are system-wide counterparts too, XDG_CONFIG_DIRS and XDG_DATA_DIRS, which are colon-separated search paths. They are why an application finds /usr/share/applications as well as your own.

The distinction that matters

.config versus .local/share is the one people get wrong, and the consequence is a bad backup.

Config is what you would write by hand. Your editor settings, your terminal colours, your keybindings. Portable between machines, sensible in a git repository.

Data is what the application maintains. Your installed extensions, your saved sessions, a local database, your notes. Frequently not portable, and losing it loses work rather than preferences.

# worth backing up, and portable
~/.config/nvim/
~/.config/git/config
~/.config/kitty/kitty.conf

# worth backing up, machine specific
~/.local/share/keyrings/
~/.local/share/gnupg/
~/.local/share/applications/

# safe to lose
~/.cache/

That split is the point of the whole specification: you can back up config and data while excluding cache, and a cache directory on a machine with a slow backup can be gigabytes.

# what is your cache costing you
du -sh ~/.cache
du -sh ~/.cache/* | sort -rh | head

.cache reaching several gigabytes is normal. Browsers, thumbnails, package manager caches and compiler caches all live there.

Deleting the cache

# close applications first
rm -rf ~/.cache/*

Safe by definition, because anything there must be regenerable. Expect a slower first launch while thumbnails and indexes rebuild.

The caveat is that some applications misuse .cache for state they cannot recreate. It is against the specification and it happens. Closing applications before clearing reduces the chance of one writing a half-state, and if a program behaves oddly afterwards, that is what happened.

For automating it, our disk usage guide covers finding what is actually large first.

XDG_RUNTIME_DIR

ls -ld "$XDG_RUNTIME_DIR"
# drwx------ 12 you you 420 /run/user/1000

ls "$XDG_RUNTIME_DIR"
# bus  gnupg  pipewire-0  systemd/  wayland-1  ...

A tmpfs, mode 700, created by the session manager at login and removed at logout. It holds sockets, lock files and PID files: things that must not outlive the session.

This is where the Wayland socket, the PipeWire socket and the D-Bus session socket live. Our PipeWire guide and Wayland guide both touch on it.

Two practical consequences:

Nothing you want to keep goes here. It is deleted, and being on tmpfs it consumes RAM.

Cron jobs and systemd system services do not have one. A script that works in your terminal and fails from cron with an error about a missing socket is usually this. Our cron guide covers the wider environment problem.

# for a user service that needs it, this works
systemctl --user status

# for a system service, it does not exist, and
# loginctl enable-linger is what keeps a user session alive
loginctl enable-linger "$USER"

The programs that ignore it

# the offenders, roughly
ls -a ~ | grep '^\.' | grep -v '^\.\.$' | head -40

Some can be relocated. Others cannot.

Can be moved:

# a selection that works via environment variables
export LESSHISTFILE="$XDG_STATE_HOME/less/history"
export GNUPGHOME="$XDG_DATA_HOME/gnupg"
export WGETRC="$XDG_CONFIG_HOME/wgetrc"
export DOCKER_CONFIG="$XDG_CONFIG_HOME/docker"
export NPM_CONFIG_USERCONFIG="$XDG_CONFIG_HOME/npm/npmrc"
export CARGO_HOME="$XDG_DATA_HOME/cargo"
export RUSTUP_HOME="$XDG_DATA_HOME/rustup"
export GOPATH="$XDG_DATA_HOME/go"
export PYTHONPYCACHEPREFIX="$XDG_CACHE_HOME/python"
export SQLITE_HISTORY="$XDG_CACHE_HOME/sqlite_history"
export PSQL_HISTORY="$XDG_STATE_HOME/psql_history"

Put these in your login shell config, per our shell startup files guide, and create the directories first or the programs will fail rather than falling back.

mkdir -p "$XDG_STATE_HOME"/{less,} "$XDG_DATA_HOME"/{gnupg,cargo,go}
chmod 700 "$XDG_DATA_HOME/gnupg"

GNUPGHOME needs mode 700 or gpg refuses to use it.

Cannot be moved, realistically:

  • ~/.bashrc and friends. Bash reads fixed paths, and this will not change
  • ~/.ssh. The path is compiled in, and -F per invocation is not a solution
  • ~/.local/bin is itself the XDG-ish answer for user binaries

For ssh and bash, accept them. They are two entries rather than forty, and both are load-bearing enough that relocating them would break more than it tidies.

Auditing your own home directory

# top-level dotfiles and directories, by size
du -sh ~/.[!.]* 2>/dev/null | sort -rh | head -25

# which are actively used
find ~ -maxdepth 1 -name '.*' -newermt '-30 days' -printf '%TY-%Tm-%Td %p\n' | sort

# and which are not
find ~ -maxdepth 1 -name '.*' ! -newermt '-1 year' -printf '%TY-%Tm-%Td %p\n' | sort

That last one is the useful one. Directories untouched for a year usually belong to software you removed, and clearing them is safe once you have checked what they are.

# before deleting anything, find out what owns it
dpkg -S ~/.thatdirectory 2>/dev/null
ls -la ~/.thatdirectory

Move rather than delete, until you are sure:

mkdir -p ~/old-dotfiles
mv ~/.oldthing ~/old-dotfiles/
# use the machine for a week, then delete

Backups, which is the practical payoff

# config and data, excluding cache and runtime
restic backup \
  --exclude="$HOME/.cache" \
  --exclude="$HOME/.local/share/Trash" \
  --exclude="$HOME/.local/share/containers" \
  --exclude="$HOME/.var/app/*/cache" \
  "$HOME"

Flatpak applications keep their own tree at ~/.var/app/<id>/ with config, data and cache subdirectories mirroring the specification, which our Flatpak permissions guide covers. The cache directories in there are worth excluding too.

The specification is what makes an exclude list short and reliable. Without it you would be enumerating cache directories by name forever, which is precisely the situation on a machine full of programs that ignore it. Our automated backups guide covers the scheduling side.

Setting the variables

If you want non-default locations:

# ~/.profile, read by the session manager on most systems
export XDG_CONFIG_HOME="$HOME/.config"
export XDG_DATA_HOME="$HOME/.local/share"
export XDG_STATE_HOME="$HOME/.local/state"
export XDG_CACHE_HOME="$HOME/.cache"
# for graphical applications, which do not read your shell config
mkdir -p ~/.config/environment.d
cat > ~/.config/environment.d/xdg.conf <<'EOF'
XDG_CACHE_HOME=/scratch/cache
EOF

Setting them to their default values is harmless, and occasionally helps a program that implements the fallback badly rather than not at all.

Pointing XDG_CACHE_HOME at a different disk is a real use: a fast scratch drive, or somewhere excluded from a snapshot schedule so that cache churn does not fill your Btrfs snapshots.

XDG covers more than directories, and these come up often enough to know about.

# desktop entries, what appears in your application menu
ls ~/.local/share/applications/
ls /usr/share/applications/

# default application by MIME type
xdg-mime query default text/plain
xdg-mime default org.gnome.TextEditor.desktop text/plain

# open a file with whatever the default is
xdg-open report.pdf

# the user directories, Documents and Downloads
cat ~/.config/user-dirs.dirs
xdg-user-dir DOWNLOAD

xdg-open is worth using in scripts instead of naming a specific application, and xdg-user-dir is how you find someone’s Downloads directory on a system in a language you did not anticipate. Our locale guide covers why hardcoding ~/Downloads is a bug.

Frequently Asked Questions

What is the XDG Base Directory Specification?

A convention that splits per-user files into categories with a standard location for each: config in .config, data in .local/share, cache in .cache, and runtime files in a temporary directory. The point is that you can back up config and data while excluding cache, and know which is which.

What is the difference between .config and .local/share?

config holds settings you would write by hand or want in version control. local/share holds data the application manages for you, such as installed themes, saved state or a database. Config is portable between machines, data usually is not, and losing data loses work while losing config loses preferences.

Is it safe to delete the .cache directory?

Yes by definition, since anything there must be regenerable. In practice some applications misuse it for state they cannot recreate, so close your applications first and expect a slower first start afterwards while thumbnails and indexes rebuild.

Why do some programs still create dotfiles in my home directory?

Because the specification is a convention rather than something the system enforces, and the program predates it or its author chose not to follow it. Many accept an environment variable or a command line option to relocate their files, and some simply cannot be moved.

Should I set XDG_CONFIG_HOME myself?

Only if you want a non-default location. The specification defines the defaults, so unset variables are not a problem and applications are supposed to fall back correctly. Setting them to their default values is harmless and occasionally helps a program that implements the fallback badly.

What is XDG_RUNTIME_DIR for?

Short-lived files that must not outlive your login session: sockets, pipes, lock files, PID files. It is created by the session manager, owned by you with no access for anyone else, and removed when you log out. Nothing you want to keep should ever be written there.