Guides

Serving Vector Tiles

Guide to serving PMTiles and MBTiles with tileserver-rs

This guide covers everything you need to know about serving vector tiles with tileserver-rs, including the differences between PMTiles and MBTiles, how to obtain tile data, and configuration options.

Tile Formats

tileserver-rs supports two vector tile container formats:

FormatBest ForFile Extension
PMTilesProduction, CDN, cloud storage.pmtiles
MBTilesDevelopment, local use, SQLite tools.mbtiles

PMTiles

PMTiles is a cloud-optimized format designed for efficient HTTP range requests. Benefits:

  • Single file - No database, just a binary file
  • Cloud-native - Works directly from S3, GCS, Azure Blob, or any HTTP server
  • Efficient - Only downloads the tiles you need via range requests
  • Fast - Optimized internal structure for quick tile lookups
[[sources]]
id = "world"
type = "pmtiles"
path = "/data/world.pmtiles"
# Or serve from HTTP (requires --features http)
# path = "https://example.com/tiles/world.pmtiles"

MBTiles

MBTiles is an SQLite-based format. Benefits:

  • Mature tooling - Wide ecosystem of tools for creating and editing
  • Inspectable - Use any SQLite client to browse contents
  • Metadata - Rich metadata support in the database
[[sources]]
id = "local"
type = "mbtiles"
path = "/data/local.mbtiles"

Obtaining Tile Data

Option 1: Protomaps Downloads

Protomaps provides free OpenStreetMap extracts in PMTiles format:

# Download a city extract (small, good for testing)
curl -L -o florence.pmtiles \
  "https://build.protomaps.com/20231211/florence.pmtiles"

# Download a country extract
curl -L -o switzerland.pmtiles \
  "https://build.protomaps.com/20231211/switzerland.pmtiles"
Check protomaps.com/downloads for the latest extracts and the full planet file.

Option 2: OpenMapTiles

OpenMapTiles provides pre-built MBTiles files:

# Download from OpenMapTiles (requires account for larger areas)
# Small extracts are available for free

Option 3: Generate Your Own

From OpenStreetMap with Planetiler

Planetiler is the fastest way to generate tiles from OSM data:

# Download and run Planetiler
wget https://github.com/onthegomap/planetiler/releases/latest/download/planetiler.jar

# Generate PMTiles for a region
java -Xmx8g -jar planetiler.jar \
  --download --area=switzerland \
  --output=switzerland.pmtiles

From OpenStreetMap with tilemaker

tilemaker is a lightweight alternative:

# Install tilemaker
brew install tilemaker  # macOS

# Download OSM data
curl -O https://download.geofabrik.de/europe/switzerland-latest.osm.pbf

# Generate MBTiles
tilemaker --input switzerland-latest.osm.pbf --output switzerland.mbtiles

Convert MBTiles to PMTiles

Use the pmtiles CLI to convert between formats:

# Install pmtiles CLI
npm install -g pmtiles
# or
go install github.com/protomaps/go-pmtiles/cmd/pmtiles@latest

# Convert MBTiles to PMTiles
pmtiles convert input.mbtiles output.pmtiles

# Convert PMTiles to MBTiles
pmtiles convert input.pmtiles output.mbtiles

Configuration

Basic Configuration

# config.toml
[[sources]]
id = "osm"
type = "pmtiles"
path = "/data/osm.pmtiles"
name = "OpenStreetMap"
attribution = "© OpenStreetMap contributors"

Multiple Sources

Serve multiple tile sources from one server:

[[sources]]
id = "basemap"
type = "pmtiles"
path = "/data/basemap.pmtiles"
name = "Base Map"

[[sources]]
id = "terrain"
type = "mbtiles"
path = "/data/terrain.mbtiles"
name = "Terrain"

[[sources]]
id = "buildings"
type = "pmtiles"
path = "/data/buildings.pmtiles"
name = "3D Buildings"

Each source is accessible at its own endpoint:

  • /data/basemap/{z}/{x}/{y}.pbf
  • /data/terrain/{z}/{x}/{y}.pbf
  • /data/buildings/{z}/{x}/{y}.pbf

HTTP PMTiles (Remote Files)

