Fixing COG Validation Errors in GDAL
You exported a GeoTIFF you believed was cloud-optimized, ran GDAL’s validate_cloud_optimized_geotiff.py (or rio cogeo validate), and it printed <file> is NOT a valid cloud optimized GeoTIFF followed by a list of concrete faults: The file is not tiled, The file does not have overviews, The offset of the main IFD should be after the offset of the overview IFDs, or Overview block size mismatch. The raster still opens in QGIS and draws correctly, which is exactly why this is confusing — the file is a valid GeoTIFF, it is just not laid out the way a range-request client needs. This page maps each error string to the byte-level cause and gives the minimal re-emit that fixes it deterministically.
Why GDAL rejects a GeoTIFF as “not a valid COG”
A COG is a GeoTIFF with three extra structural guarantees, and the validator checks each one independently. First, the pixels must be stored in internal tiles (square blocks), not the horizontal strips a default GeoTIFF uses — strips force a client to read a full image-width band to sample any tile. Second, the file must embed decimated overviews, a downsampled pyramid, so a zoomed-out request does not read full-resolution pixels. Third — the subtle one — the IFD ordering must place the overview image file directories and their tile offsets ahead of the main full-resolution image in the byte stream, so the very first HTTP range request lands on the header and pyramid index. A file can satisfy two of these and fail the third; the validator reports exactly which.
The reason a normal write fails ordering is mechanical: GDAL’s classic GTiff driver writes the main image first and appends overviews afterward (or builds them in a sidecar .ovr), so the offsets come out backwards for streaming even when tiling and overviews are both present. The dedicated COG driver exists precisely to reorganize the whole file on close so the offsets are correct. Getting this right is the export stage of the DEM/DSM raster export pipeline, and the full authoring path is covered in exporting Cloud-Optimized GeoTIFFs with Rasterio.
Minimal reproducible solution
Almost every validation error is fixed by the same move: re-emit the raster through GDAL’s COG driver, which enforces tiling, builds overviews, and fixes IFD ordering in one pass. The routine below runs the validator, and on failure re-emits with the correct options and re-validates. It is intentionally small so the fix is auditable.
from rio_cogeo.cogeo import cog_translate, cog_validate
from rio_cogeo.profiles import cog_profiles
def repair_cog(src_path: str, dst_path: str, *, is_float: bool = False) -> None:
"""Validate a raster and, if it fails, re-emit a correct COG."""
valid, errors, warnings = cog_validate(src_path) # structural check
if valid:
print(f"already a valid COG: warnings={warnings}")
return
print(f"invalid COG, re-emitting. errors={errors}")
# 'deflate' base profile: tiled 512, DEFLATE, correct IFD ordering.
profile = cog_profiles.get("deflate")
profile.update(blockxsize=512, blockysize=512)
# predictor 3 for float DEMs, 2 for integer rasters — this is a creation option.
creation = {"predictor": 3 if is_float else 2}
cog_translate(
src_path,
dst_path,
profile,
overview_resampling="average", # 'nearest' for categorical rasters
**creation,
)
ok, errs, warns = cog_validate(dst_path) # re-validate the output
assert ok, f"still invalid after re-emit: {errs}"
print(f"repaired -> {dst_path}, warnings={warns}")
cog_translate streams the source in windows, so it never loads the whole raster into RAM, and the cog_profiles.get("deflate") base already sets tiled=True with a 512 block — the two conditions that fix the not tiled and block size errors. Passing overview_resampling is what fixes the overviews missing error, and writing through this driver at all is what fixes the IFD ordering error. If you would rather drive this from the command line, gdal_translate -of COG -co COMPRESS=DEFLATE -co PREDICTOR=2 in.tif out.tif produces the identical layout.
Edge-case matrix
The exact validator message tells you which guarantee failed. Map it to the cause and the fix.
| Validator error | Root cause | Expected fix |
|---|---|---|
The file is not tiled |
Written as strips (no tiled=True) |
Re-emit via COG driver; base profile sets 512 tiling |
The file does not have overviews |
No embedded pyramid, or overviews in a sidecar .ovr |
Pass overview_resampling; COG driver embeds them internally |
The main IFD should be after the overview IFDs |
GTiff wrote main image before overviews | Re-emit via COG driver, which reorders IFDs on close |
Overview block size mismatch |
Overviews tiled at a different block size than the main image | Re-emit so overviews inherit the main 512 block size |
Missing NoData / mask (warning) |
NoData not written or mask not internal | Set nodata on export, or add an internal alpha/mask band |
The block size is not a power of two |
Non-standard blockxsize (e.g. 500) |
Set blockxsize=blockysize=512 |
The block-size mismatch case is the sneaky one: a file can be tiled and have overviews and still fail because the overviews were built at 128 while the main image is 512, so a client cannot use one tiling scheme for both. Re-emitting from source resolution through the COG driver forces both to the same block size.
Verification snippet
After the repair, assert the three structural guarantees independently so a regression tells you which one broke, not just that validation failed.
import rasterio
from rio_cogeo.cogeo import cog_validate
def assert_valid_cog(path: str) -> None:
with rasterio.open(path) as ds:
assert ds.profile["tiled"] is True, "not tiled"
assert ds.profile["blockxsize"] == 512, "unexpected block size"
assert len(ds.overviews(1)) >= 3, f"too few overviews: {ds.overviews(1)}"
ok, errors, _ = cog_validate(path)
assert ok, f"structural validation failed: {errors}"
print("COG verified:", ds.overviews(1))
Run this in CI as a hard gate. The overviews(1) list also confirms the pyramid depth — an empty list means the overviews missing error will recur, and fewer than three levels usually means the source was too small or the factors were truncated.
When to escalate
The re-emit above fixes structural (layout) faults. Escalate to the parent guide when the problem is not layout:
- The re-emit succeeds but the file is huge or slow — that is a compression/overview tuning problem, not a validity one. Work through codec, predictor, and pyramid depth in exporting Cloud-Optimized GeoTIFFs with Rasterio and the dedicated setting compression and overviews for orthomosaic COGs page.
cog_translateitself aborts on memory on a very large mosaic — the layout is fine but the process is exhausting RAM; see decoding GDAL “cannot allocate memory” on export.- You are choosing between the CLI validator and a scripted check for a whole export stage — the trade-offs live in GDAL vs Rasterio for DEM and orthomosaic export.