FFmpeg fontconfig error isn't fatal. It ships the wrong font.

Your caption burn works on your laptop. You push the same command into a container or a CI runner, FFmpeg prints Fontconfig error: Cannot load default config file, and the job exits 0. The pipeline reports success, the file plays, and the subtitles are in a font nobody chose. Sometimes they're empty boxes.
Quick answer: "Fontconfig error: Cannot load default config file" is a non-fatal warning from libass, the subtitle renderer FFmpeg uses for thesubtitlesandassfilters. Your image has FFmpeg but no fontconfig configuration and no installed font files, so captions render in a fallback face or not at all while FFmpeg still exits 0. Fix the FFmpeg fontconfig error by installingfontconfigplus a real font package in the image (apk add fontconfig font-dejavuon Alpine,apt-get install fontconfig fonts-dejavu-coreon Debian slim), runningfc-cache -fat build time, and naming the font explicitly withforce_style='FontName=DejaVu Sans'. If you'd rather not maintain a font image at all, FFmpeg Micro burns captions on a hosted font stack in one API call.
What the fontconfig error means
The message comes from fontconfig itself, not from FFmpeg. When libass turns the string "Arial" in a subtitle style into a glyph outline, it asks fontconfig, which looks for /etc/fonts/fonts.conf. Slim base images don't ship one. The library prints to stderr and returns a built-in fallback configuration, which on a bare image points at empty directories.
You usually see two lines together:
Fontconfig error: Cannot load default config file: No such file or directory
Fontconfig warning: using without calling FcInit()
No usable fontconfig configuration file found, using fallback.
That third line is libass admitting it's guessing. The report shows up in downstream trackers rather than in FFmpeg's own docs: jrottenberg/ffmpeg issue #328 collects it across Alpine and CentOS base images, the ffmpeg-kit Tips wiki documents the same missing-fontconfig behavior for mobile builds, and VideoHelp thread #371053 works through the same failure with a broken config file instead of a missing one. The FFmpeg error wiki has nothing on it.
Why the job exits 0 and ships the wrong font
Missing fonts are a warning in FFmpeg's model, not an error, because libass always has something to draw with. The encode completes, the muxer writes a valid MP4, and the process returns 0. Any pipeline that branches on exit code marks the render green.
Two things happen downstream. With Latin text you get a substituted face: FFmpeg's subtitles filter wraps an SRT in a default ASS header that asks for Arial at size 16 against a 384x288 reference resolution, and since no Linux base image ships Arial, whatever fontconfig hands back is what viewers see. With non-Latin text, emoji, or a font with no reasonable substitute, you get blank rectangles or nothing at all, on a file your workflow already uploaded.
A faceless-channel builder rendering 200 captioned clips a night doesn't watch 200 clips. They find out when a viewer mentions the Japanese title card is blank, three weeks of uploads later.
Catch the warning in stderr before the render ships
Grep stderr and fail the job yourself, because the exit code never will. Fontconfig writes these lines straight to the file descriptor, not through FFmpeg's logging system, so -loglevel quiet and -loglevel error don't suppress them and a grep works at any verbosity.
ffmpeg -hide_banner -i in.mp4 \
-vf "subtitles=subs.srt" -c:a copy out.mp4 2> render.log
status=$?
if grep -qE "Fontconfig error|No usable fontconfig|Glyph .* not found" render.log; then
echo "font stack broken in this image, refusing to publish" >&2
exit 1
fi
exit $status
Glyph 0x... not found is the third string worth catching. libass prints it when the font it found has no glyph for a character, which is the emoji and CJK case. In n8n or Make, the same check is a filter on the Execute Command node's stderr before the upload step.
Better, assert it at build time so a broken image never ships. fc-match resolves a font name the way libass will:
RUN fc-cache -f && fc-match "DejaVu Sans" | grep -q "DejaVu"
Install fontconfig and a font in Alpine and Debian slim
Two packages fix this in almost every image: the fontconfig library plus at least one font family. FFmpeg alone gets you the renderer with nothing to render with.
FROM alpine:3.20
RUN apk add --no-cache ffmpeg fontconfig font-dejavu font-noto \
&& fc-cache -f
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ffmpeg fontconfig fonts-dejavu-core fonts-noto-core \
&& fc-cache -f \
&& rm -rf /var/lib/apt/lists/*
Pick the font package by what your captions contain, and watch the size: full Unicode coverage isn't free.
| Coverage | Alpine | Debian/Ubuntu | Rough installed size |
|---|---|---|---|
| Latin, Cyrillic, Greek | `font-dejavu` | `fonts-dejavu-core` | ~3 MB |
| Metric-compatible with Arial | `font-liberation` | `fonts-liberation` | ~5 MB |
| Broad Unicode, no CJK | `font-noto` | `fonts-noto-core` | ~30 MB |
| Chinese, Japanese, Korean | `font-noto-cjk` | `fonts-noto-cjk` | 100 MB+ |
| Color emoji | `font-noto-emoji` | `fonts-noto-color-emoji` | ~10 MB |
If you only need one CJK weight, copy a single .otf into the image instead of installing the whole family. The difference between a 250 MB layer and a 15 MB one is one COPY line.
The awkward case is a distroless or scratch base, the default problem for anyone on n8n's official image since it went distroless in 2026 and lost apk. A static FFmpeg build has libass and fontconfig compiled in and still reads /etc/fonts at runtime, so you stage the font tree in an earlier layer and copy it across:
FROM debian:bookworm-slim AS fonts
RUN apt-get update && apt-get install -y --no-install-recommends \
fontconfig fonts-dejavu-core && fc-cache -f
FROM gcr.io/distroless/base-debian12
COPY --from=fonts /etc/fonts /etc/fonts
COPY --from=fonts /usr/share/fonts /usr/share/fonts
COPY --from=fonts /var/cache/fontconfig /var/cache/fontconfig
ENV FONTCONFIG_PATH=/etc/fonts
None of this is hard, and all of it is yours to maintain: every base image bump, every new language your captions support, every rebuild that quietly drops a package. The alternative is not building the image at all. FFmpeg Micro's Auto Captions blueprint transcribes and burns subtitles on a hosted font stack with a review step before the burn, so the font question is answered once instead of once per Dockerfile.
Pin the font with force_style and fontsdir
Installing a font is half the fix: libass renders whatever name the subtitle style asks for, and an SRT has no styling at all. Name the font yourself with force_style on the subtitles filter instead of trusting fontconfig's substitution table:
ffmpeg -i in.mp4 -vf "subtitles=subs.srt:fontsdir=/opt/fonts:\
force_style='FontName=DejaVu Sans,FontSize=28,PrimaryColour=&H00FFFFFF,\
OutlineColour=&H64000000,BorderStyle=3,MarginV=60'" -c:a copy out.mp4
fontsdir points libass at a directory of font files that aren't installed system-wide, which is how you ship a brand font without touching the OS font path. ASS colors are &HAABBGGRR with inverted alpha, so &H00FFFFFF is opaque white and &H64000000 is a semi-transparent black outline. FontName must match the family name inside the font file, not the filename.
The subtitles filter and the ass filter behave differently here. subtitles accepts SRT, VTT, and ASS, converts non-ASS input using FFmpeg's default header, and supports force_style. The ass filter takes an .ass file and renders its Style: lines verbatim, with no force_style option at all. If you're using ass, the font name lives in your subtitle file and you edit it there. Either way, fontconfig still has to resolve that name to a file on disk.
For the full transcribe-then-burn path, Auto-Generate and Burn In Captions with Whisper + FFmpeg covers the pipeline end to end.
Embed the font in the image, don't mount it
Embedding beats mounting for anything that runs unattended. A COPY ./fonts/ /usr/share/fonts/truetype/brand/ plus fc-cache -f produces an image that renders identically on every machine, and the font travels with the code that needs it.
Bind-mounting the host's /usr/share/fonts or /etc/fonts into a container works on your laptop and breaks in CI, because the host's fontconfig version and the container's rarely match. VideoHelp thread #371053 shows that second failure mode: a config file written for one fontconfig release read by another, producing Fontconfig warning: ... invalid attribute 'mode' and a config that half-parses. Mount fonts only for local iteration, when you're testing five typefaces and don't want five rebuilds.
Common pitfalls
Five font problems come up again and again, and each produces a silently wrong render rather than a clean failure:
drawtextwithfont=needs fontconfig too.drawtext=font=Arialgoes through the same lookup and fails the same way.drawtext=fontfile=/opt/fonts/Inter.ttfreads the file directly through FreeType and never touches fontconfig, the safer choice for hardcoded overlays.- Skipping
fc-cacheat build time. Fontconfig tries to build its cache on first use instead. On a read-only container filesystem that write fails, and the font you copied in never gets matched. - Colons and quotes in the subtitles path. The filter parser eats
:as an option separator, so/data/my:clip/subs.srtbreaks the filter graph. Escape it assubtitles=/data/my\\:clip/subs.srtor, easier,cdinto the directory and pass a bare filename. - Testing on macOS and shipping to Linux. A Homebrew FFmpeg install has a working fontconfig setup and hundreds of system fonts, including real Helvetica.
debian:bookworm-slimhas zero fonts. The command is identical; the output isn't. - Assuming a font with the glyphs also has the emoji. DejaVu covers Latin, Cyrillic, and Greek and has no CJK or color emoji. Missing glyphs surface as
Glyph 0x... not foundin stderr, or as boxes in the video if you weren't reading stderr.
FAQ
Why does FFmpeg say "No usable fontconfig configuration file found, using fallback"?
FFmpeg prints "No usable fontconfig configuration file found, using fallback" when libass asked fontconfig for a font and fontconfig had no /etc/fonts/fonts.conf. Install the fontconfig package, which ships that file, plus at least one font family.
Will FFmpeg fail the job if the font is missing?
FFmpeg does not fail on a missing font. It logs the fontconfig warning, renders the subtitles with a fallback face or with blank glyphs, and exits with status 0, so any exit-code check passes. Grep stderr for Fontconfig error, No usable fontconfig, and Glyph .* not found if you need the job to stop.
Which font package do I install in an Alpine image for burning subtitles?
Alpine needs fontconfig plus a font package: apk add --no-cache fontconfig font-dejavu covers Latin, Cyrillic, and Greek in about 3 MB. Older Alpine releases named that package ttf-dejavu. Add font-noto-cjk for Chinese, Japanese, or Korean, and run fc-cache -f in the same layer.
Why are my subtitles in the wrong font in Docker when there's no error at all?
Wrong-font-with-no-error means fontconfig is installed and working, and it substituted a face for one that isn't there. The subtitles filter defaults to Arial at size 16 for SRT input, no Linux image ships Arial, and fontconfig quietly returns its closest match. Pass force_style='FontName=...' naming a font you actually installed.
How do I burn captions with emoji or Japanese text?
Emoji and Japanese captions need a font that contains those glyphs, which DejaVu does not: install fonts-noto-cjk and fonts-noto-color-emoji on Debian, or font-noto-cjk and font-noto-emoji on Alpine, then set that family in force_style. Color emoji also require a libass build with color bitmap support; older builds render them as monochrome outlines or drop them.
Every fix above is an image you own, rebuild, and re-verify each time the base changes. If the font stack isn't the part of your product you want to maintain, hand the burn to a hosted FFmpeg API instead: sign up free, send the video and the SRT, and get back a captioned file rendered against a font stack that already works. It works the same way from code, from n8n, Make, and Zapier, and from an AI agent.
About Javid Jamae
Founder & CEO at FFmpeg Micro
Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.
You might also like

Auto-Generate and Burn In Captions with Whisper + FFmpeg (No Server Required)
Whisper FFmpeg burn in captions API: transcribe to SRT and burn subtitles into video with two HTTP calls. No GPU, no local FFmpeg build, no 25 MB cap.

FFmpeg Invalid Data Found Isn't a Corrupt Video. Validate Input.
FFmpeg invalid data found when processing input rarely means a corrupt video. The real causes on files you didn't create, plus a copy-paste ffprobe guard.

MP4 Not Playing in Browser? Four Encoder Flags Fix It
MP4 not playing in browser? VLC decodes almost anything, hiding the four real breakers: pixel format, H.264 profile/level, moov placement, audio codec.
Skip the command line
The Auto Captions blueprint transcribes your video and burns the captions in. You just review the transcript.
Run it (free)