Serve PMTiles directly from HTTP/HTTPS URLs:

[[sources]]
id = "remote"
type = "pmtiles"
path = "https://example.com/tiles/world.pmtiles"
name = "Remote Tiles"
HTTP PMTiles requires the server at the URL to support HTTP range requests. Most CDNs and cloud storage providers support this.

TileJSON Metadata

tileserver-rs automatically generates TileJSON 3.0 metadata for each source:

# Get metadata for a source
curl http://localhost:8080/data/osm.json
{
  "tilejson": "3.0.0",
  "name": "OpenStreetMap",
  "attribution": "© OpenStreetMap contributors",
  "tiles": ["http://localhost:8080/data/osm/{z}/{x}/{y}.pbf"],
  "minzoom": 0,
  "maxzoom": 14,
  "bounds": [-180, -85.0511, 180, 85.0511],
  "center": [0, 0, 2],
  "vector_layers": [
    {
      "id": "water",
      "fields": { "class": "string" }
    },
    {
      "id": "transportation",
      "fields": { "class": "string", "name": "string" }
    }
  ]
}

Tile Endpoints

Get a Vector Tile

GET /data/{source}/{z}/{x}/{y}.pbf
# Fetch a tile
curl http://localhost:8080/data/osm/10/544/373.pbf -o tile.pbf

# Check response headers
curl -I http://localhost:8080/data/osm/10/544/373.pbf
# Content-Type: application/x-protobuf
# Content-Encoding: gzip
# Cache-Control: public, max-age=86400

Response Codes

CodeMeaning
200Tile found and returned
204Tile exists but is empty (no data in this area)
404Source not found or coordinates outside bounds

Tile as GeoJSON (Debugging)

Convert a tile to GeoJSON for inspection:

curl http://localhost:8080/data/osm/14/8704/5972.geojson | jq .

Performance Optimization

1. Use PMTiles for Production

PMTiles has better read performance than MBTiles for most use cases:

MetricPMTilesMBTiles
Random access~1,300 req/s~750 req/s
Sequential accessSimilarSimilar
Cloud servingExcellentPoor

2. Enable Compression

Tiles are typically gzip-compressed. tileserver-rs handles this automatically:

  • Compressed tiles are served with Content-Encoding: gzip
  • Uncompressed tiles are compressed on-the-fly if the client supports it

3. Use Appropriate Zoom Levels

Don't serve more zoom levels than you need:

[[sources]]
id = "overview"
type = "pmtiles"
path = "/data/overview.pmtiles"
# This file only contains z0-z10
# Requests for z11+ will return 204 (no content)

4. CDN Caching

Tiles are immutable - cache them aggressively:

location ~ ^/data/.*\.pbf$ {
    proxy_pass http://tileserver:8080;
    proxy_cache tiles;
    proxy_cache_valid 200 30d;
    proxy_cache_valid 204 1h;
    add_header X-Cache-Status $upstream_cache_status;
}

Combining with Styles

Vector tiles are just data. To render them, you need a style. tileserver-rs can serve styles too:

[[sources]]
id = "osm"
type = "pmtiles"
path = "/data/osm.pmtiles"

[[styles]]
id = "bright"
path = "/data/styles/bright/style.json"

The style references your tile source:

{
  "version": 8,
  "sources": {
    "osm": {
      "type": "vector",
      "url": "http://localhost:8080/data/osm.json"
    }
  },
  "layers": [...]
}

Troubleshooting

Tiles Return 404

  1. Check source ID - URL must match the id in config
  2. Check zoom range - Request within minzoom/maxzoom
  3. Check bounds - Coordinates must be within the tile bounds
# Check what sources are available
curl http://localhost:8080/data.json

# Check metadata for bounds and zoom range
curl http://localhost:8080/data/osm.json | jq '{bounds, minzoom, maxzoom}'

Tiles Return 204 (Empty)

This is normal for areas with no data. Not every tile coordinate contains features.

Large File Errors

For very large PMTiles files (10GB+), ensure you have enough memory:

# Check file size
ls -lh world.pmtiles

# Run with more memory if needed
RUST_LOG=debug ./tileserver-rs --config config.toml

Next Steps