> For the complete documentation index, see [llms.txt](https://docs.mapbox.com/atlas/llms.txt)

# 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:

```bash
$ ./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](https://docs.mapbox.com/atlas/guides/#data-and-disk-size) in [system requirements](https://docs.mapbox.com/atlas/guides/#system-requirements).

## Atlas v3 directory structure

After downloading Atlas v3 using the [Atlas installer](https://docs.mapbox.com/atlas/guides/install#run-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](https://docs.mapbox.com/atlas/guides/gl-js/) and [map styles](https://docs.mapbox.com/atlas/guides/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

```bash
$ 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. Set the content encoding on font glyphs

The font glyph files in `data/fonts/` are gzip compressed, so your server must send a `Content-Encoding: gzip` response header for them.

```bash
$ aws s3 cp ./atlas-server-files/data/fonts/ s3://<your-bucket>/data/fonts/ \
    --recursive \
    --content-encoding gzip \
    --content-type application/x-protobuf
```

#### 3. Configure CORS

Configure the CORS policy below on the S3 bucket. Adjust `AllowedOrigins` for your domain.

```json
[
  {
    "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.

#### 4. 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 `Range` and `Origin` request headers to the origin and includes them in the cache key. `CachingOptimized` with an attached origin request policy that forwards the two headers works. Otherwise, clone `CachingOptimized` and add them explicitly.
-   **Response headers policy**: Set `Access-Control-Allow-Origin` to your map hosting origin and echo `Content-Range`, `Accept-Ranges`, and `Cache-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.

Confirm that the distribution passes the origin's `Content-Encoding` header through to the browser unchanged. A CDN that strips or rewrites it will break map labels even though the S3 objects are correct.

#### 5. Verify

```bash
$ 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/…`.

Check the font glyphs as well:

```bash
$ curl -I "https://<distribution>.cloudfront.net/data/fonts/mapbox/DIN%20Pro%20Regular/0-255.pbf"
```

This response must include `Content-Encoding: gzip`.

### Option 2 — Nginx

Use the following `nginx.conf` file to configure the appropriate headers for Atlas v3:

```nginx
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:

1.  Run `./atlas-installer download <destination-directory> --token <your-atlas-token> --version <latest Atlas version>` to fetch the latest tilesets, assets, and GL JS.
2.  (Optional, but highly recommended) Validate the new bundle and tilesets in a staging environment.
3.  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

### Map labels do not render

The map renders but labels are missing. Check a glyph file:

```bash
$ curl -I "https://<your-host>/data/fonts/mapbox/DIN%20Pro%20Regular/0-255.pbf"
```

If `Content-Encoding: gzip` is missing, configure your backend to send it:

-   **Amazon S3**: [Set the content encoding on font glyphs](#set-the-content-encoding-on-font-glyphs).
-   **Nginx**: add the `location ~* ^/data/fonts/.*\.pbf$` block shown in [Option 2 — Nginx](#option-2-nginx).
-   **CloudFront or another CDN**: forward the origin's `Content-Encoding` header unchanged.

If the header is present, check that the `glyphs` URL in your style file points at your own server rather than `mapbox://`.

### 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.