Tune Fontconfig with a Per-User fonts.conf
A per-user fonts.conf lets you add a private font directory and alias one family, without ever touching the system-wide /etc/fonts/fonts.conf. Package updates overwrite that file anyway, so this is the safer place to work. It takes about 10 minutes and was checked with Fontconfig 2.15.0, Ubuntu's fontconfig-config package version 2.15.0-1.1ubuntu2.
The route
Jump straight to the step you need, or tick off Done means at the end.
Before you start
You need a shell, a text editor, and a font file you are actually allowed to use. This guide assumes the normal XDG location, $XDG_CONFIG_HOME/fontconfig/fonts.conf, which falls back to ~/.config/fontconfig/fonts.conf when XDG_CONFIG_HOME is unset. Fontconfig also accepts an explicit FONTCONFIG_FILE, which is handy for testing before you commit to anything.
Warning
Do not edit /etc/fonts/fonts.conf. The local file itself states that package updates replace it outright. System-wide changes belong in the administrator-managed configuration; a personal preference belongs in the user file or a numbered file in the user conf.d directory.
1. Check the active installation
Record the version and the current answer for a generic family first. None of these need elevated privileges, and none change configuration:
fc-match --version
fc-match -f '%{family}\n' sans-serif | head -n 1
fc-list : family | head -n 5
On the system used here, the version is fontconfig version 2.15.0. The selected family is host-dependent, so a result like DejaVu Sans is normal. Fontconfig always returns the nearest available match, so success here does not prove the family you actually asked for is installed.
Checkpoint
Keep this original fc-match result so you can compare it against the result after your override.
2. Create a private font directory
Make a directory under the XDG data location. This is ordinary user state, so sudo has no business here:
mkdir -p "${XDG_DATA_HOME:-$HOME/.local/share}/fonts"
printf '%s\n' "${XDG_DATA_HOME:-$HOME/.local/share}/fonts"
Copy or move your licensed font files into that directory. Replace the placeholder below with a real file; do not type the angle brackets:
cp /path/to/YourFont.ttf "${XDG_DATA_HOME:-$HOME/.local/share}/fonts/"
Fontconfig's <dir prefix="xdg">fonts</dir> resolves fonts below $XDG_DATA_HOME; if that variable is unset, the usual fallback is ~/.local/share/fonts.
3. Add the user XML configuration
Create the parent directory, then open the file in your editor of choice. This example uses nano, but the editor itself carries no privilege here:
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/fontconfig"
nano "${XDG_CONFIG_HOME:-$HOME/.config}/fontconfig/fonts.conf"
Paste this complete XML document. The alias makes a request for Work Sans prefer DejaVu Sans, then keeps sans-serif as a fallback. Only swap those two family names for names fc-list : family actually returns on your machine:
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
<fontconfig>
<dir prefix="xdg">fonts</dir>
<alias>
<family>Work Sans</family>
<prefer>
<family>DejaVu Sans</family>
</prefer>
<default>
<family>sans-serif</family>
</default>
</alias>
</fontconfig>
The document needs exactly one <fontconfig> root element. <dir> adds a directory of font files, <alias> edits the family list used during matching. This is configuration, not an installer: a missing font file or a typo in a family name will not be fixed by the XML itself.
4. Test the file without touching the system
Ask Fontconfig to parse the file explicitly. The FONTCONFIG_FILE setting applies only to this one command, which makes it a safe first test:
FONTCONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/fontconfig/fonts.conf" \
fc-match -f '%{family}\n' 'Work Sans'
With the example above, the first family should be DejaVu Sans. If the command reports a configuration warning, look for a missing closing tag, a misspelt element, or an unescaped < character. Use a real family name in the test, since Fontconfig can quietly return a fallback for an unknown request and hide a typo from you.
Check that the private directory is visible under the same temporary configuration:
FONTCONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/fontconfig/fonts.conf" \
fc-list --format '%{file}\n' | grep -F "${XDG_DATA_HOME:-$HOME/.local/share}/fonts/" | head
A matching path confirms the directory was actually scanned. No output usually means the directory is empty, the file is not a supported font, or the cache has not caught up yet.
5. Refresh and verify normally
Fontconfig rescans configuration periodically, but its font cache can still make a newly copied file appear late. Refresh only your user cache with fc-cache; it needs no sudo:
fc-cache -f
fc-match -f '%{family}\n' 'Work Sans'
fc-list --format '%{family}\n' | sort -u | grep -F 'DejaVu Sans' | head -n 1
Applications that already loaded their own font list may need restarting: a browser tab or terminal open before the change can keep using its old selection.
Common traps and recovery
~/.fonts.conf and ~/.fonts.conf.d are deprecated conventional locations in Fontconfig 2.15.0; use the XDG paths instead. Inside a conf.d directory, only files beginning with an ASCII digit and ending in .conf load, in sorted order, so a file named custom.conf is easy to miss entirely.
Configuration loads in order, and a later rule can override the family list an earlier rule produced; an alias never manufactures a font that is not actually installed. If an override causes unexpected matches, undo it by removing the user file and clearing the user cache:
rm "${XDG_CONFIG_HOME:-$HOME/.config}/fontconfig/fonts.conf"
fc-cache -f
Recovery
That removal is irreversible unless you kept a copy, so check the path before you run it. To preserve other settings, edit the file instead and remove only the <dir> or <alias> block you added.
Done means
- Version confirmed.
fc-match --versionreports the Fontconfig build you expected. - Font directory in place, under the XDG data path and containing the intended file.
- XML file valid, under the XDG Fontconfig path with exactly one
<fontconfig>root. - Alias verified.
fc-matchreturns the preferred family you configured. - Rollback known, so you can remove or edit the user file if matching goes wrong.