Choosing Matplotlib's Agg Backend for Headless Figure Generation
When generating Matplotlib figures on headless servers, the backend choice silently controls rendering success. This article explains how to set the Agg backend, configure DPI, and avoid common failure modes.
13 Sept 2025, 07:08 UTC

Problem and Useful Takeaway
You run a Matplotlib script on a headless server to generate a figure, and the output is blank, missing text, or rasterized beyond recognition. The rendering backend silently chosen by Matplotlib determines whether your plot appears at all, and the default can conflict with automated environments. Useful takeaway: explicitly set the Agg backend for any script running without a display server, configure DPI upfront, and verify the output format matches your content type.
Requirements
- A headless environment (no X11, Wayland, or macOS Aqua) or a CI runner that does not start a display server.
- Python 3.9+ with matplotlib installed via
pip install matplotlib. - A decided target output format (PNG, PDF, or SVG) before the first plotting call.
Smallest Suitable Design
Import the Agg backend before any pyplot usage, then create and save a figure with explicit DPI:
#!/usr/bin/env python3 import matplotlib matplotlib.use('Agg') # must precede pyplot import import matplotlib.pyplot as plt plt.figure() plt.plot([1, 2, 3], [4, 5, 6]) plt.title('Server-side plot') plt.savefig('figure.png', dpi=300) plt.close()Running python -c 'import matplotlib; print(matplotlib.get_backend())' confirms the active backend before the script runs.
Trust / Data Boundaries
The Agg backend renders figures purely in Python without requiring a GUI stack. It supports raster formats such as PNG and PDF (via optional extensions), and vector output via the SVG backend. Color maps and normalization are handled by Matplotlib's internal modules; LaTeX math text requires either a full LaTeX installation or falls back to TextPath rendering, which increases SVG file size. Output format dictates DPI expectations: PNG benefits from 300 DPI for print, 72 DPI for web, while SVG is resolution-independent but may be slower for complex paths.
Operational Checks
- After generating a figure, verify the file with
python -c 'from PIL import Image; img = Image.open("figure.png"); print(img.size, img.mode)'to confirm dimensions and mode. - If LaTeX math was used, inspect the rendered text by opening the PNG in an editor or checking the console for fallback warnings.
- Compare the saved file's DPI against the requested value; a mismatch often indicates the backend default (100 DPI) was used instead of the explicit setting.
Failure Modes
- Running with a GUI backend (e.g.,
TkAgg) on a headless server throwsRuntimeError: Display not available. - Choosing the
PDFbackend without the required extension package may omit vector text, resulting in bitmap-like output. - High-DPI output without an explicit
dpiparameter yields the Matplotlib default of 100 DPI, which appears tiny on 4K displays. - LaTeX math text with
usetex=Trueand the Agg backend causes a silent fallback to MathText, potentially altering symbol shapes.
Conditions That Would Change the Design
If your pipeline requires true vector output for print production, switch to the PDF or SVG backend and validate path quality with a vector-graphics editor. For interactive Jupyter notebooks, keep the default inline backend; do not override to Agg. When integrating with CI pipelines that capture screenshots, enforce Agg plus explicit dpi to make output deterministic across runners.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.