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
from pathlib import Path import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from matplotlib.backends.backend_agg import FigureCanvasAgg
fig, ax = plt.subplots(figsize=(8, 4.5), layout="constrained")
canvas = fig.canvas
if not isinstance(canvas, FigureCanvasAgg): raise RuntimeError(f"Expected FigureCanvasAgg, got {type(canvas).__name__}")
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)
image = plt.imread(output)
height, width = image.shape[:2] if (width, height) != (960, 540): raise RuntimeError(f"Expected 960 x 540 pixels, got {width} x {height}")
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}")
$ env -u DISPLAY -u WAYLAND_DISPLAY python3 headless_plot.py backend: Agg canvas: FigureCanvasAgg saved: headless-plot.png pixels: 960 x 540