A plotting process can fail before it reaches savefig() when its backend expects a window system that the host does not provide. Scheduled jobs, CI runners, containers, and remote shells need a renderer that never depends on a desktop display.
Import order controls this script-level choice. Calling matplotlib.use(“Agg”) before importing matplotlib.pyplot fixes the backend before pyplot creates or manages any figures.
The Agg backend supplies the non-interactive FigureCanvasAgg canvas and writes raster images without an X or Wayland connection. A display-less run can therefore identify that canvas and read the saved PNG back from disk in the same process.
Related: How to save a Matplotlib figure
Related: How to set an interactive Matplotlib backend
Steps to select Agg before importing pyplot:
- Start headless_plot.py with Agg selected before the pyplot import.
- headless_plot.py
from pathlib import Path import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from matplotlib.backends.backend_agg import FigureCanvasAgg
- Create the plotting figure with one axes object below the imports.
fig, ax = plt.subplots(figsize=(8, 4.5), layout="constrained")
- Bind the figure's FigureCanvasAgg instance to canvas.
canvas = fig.canvas
- Reject any canvas class other than FigureCanvasAgg.
if not isinstance(canvas, FigureCanvasAgg): raise RuntimeError(f"Expected FigureCanvasAgg, got {type(canvas).__name__}")
Prove Agg rendering without a display:
- Export a labeled line plot from the figure to headless-plot.png.
output = Path("headless-plot.png") ax.plot([1, 2, 3, 4], [2, 4, 3, 5], marker="o") ax.set_title("Headless Matplotlib") ax.set_xlabel("Run") ax.set_ylabel("Value") fig.savefig(output, dpi=120)
- Read headless-plot.png back into Matplotlib.
image = plt.imread(output)
- Reject a decoded PNG whose dimensions differ from 960 by 540 pixels.
height, width = image.shape[:2] if (width, height) != (960, 540): raise RuntimeError(f"Expected 960 x 540 pixels, got {width} x {height}")
- Report the backend, canvas class, filename, and pixel dimensions.
canvas_name = type(canvas).__name__ print(f"backend: {matplotlib.get_backend()}") print(f"canvas: {canvas_name}") print(f"saved: {output.name}") print(f"pixels: {width} x {height}")
- Run headless_plot.py without desktop display variables to verify the Agg canvas and PNG readback.
$ env -u DISPLAY -u WAYLAND_DISPLAY python3 headless_plot.py backend: Agg canvas: FigureCanvasAgg saved: headless-plot.png pixels: 960 x 540
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.