Serving Vector Tiles
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:
| Format | Best For | File Extension |
|---|---|---|
| PMTiles | Production, CDN, cloud storage | .pmtiles |
| MBTiles | Development, 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"
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"
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
| Code | Meaning |
|---|---|
200 | Tile found and returned |
204 | Tile exists but is empty (no data in this area) |
404 | Source 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:
| Metric | PMTiles | MBTiles |
|---|---|---|
| Random access | ~1,300 req/s | ~750 req/s |
| Sequential access | Similar | Similar |
| Cloud serving | Excellent | Poor |
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
- Check source ID - URL must match the
idin config - Check zoom range - Request within minzoom/maxzoom
- 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
- Configuration Reference - All config options
- Static Map Images - Generate images from tiles
- MapLibre Integration - Display tiles in the browser