Deploy to production
This guide covers configuring Atlas v3 for production deployments. You can deploy Atlas v3 to any backend that supports HTTP range requests and CORS. This includes S3-compatible storage (e.g., AWS S3) or web servers like Nginx.
Download production tilesets
To download production tilesets with the Atlas installer, use the guided installation method (atlas-installer install) or run the following command:
$./atlas-installer download ./atlas-server \
--token <your-atlas-token> \
--version <Atlas version>
--tilesets all
The above command will download all tilesets granted to you by your Atlas license. Run atlas-installer download --help to see all options for the --tilesets flag. A full list of Atlas tilesets and sizes is in data and disk size in system requirements.
Atlas v3 directory structure
After downloading Atlas v3 using the Atlas installer, the atlas-server directory should look like this:
atlas-server/
├── data/
│ ├── fonts/
│ ├── sprites/
│ ├── styles/
│ └── mapbox/
│ ├── tilesets/
├── mapbox-gl-js/
│ ├── plugins/
└── mapbox-gl.css
└── mapbox-gl.js
Configure Mapbox GL JS and map styles
Before deploying to production, we recommend configuring Mapbox GL JS and map styles for your Atlas v3 deployment.
Deploy to production
Option 1 — Amazon S3 with CloudFront (recommended)
We recommend setting up Atlas v3 with AWS S3 or another S3-compatible storage option.
While you could serve Atlas v3 directly from AWS S3, every HTTP range request hits the same PMTiles object. Under high concurrency this exceed S3's per-prefix request limits. We recommend setting up CloudFront in front of S3 to absorb the load and to centralize CORS and caching.
1. Upload Atlas v3 assets to S3
$aws s3 sync ./atlas-server-files/ s3://<your-bucket>/ --delete
Use --exclude to keep the Atlas v3 bundle out of any public paths you may already be using.
2. Configure CORS
Configure the CORS policy below on the S3 bucket. Adjust AllowedOrigins for your domain.
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedOrigins": ["https://maps.example.com"],
"ExposeHeaders": ["ETag", "Content-Range", "Accept-Ranges"],
"MaxAgeSeconds": 3000
}
]
Content-Range and Accept-Ranges must be exposed. Mapbox GL JS relies on these headers to confirm that HTTP range support is available.
3. Create a CloudFront distribution
Set up an AWS CloudFront distribution with the following settings:
- Origin: Your S3 bucket (use Origin Access Control rather than a public bucket).
- Viewer protocol policy: Redirect HTTP to HTTPS.
- Allowed HTTP methods:
GET, HEAD. - Cache policy: Use a policy that forwards the
RangeandOriginrequest headers to the origin and includes them in the cache key.CachingOptimizedwith an attached origin request policy that forwards the two headers works. Otherwise, cloneCachingOptimizedand add them explicitly. - Response headers policy: Set
Access-Control-Allow-Originto your map hosting origin and echoContent-Range,Accept-Ranges, andCache-Control. - Default TTL: Tilesets and GL JS files are immutable per release. Long TTLs (hours to days) are safe if you version-stamp upgrades with a fresh path or an object replace.
4. Verify
$curl -I -H "Range: bytes=0-1023" https://<distribution>.cloudfront.net/data/<tileset>.pmtiles
A healthy response returns HTTP/2 206, Accept-Ranges: bytes, and Content-Range: bytes 0-1023/….
Option 2 — Nginx
Use the following nginx.conf file to configure the appropriate headers for Atlas v3:
server {
listen 443 ssl http2;
server_name maps.example.com;
root /srv/atlas-v3;
index index.html;
# ------------------------------------------------------------------
# CORS — required when serving data files from a different origin
# than the page hosting index.html. If everything is on the same
# origin you can remove the add_header and if ($request_method) block.
# ------------------------------------------------------------------
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
add_header Access-Control-Allow-Headers "Range" always;
add_header Access-Control-Expose-Headers "Content-Length, Content-Range" always;
# Handle CORS preflight requests
if ($request_method = OPTIONS) {
return 204;
}
# ------------------------------------------------------------------
# PMTiles — must support Range requests; do NOT gzip (breaks offsets)
# ------------------------------------------------------------------
location ~* \.pmtiles$ {
gzip off;
types { application/octet-stream pmtiles; }
add_header Accept-Ranges bytes always;
add_header Cache-Control "public, max-age=86400" always;
# Re-add CORS (location-level add_header overrides server-level)
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
add_header Access-Control-Allow-Headers "Range" always;
add_header Access-Control-Expose-Headers "Content-Length, Content-Range" always;
}
# ------------------------------------------------------------------
# Font glyphs (Protocol Buffer files)
# Files on disk are pre-gzipped; Content-Encoding tells the browser
# to decompress on the fly so Mapbox GL JS receives raw protobuf.
# ------------------------------------------------------------------
location ~* ^/data/fonts/.*\.pbf$ {
types { application/x-protobuf pbf; }
add_header Content-Encoding "gzip" always;
add_header Cache-Control "public, max-age=604800" always;
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
}
# ------------------------------------------------------------------
# Sprites and style JSON
# ------------------------------------------------------------------
location ~* \.(json|png)$ {
add_header Cache-Control "public, max-age=86400" always;
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
}
}
Reload Nginx and test with the same curl -I -H "Range: …" check shown above.
Other backend options
Any backend that returns 206 Partial Content for Range requests and supports CORS will work, including S3-compatible storage options.
- MinIO — Configure CORS with
mc admin config. - Ceph (RGW) — Enable CORS at the bucket level.
- Local Nginx or Caddy for air gapped environments.
Update Atlas
To update Atlas, download the latest Atlas bundle and tilesets:
- Run
./atlas-installer download <destination-directory> --token <your-atlas-token> --version <latest Atlas version>to fetch the latest tilesets, assets, and GL JS. - (Optional, but highly recommended) Validate the new bundle and tilesets in a staging environment.
- Sync the new directory to your production server (e.g.,
aws s3 sync,rsync, etc.). If you rely on immutable caching, keep the old version available during the update as a fallback.
The rollback process is the inverse - re-upload your previous directory. Because Mapbox GL JS and the style.json are part of the Atlas bundle, the rendered map always matches the assets you are serving.
Troubleshooting
The map stays blank. Open the browser developer tools network tab and look at the .pmtiles requests. Each request should return 206 Partial Content. If you see 200 OK instead, HTTP range support is not enabled on your storage or CDN.
CORS errors. Confirm that your storage and CDN both send Access-Control-Allow-Origin for the origin you are serving the page from. CloudFront in particular needs an explicit response headers policy — inherited S3 CORS alone is not enough.
S3 rate-limit errors under load. Put CloudFront in front of S3 (or a similar CDN) and increase cache TTLs. The bundle is immutable per Atlas release, so long TTLs are safe.