What Is a Cloud Optimized GeoTIFF (COG)? How to Check One and How to Make One
A Cloud Optimized GeoTIFF is not a new file format. It is an ordinary GeoTIFF whose bytes are arranged so a program reading it over the network can fetch the few kilobytes it needs instead of the whole file. The same .tif still opens in QGIS, in ArcGIS, in Python, in anything that has ever opened a TIFF. A COG is a layout convention, not a format — worth saying first, because it is the most common misconception, and because a file that is not one is not broken, just arranged badly for the network.
Everything below was run on 11 September 2026 with GDAL 3.13.3 against real Sentinel-2 imagery. Where a claim comes from documentation instead of a run here, we say so.
What a COG actually is
Three properties turn a GeoTIFF into a COG, and all three live inside the one file.
- Internal tiling instead of strips. A desktop export stores the image as full-width strips — how many rows go into each one is the encoder's choice, and ours came out at one row per strip — so a small square in the middle costs hundreds of them. A COG stores square blocks, 512 pixels a side by default, so a window is a few contiguous reads.
- Overviews inside the same file. Reduced-resolution copies are written ahead of the full-resolution data — in a
-of COGfile the smallest pyramid level sits in the first few kilobytes — so a viewer needing a thumbnail reads the thumbnail instead of decoding 120 million pixels and discarding most of them. - A header a client can read in one or two requests. The image file directories — the tag blocks saying how big the image is and where each tile starts — sit at the front, so a client reads them once and then asks only for the tiles it wants.
On a real file: a Sentinel-2 true-colour asset from the open sentinel-cogs bucket reports LAYOUT=COG, COMPRESSION=DEFLATE, Band 1 Block=1024x1024 and Overviews: 5490x5490, 2745x2745, 1373x1373, 687x687. Write the same pixels back out with gdal_translate -of GTiff -co TILED=NO -co COMPRESS=NONE and that 10,980-pixel-square image becomes 361,747,558 bytes with Band 1 Block=10980x1: one strip per row, no overviews, no compression. Identical pixels, useless over a network.
How to check whether a GeoTIFF is a COG
The browser checker
The fastest check needs no install: drop the file on the COG checker. It reads the header in your browser with geotiff.js and reports four things. There is no URL box and nothing is uploaded — it sees only the file you drop.
- Georeferenced — the file carries a CRS, and a pass names it:
Georeferenced (EPSG:32719). - Internally tiled — square blocks rather than strips. A pass prints the block size,
Internally tiled (512×512).; a failure printsStriped, not tiled — tiling is required for a COG. - Overviews (pyramids) — reduced-resolution levels inside the same file, with the count. A failure prints
No overviews (pyramids) — required for a COG. - Compression — a recommendation, and the one that surprises people. It is a warning, never a failure. We built a tiled file with overviews and
-co COMPRESS=NONE, dropped it in, and the tool answered✓ Valid COGwith the compression line as a note readingUncompressed — valid, but DEFLATE/LZW would reduce file size.On a file the checker reads as compressed, that line names the codec instead.
So the verdict is the first three together. Miss any one and the file is not a COG; miss only the compression recommendation and it still is. "A COG must be compressed" does not belong in your checklist.
gdalinfo, and the three lines that are absences
With GDAL installed, gdalinfo file.tif answers the same question, and the trick is that every "no" is printed as nothing at all.
- Tiling is the
Block=value on each band.Block=512x512is tiled;Block=10980x1, the image width by one, is striped. - Overviews are an
Overviews:line under the band listing each level. With no pyramid,gdalinfodoes not say "none" — it prints no such line and the band stanza ends. - Compression is a
COMPRESSION=line inImage Structure Metadata. Uncompressed is the absence of that line, notCOMPRESSION=NONE.
One line that looks like a check and is not: OVR_RESAMPLING_ALG, which is copied from whatever file you converted. We measured two outputs built with different resampling that print the same tag and have different overview checksums.
The third option, in Python, is rio cogeo validate from the rio-cogeo plugin for rasterio. We did not run it — it is not installed here, so this sentence rests on its documentation, not on a transcript of ours.
How to make a COG with gdal_translate
GDAL ships a driver for this, so the conversion is one command: gdal_translate -of COG input.tif output.tif. That turned the 361,747,558-byte striped file above into a 2,232,769-byte COG in about three seconds, with Block=512x512, COMPRESSION=LZW and five overview levels down to 343 pixels square.
Two details there matter. The COG driver compresses by default — COMPRESS defaults to LZW, which is why that run reports COMPRESSION=LZW unasked, so the common claim that -of COG leaves you uncompressed is false on a modern GDAL. And it built five overview levels where the source had four, halving until the smallest fits one block: pyramid depth follows your image, not the input.
The creation options that matter
Each is a -co NAME=VALUE flag on that command, and every number is from a run on the same input.
- COMPRESS —
-co COMPRESS=DEFLATEfor lossless,-co COMPRESS=JPEGfor lossy 8-bit imagery,-co COMPRESS=WEBPfor the smallest lossy result. GDAL rewrites a JPEG request on RGB data intoCOMPRESSION=YCbCr JPEG; that is expected, and it is where most of JPEG's saving comes from. - BLOCKSIZE — the tile edge, 512 by default.
-co BLOCKSIZE=1024made the output smaller, 1,830,891 bytes against 2,232,769, because fewer, larger tiles mean less bookkeeping. The trade is granularity: a client wanting one pixel fetches a 1024-pixel square. - OVERVIEW_RESAMPLING — how the pyramid is downsampled, as in
-co OVERVIEW_RESAMPLING=NEAREST. Use nearest for categorical rasters such as land cover, where averaging two class codes invents a third; averaging suits continuous data. - OVERVIEWS —
-co OVERVIEWS=NONEsuppresses the pyramid, and it is here as a warning rather than a recommendation. - BIGTIFF —
-co BIGTIFF=YESswitches to 64-bit offsets, needed as a file nears the 4 GiB ceiling of classic TIFF. It is invisible ingdalinfoand visible in the file's first four bytes,II*for classic againstII+for BigTIFF. Forcing it cost 3,364 extra bytes here.
OVERVIEWS=NONE is the sharpest trap in the driver: GDAL still writes LAYOUT=COG, because it produced a COG-layout file that happens to have one resolution level, and the checker then fails it on overviews. The layout tag is not the verdict. Assert the overview count instead.
Compression, measured on one window
These six runs start from one 2048-pixel-square, three-band window of cloud-free desert imagery. They are not a universal table — a mostly-empty scene compresses far better, which is why the window is 100 % valid pixels.
- Uncompressed — 16,517,434 bytes, the baseline.
- LZW, the driver default — 15,929,384 bytes, barely 4 % off. LZW earns its keep on flat or synthetic rasters, not photography.
- DEFLATE — 13,094,841 bytes, 21 % off, and lossless: checksums match the original on all three bands.
- JPEG — 1,047,694 bytes at the default quality of 75, a 94 % cut, and lossy: the checksums differ on every band. At quality 90 it is 1,815,578 bytes, an 89 % cut with visibly less damage.
- WEBP — 828,166 bytes, the smallest of the six, and lossy by default.
The choice is about what the raster is for: lossless for anything you will measure, lossy for backdrop imagery a human only looks at.
Why it matters: the range request
All of this exists for one HTTP feature. A server answering Accept-Ranges: bytes lets a client ask for a byte span and get 206 Partial Content instead of the whole file, and a COG is the arrangement that makes those requests land on something useful. We measured it against a 313,765,265-byte Sentinel-2 COG read over /vsicurl/, with no local copy.
- Reading the entire header and overview list — size, CRS, block layout, every pyramid level — cost 2 GET requests and 16,716 bytes, or 0.005 % of the file.
- Extracting a 2048-pixel-square window from the middle cost 7 GET requests and 22,672,068 bytes, or 7.2 % of the file.
Against a striped, uncompressed GeoTIFF, both cost the whole file.
One caveat, about browsers. Our GeoTIFF viewer accepts a URL, and the URL box downloads the whole file before it renders anything. We confirmed it in a real browser: the first request is a plain GET with no Range header that returns 200 and pulls 100 % of the file, and only then does the renderer issue genuine 206 range reads — which cost zero extra bytes, because the body is already cached. So paste a URL when the file is small and download it when it is not. Dropping is the only mode the checker has anyway.
Test files you can use
Every URL here was requested on 11 September 2026 with a browser user agent, recording status, Accept-Ranges, the CORS header and the total size. A list like this rots, so read it as a snapshot of one day and one network.
One habit first: check the content type before calling a URL a raster. A 206 only proves the server supports byte ranges, and plenty of HTML pages answer 206 too — six did in our parallel elevation-source campaign. The rasters below all answer content-type: image/tiff.
- Sentinel-2 on AWS, found through a search and not a guessed key. The
sentinel-cogsbucket holds every Sentinel-2 Level-2A scene as COGs, open and CORS-open:206,accept-ranges: bytes,access-control-allow-origin: *. Do not copy a scene path out of a tutorial — scene ids encode a date, and a guessed neighbouring key returns404 NoSuchKey, which reads like a dead bucket and is really a wrong key. Query the STAC API instead: post a bounding box and a date range to the earth-search endpoint and use the asset hrefs it returns. The collection document answers200and sets noaccept-ranges, being a catalogue and not a file. Licence: Copernicus Sentinel data is free and open under the Copernicus terms, with attribution. - A small scene you can paste straight into the viewer. is 1,412,993 bytes. In a real browser it transferred 1,413,682 bytes over 2 requests:
✓ valid COG,4 tiles · EPSG:32723, 6.3 seconds. - A bigger one, also verified in a browser. is 5,810,809 bytes, and transferred 5,811,498 bytes over 5 requests:
✓ valid COG,84 tiles · EPSG:32723, 16 seconds. - An asset's size is a property of the scene, not of the band name. Same collection, same day:
TCI.tifwas 1,412,993 bytes over the São Paulo coast and 313,765,265 bytes over the Atacama, a 222-fold spread, because the first scene is 90 % cloud-masked nodata. Read the size out of the search result before pasting a URL; above roughly 64 MiB, download instead. - USGS 3DEP elevation, open and far too big to paste. answers
206withaccept-ranges: bytesandaccess-control-allow-origin: *, and is 222,936,410 bytes. A genuine COG, but the URL box would pull all 212.6 MiB first. Download and drop it. Licence: US public domain. - Copernicus GLO-30, which works everywhere except in a browser app. answers
206withaccept-ranges: bytesand is 44,155,932 bytes, but sends noaccess-control-allow-originheader. A plain HTTP client reads it happily; a browser page cannot. We pasted it into the viewer to be sure, and the browser refused at the CORS preflight with zero bytes crossing the wire. Licence: the Copernicus DEM terms in the bucket's readme, which require attribution. - OpenTopography, the friendliest host we measured. is a one-degree SRTM tile of 15,340,529 bytes:
206, cross-origin allowed, and the only host in this wave that also exposesContent-Rangeto page scripts. We have not loaded it in the viewer ourselves, so download and drop it for now. Licence: per dataset, on OpenTopography. - One that is dead, so you stop looking. The Maxar Open Data event index at
maxar-opendata.s3.amazonaws.com/events/index.htmlis still widely linked and returns404 NoSuchKeytoday, as do several NASA LP DAAC download directories in older tutorials. When an S3 path 404s, check whether the bucket answers before concluding the data is gone.
The elevation sources get a longer treatment, with resolution, vertical datum and coverage, in our list of free DEM and elevation data sources.
The errors decoded
When a tool refuses your raster or renders it painfully slowly, it is almost always one of these.
- Striped, not tiled.
Block=<width>x1ingdalinfo; the file must be read nearly end to end to yield any window. Fix it withgdal_translate -of COG. - Striped and large, which is when a viewer stops and asks. Our viewer sorts rasters into four tiers and only the striped-and-large one interrupts: above 100 megapixels, roughly 10,000 pixels square, a striped file gets the heading
This TIFF is not cloud-optimized, a Load anyway button and a convert-to-COG prompt. A 4-megapixel striped file renders unwarned, and so does a 120-megapixel file that is tiled but has no pyramid, labelledtiled, no overviews— the threshold is consulted only on the striped branch. - An external .ovr sidecar does not count. A pyramid built beside the file rather than inside it lands in a second object,
file.tif.ovr. Fine on a local disk, useless over HTTP: a client that fetchedfile.tifhas one object and the overviews are in another it has no reason to request. A browser checker cannot see a sidecar you did not drop. - JPEG where DEFLATE belonged. JPEG-in-TIFF is dramatically smaller — 1,047,694 bytes against 13,094,841 on our window — and it changes your pixel values. On elevation, reflectance or anything a class boundary depends on, that is data corruption with a smaller file size.
- The 4 GiB wall. Classic TIFF uses 32-bit offsets, so a file cannot exceed 4 GiB. Past that you need BigTIFF, with 64-bit offsets and a different magic number,
II+rather thanII*. Nothing ingdalinfoannounces it, so if a large file will not open in older software, check the magic bytes before blaming the data.
Serving COGs without running GDAL yourself
Converting one file is a command; converting every raster a field team uploads, keeping the pyramids current and serving the tiles is a pipeline, and that is the part Geodocs does. The two tools above are free and need no account: check a file, or open one on a map. For the convention itself, see the specification site and GDAL's COG driver page.