Klipper Quad Gantry Level and Z-Tilt Adjust: Getting a CoreXY Gantry Actually Level
Bed mesh calibration and input shaper get all the attention in most Klipper setup guides, but neither one fixes a fundamental problem that only shows up on CoreXY and H-bot machines: an out-of-parallel gantry. If your printer's X-axis rides on two independently driven Z motors (or four, on a machine like a Voron 2.4 or Trident), the gantry itself can be tilted or twisted relative to the bed even after a perfect bed mesh. Bed mesh compensates for a warped surface; it does not — and cannot — fix a gantry that isn't level to begin with. That's what QUAD_GANTRY_LEVEL and Z_TILT_ADJUST are for, and getting them dialed in is one of the most consequential things you can do for first-layer consistency on a multi-Z-motor printer.
Quad Gantry Level vs Z-Tilt Adjust vs Bed Mesh — What Each One Actually Does
FeatureWhat It AdjustsTypical PrintersWhen It Runs QUAD_GANTRY_LEVELIndependently drives 4 Z steppers to physically tilt the gantry until it's parallel to the bed, measured at 4 pointsVoron 2.4, Trident (4 Z motors)Before bed mesh, every print (fast) or on startup (slow, full) Z_TILT_ADJUSTSame concept but for printers with 2 or 3 independently driven Z motorsVoron Switchwire, many CoreXY kits, some bed-slinger conversionsBefore bed mesh BED_MESH_CALIBRATESoftware compensation for a bed surface that isn't perfectly flat — does not move any motorsAll Klipper printersAfter gantry leveling Z_ENDSTOP_CALIBRATE / single ZN/A — single motor Z has nothing to tiltBed-slinger printers (Ender, Kobra 3, Prusa)N/AThe order matters and Klipper enforces it: gantry leveling has to run before bed mesh, because bed mesh is measuring against a gantry that needs to already be square. Run them in the wrong order (or skip QGL) and your bed mesh will "correctly" compensate for a tilted gantry, which bakes the tilt into every print instead of removing it — first layers will look fine on one side and thin on the other in a way that no amount of Z-offset tweaking fixes.
Setting Up QUAD_GANTRY_LEVEL
QGL requires a Z endstop (or probe) that can be triggered at four separate XY positions near each corner of the gantry, plus four independently controlled Z stepper drivers defined as stepper_z, stepper_z1, stepper_z2, and stepper_z3 in printer.cfg. A minimal config block looks like this:
[quad_gantry_level] gantry_corners: -60,-10 360,370 points: 50,25 50,275 300,275 300,25 speed: 200 horizontal_move_z: 10 retries: 5 retry_tolerance: 0.0100 max_adjust: 10gantry_corners defines the physical rectangle of the gantry frame (used to calculate lead-screw adjustment ratios), while points are the actual XY locations where the probe touches down — these should sit as close to each of the four Z lead screws as your bed size allows without going past the usable print area. retries and retry_tolerance control how many passes QGL will run to converge; five retries at a 0.01mm tolerance is a reasonable starting point and rarely needs more than two or three passes once a machine is mechanically sound.
Running It and Reading the Output
Home the printer, then run QUAD_GANTRY_LEVEL from the console. Klipper will probe all four points, calculate how far out of plane the gantry is, and report something like:
Probe points: 0.123, 0.045, -0.089, 0.012 (z-offset from mean) Applying corrective offsets to Z motors Retry attempt 1 Probe points: 0.031, -0.008, 0.019, -0.005 Retry attempt 2 Probe points: 0.004, -0.002, 0.003, 0.001 Quad gantry leveling completeValues under about 0.01–0.02mm across all four points after convergence indicate a mechanically healthy gantry. If QGL needs more than 3–4 retries to converge, or the numbers oscillate instead of shrinking, that's a mechanical symptom, not a tuning problem — check for a bent gantry rod, a loose coupler between a Z motor and its lead screw, a lead screw that isn't running true, or a linear rail carriage that's binding. QGL can compensate for small amounts of physical tilt, but it's fighting the machine, not fixing it, and large or unstable corrections mean something upstream needs a wrench, not another retry.
Setting Up Z_TILT_ADJUST (2 or 3 Z Motors)
The config is nearly identical but simpler since there's no gantry rectangle to define — Klipper just needs the probe points and which Z stepper each one is nearest to:
[z_tilt] z_positions: -20, 20 150, 320 320, 20 points: 20,20 150,300 280,20 speed: 150 horizontal_move_z: 10 retries: 5 retry_tolerance: 0.0100z_positions are the physical coordinates of each Z motor/lead screw relative to the bed, used the same way gantry_corners is used in QGL — to translate a height error at a probe point into the correct amount of motor rotation at each screw.
Wiring It Into Your Start Sequence
Most macros run a fast QGL check every print and a full bed mesh only when the gantry has actually moved (after maintenance, a belt change, or transport):
[gcode_macro PRINT_START] gcode: G28 QUAD_GANTRY_LEVEL G28 Z BED_MESH_CALIBRATE G1 X150 Y150 Z10 F6000Note the second G28 Z after QGL — leveling the gantry changes the physical Z height relationship at the homing point, so re-homing Z afterward is what keeps your Z-offset and first-layer squish consistent print to print. Skipping this step is one of the most common causes of "my first layer height changes randomly" reports on CoreXY machines that otherwise have QGL configured correctly.
Troubleshooting Common Failures
SymptomLikely CauseFix QGL never converges below retry_toleranceBent gantry rod, binding linear rail, or a lead screw coupler slippingCheck each Z axis moves freely by hand-turning the motor with power off; inspect couplers for set-screw flats Corrections are large and inconsistent between runsProbe isn't repeating reliably (dirty nozzle, inductive probe not properly heat-soaked)Clean the probe/nozzle, add a probe repeatability check, let the machine thermally stabilize before probing First layer good on one side, thin on the other, despite QGL passingBed mesh ran before QGL, or Z wasn't re-homed after QGLFix macro order: home → QGL → home Z → bed mesh QGL takes a long time every single printFull 5-retry QGL running unconditionallyUse a conditional macro that only forces a full QGL after a flag file / after N hours, otherwise runs a single-pass checkA Note on Mechanical Health First
QGL and Z-tilt are compensation tools, not substitutes for a square, well-built gantry. On a fresh Voron 2.4 build, do a rough mechanical square-up with a straightedge and calipers before ever touching the Klipper config — measure diagonal gantry rod spacing at both ends, confirm all four Z lead screws are free-spinning by hand, and check belt tension is even side to side. QGL cleans up the last fraction of a millimeter of real-world imperfection; it isn't meant to correct a gantry that's several millimeters out of true, and asking it to will just produce noisy, unstable corrections that mask a problem you'll eventually have to fix by hand anyway.
Once QGL or Z-tilt is converging reliably and your start macro re-homes Z afterward, first-layer consistency across the full bed becomes almost a non-issue — the kind of "why is one corner always thin" problem that used to eat an entire evening of Z-offset fiddling usually turns out to be a gantry that was never actually level in the first place.
Related Guides
- How to Calibrate E-Steps and Flow Rate for Dimensional Accuracy
- How to Calibrate Input Shaper and Pressure Advance in Klipper
- OrcaSlicer Complete Guide: Every Setting Explained for Maximum Print Quality
- Pressure Advance Calibration for Better Print Quality
- Klipper Bed Mesh Calibration
- Anycubic Kobra 3 Complete Setup Guide: Unboxing, Calibration, and First Print