Tileserver RS uses a TOML configuration file to define tile sources, styles, and server settings.
Configuration File
Create a config.toml file. Note that root-level options (fonts, files) must come before any [section] headers due to TOML parsing rules:
# Root-level options (must come before [sections])
fonts = "/data/fonts"
files = "/data/files"
[server]
host = "0.0.0.0"
port = 8080
cors_origins = ["*"]
[telemetry]
enabled = false
# endpoint = "http://localhost:4317"
# metrics_enabled = true
# metrics_export_interval_secs = 60
[render]
pool_size = 4
render_timeout_secs = 30
[[sources]]
id = "openmaptiles"
type = "pmtiles"
path = "/data/tiles.pmtiles"
name = "OpenMapTiles"
attribution = "© OpenMapTiles © OpenStreetMap contributors"
[[sources]]
id = "terrain"
type = "mbtiles"
path = "/data/terrain.mbtiles"
name = "Terrain Data"
[[styles]]
id = "osm-bright"
path = "/data/styles/osm-bright/style.json"
Root-level keys like fonts and files must appear before any section headers ([server], [telemetry], etc.) in the TOML file. Otherwise they will be incorrectly parsed as part of the preceding section.
Server Configuration
| Option | Description | Default |
|---|---|---|
host | IP address to bind to | 0.0.0.0 |
port | Port number | 8080 |
cors_origins | Allowed CORS origins | ["*"] |
admin_bind | Admin server bind address for hot-reload | 127.0.0.1:0 (disabled) |
upload_dir | Directory for uploaded files | - |
upload_max_size_mb | Maximum upload file size in MB | 500 |
Admin Server
The admin server exposes POST /__admin/reload on a separate port for hot-reloading configuration without restarting the server.
[server]
admin_bind = "127.0.0.1:9099"
When admin_bind is set to anything other than 127.0.0.1:0, the admin server starts on that address. You can also trigger a reload by sending SIGHUP to the server process.
See the Hot Reload Guide for details.
File Uploads
Enable file uploads for the drag-and-drop visualization feature:
[server]
upload_dir = "/data/uploads"
upload_max_size_mb = 500
When upload_dir is set, the server exposes upload endpoints (POST /api/upload, GET /api/upload, DELETE /api/upload/{id}) for streaming geospatial files (MBTiles, SQLite, COG) to disk. Uploaded files are registered as temporary tile sources — they persist until deleted or the server restarts.
See the File Drop Guide and Upload API reference for details.
Source Configuration
File-based sources (PMTiles, MBTiles, GeoParquet) are configured in [[sources]] arrays. PostgreSQL sources are configured separately in [postgres].
Each file source requires:
| Option | Description | Required |
|---|---|---|
id | Unique identifier | Yes |
type | pmtiles, mbtiles, geoparquet, or duckdb | Yes |
path | Path to tile file (local or URL) | Yes |
name | Display name | No |
attribution | Map attribution | No |
PMTiles Sources
[[sources]]
id = "world"
type = "pmtiles"
path = "/data/world.pmtiles"
# Or from a URL (requires http feature)
# path = "https://example.com/tiles.pmtiles"
MBTiles Sources
[[sources]]
id = "local-data"
type = "mbtiles"
path = "/data/local.mbtiles"
GeoParquet Sources
GeoParquet support requires the geoparquet feature flag when building from source.
Serve vector tiles on-the-fly from GeoParquet files — no preprocessing or tile generation step required.
[[sources]]
id = "buildings"
type = "geoparquet"
path = "/data/overture-buildings.parquet"
name = "Overture Buildings"
layer_name = "buildings"
geometry_column = "geometry"
minzoom = 0
maxzoom = 14
| Option | Description | Default |
|---|---|---|
layer_name | MVT layer name | Source id |
geometry_column | Name of the geometry column | Auto-detected |
minzoom | Minimum zoom level | 0 |
maxzoom | Maximum zoom level | 14 |
GeoParquet 1.1 files with bbox covering columns enable fast spatial filtering via row group pruning.
DuckDB Sources
DuckDB support requires the duckdb feature flag when building from source.
Generate vector tiles on-the-fly from SQL queries against embedded DuckDB databases or GeoParquet files.
id = "places" type = "duckdb" path = "/data/overture.duckdb" query = """ SELECT name, geometry FROM places WHERE bbox.xmin <= {bbox_xmax} AND bbox.xmax >= {bbox_xmin} AND bbox.ymin <= {bbox_ymax} AND bbox.ymax >= {bbox_ymin} """ layer_name = "places"
SQL templates support {z}, {x}, {y}, and {bbox} placeholders that are substituted per tile request. Set path to an empty string for in-memory DuckDB.
PostgreSQL Configuration
PostgreSQL support requires the postgres feature flag when building from source.
Configure PostgreSQL connection and sources in the [postgres] section:
[postgres]
connection_string = "postgresql://user:pass@localhost:5432/tiles"
pool_size = 20
# Table sources (recommended - auto-generates optimized SQL)
[[postgres.tables]]
id = "points"
table = "my_points"
geometry_column = "geom"
minzoom = 0
maxzoom = 14
# Function sources (for custom SQL logic)
[[postgres.functions]]
id = "custom_tiles"
function = "get_tiles"
minzoom = 0
maxzoom = 14
Connection Options
| Option | Description | Default |
|---|---|---|
connection_string | PostgreSQL connection URL | Required |
pool_size | Maximum connections | 20 |
ssl_cert | Path to SSL certificate | - |
ssl_key | Path to SSL key | - |
ssl_root_cert | Path to SSL root certificate | - |
Table Sources
Table sources auto-discover geometry columns and generate optimized tile queries with spatial index filtering.
[[postgres.tables]]
id = "buildings"
schema = "public"
table = "buildings"
geometry_column = "geom" # Optional, auto-detected
id_column = "id" # Optional, for feature IDs
properties = ["name", "type"] # Optional, defaults to all columns
minzoom = 0
maxzoom = 14
bounds = [-180, -85, 180, 85] # Optional, auto-detected
extent = 4096 # MVT extent (default: 4096)
buffer = 64 # Tile buffer in pixels (default: 64)
max_features = 10000 # Optional feature limit per tile
| Option | Description | Default |
|---|---|---|
id | Unique source identifier | Required |
schema | PostgreSQL schema | public |
table | Table name | Required |
geometry_column | Geometry column name | Auto-detected |
id_column | Column for feature IDs | - |
properties | Columns to include | All non-geometry columns |
minzoom | Minimum zoom level | 0 |
maxzoom | Maximum zoom level | 22 |
bounds | Bounds [west, south, east, north] | Auto-detected |
extent | MVT tile extent | 4096 |
buffer | Tile buffer in pixels | 64 |
max_features | Max features per tile | Unlimited |
Ensure your geometry column has a spatial index (GIST) for optimal performance. The server will warn if no index is found.
Function Sources
Function sources call PostgreSQL functions that return MVT tiles directly.
[[postgres.functions]]
id = "dynamic_tiles"
schema = "public"
function = "get_tiles"
minzoom = 0
maxzoom = 14
bounds = [-180, -85, 180, 85]
The function must have one of these signatures:
-- Simple (z, x, y)
CREATE FUNCTION get_tiles(z integer, x integer, y integer)
RETURNS bytea AS $$ ... $$ LANGUAGE plpgsql;
-- With query parameters (z, x, y, query)
CREATE FUNCTION get_tiles(z integer, x integer, y integer, query json)
RETURNS bytea AS $$ ... $$ LANGUAGE plpgsql;
| Option | Description | Default |
|---|---|---|
id | Unique source identifier | Required |
schema | PostgreSQL schema | public |
function | Function name | Required |
minzoom | Minimum zoom level | 0 |
maxzoom | Maximum zoom level | 22 |
bounds | Bounds [west, south, east, north] | - |
Table vs Function Sources
| Aspect | Table Source | Function Source |
|---|---|---|
| Setup | Minimal config | Requires SQL function |
| Performance | Optimized (uses spatial index) | Depends on function |
| Flexibility | Fixed schema | Custom SQL logic |
| Use case | Standard tables | Complex queries, joins |
Style Configuration
[[styles]]
id = "bright"
path = "/data/styles/bright/style.json"
name = "Bright Style" # Optional display name
Styles should include sprites alongside the style.json:
styles/
└── bright/
├── style.json
├── sprite.json
├── sprite.png
├── sprite@2x.json
└── sprite@2x.png
Font Configuration
Fonts are required for rendering text labels. Configure the fonts directory:
fonts = "/data/fonts"
The fonts directory should contain subdirectories for each font family with PBF glyph files:
fonts/
├── Noto Sans Regular/
│ ├── 0-255.pbf
│ ├── 256-511.pbf
│ └── ...
├── Noto Sans Medium/
│ ├── 0-255.pbf
│ └── ...
└── Open Sans Bold/
└── ...
Font glyph PBF files can be generated using tools like node-fontnik or downloaded from OpenMapTiles fonts.
Static Files Configuration
Optionally serve static files from a directory:
files = "/data/files"
Files in this directory will be accessible at /files/{filepath}. This is useful for:
- GeoJSON overlays
- Custom marker icons
- Other static assets
Telemetry Configuration
tileserver-rs supports two independent observability pipelines: an OTLP push pipeline (OpenTelemetry traces + metrics over gRPC) and a Prometheus pull-based /metrics scrape endpoint. Both are off by default and can run independently.
[telemetry]
# OTLP push pipeline
enabled = true
endpoint = "http://localhost:4317"
service_name = "tileserver-rs"
sample_rate = 1.0
metrics_enabled = true
metrics_export_interval_secs = 60
# Prometheus pull endpoint (independent of `enabled` above)
prometheus_bind = "127.0.0.1:9100"
prometheus_path = "/metrics"
metrics_label_cardinality = "strict"
| Option | Description | Default |
|---|---|---|
enabled | Enable OpenTelemetry OTLP push (traces + metrics) | false |
endpoint | OTLP gRPC collector endpoint | http://localhost:4317 |
service_name | Service name for traces and metrics | tileserver-rs |
sample_rate | Trace sampling rate (0.0 to 1.0) | 1.0 |
metrics_enabled | Enable OTLP metrics push (requires enabled = true) | true |
metrics_export_interval_secs | How often metrics are pushed to the collector | 60 |
prometheus_bind | Bind address for the Prometheus scrape listener (unset = disabled) | unset |
prometheus_path | HTTP path for the Prometheus exposition endpoint | /metrics |
metrics_label_cardinality | Label cardinality strategy: "strict" (production) or "verbose" | "strict" |
The same instruments feed both pipelines:
| Metric | Type | Unit | Labels | Description |
|---|---|---|---|---|
http_requests_total | counter | — | route, status_class | HTTP requests by matched route + status |
http_request_duration_seconds | histogram | s | route, status_class | Per-request latency |
http_requests_in_flight | up-down | — | — | In-flight HTTP requests |
tile_requests_total | counter | — | source, format, z_bucket, outcome | Tile lookups (hit/miss/not_found/error) |
tile_request_duration_seconds | histogram | s | source, format, z_bucket, outcome | End-to-end tile latency |
tile_request_bytes | histogram | By | source, format | Tile response payload size |
tile_cache_hits_total | counter | — | source | In-process tile cache hits |
tile_cache_misses_total | counter | — | source | In-process tile cache misses |
render_duration_seconds | histogram | s | style, format | Native MapLibre raster render duration |
render_errors_total | counter | — | style, reason | Native render failures bucketed by reason |
When neither enabled = true nor prometheus_bind is set (the default), all instruments are no-ops — recording a metric is a single atomic load with negligible overhead.
Cardinality matters. metrics_label_cardinality = "strict" (the default) collapses zoom into low (0–6), mid (7–12), high (13+) buckets and drops tile x/y coordinates entirely. Switching to "verbose" exposes raw zoom 0–22, which can blow up Prometheus storage on large globe-scale sources. Use "verbose" only for short-window debugging.
See the Telemetry Guide for setup examples with Grafana, Jaeger, Prometheus, and other backends.
Render Pool Configuration
Configure the native MapLibre renderer pool for server-side raster tile generation:
[render]
pool_size = 4
render_timeout_secs = 30
| Option | Description | Default |
|---|---|---|
pool_size | Number of concurrent renderer worker threads | 4 |
render_timeout_secs | Render timeout in seconds — requests exceeding this fail | 30 |
Each worker thread maintains persistent MapLibre Native instances with their own EGL contexts. Increase pool_size for higher concurrent raster tile throughput.
The [render] section is optional. When omitted, the renderer pool starts with default values. If the server was built without MapLibre Native (stub mode), the pool initializes but returns placeholder images.
Tile Cache Configuration
tileserver-rs includes an in-process tile cache backed by moka. When enabled, tiles are cached in memory with byte-weighted eviction and per-entry TTL. This eliminates repeated disk I/O or network calls for frequently requested tiles.
[cache]
enabled = true
max_size_mb = 512
ttl_seconds = 3600
| Option | Description | Default |
|---|---|---|
enabled | Enable the global tile cache | false |
max_size_mb | Maximum cache size in megabytes | 512 |
ttl_seconds | Time-to-live for cache entries (seconds) | 3600 |
The global cache applies to PMTiles, MBTiles, GeoParquet, and DuckDB sources. PostgreSQL sources have their own dedicated cache configured via [postgres.cache].
Cache is invalidated on config reload. Sending POST /__admin/reload or SIGHUP drops the entire cache along with the old configuration.
Environment Variables
| Variable | Description | Default |
|---|---|---|
RUST_LOG | Log level (error, warn, info, debug, trace) | info |
CONFIG_PATH | Path to config file | config.toml |
HOST | Override server host | - |
PORT | Override server port | - |
CLI Options
tileserver-rs --help
Usage: tileserver-rs [PATH] [OPTIONS]
Arguments:
[PATH] Path to a tile file or directory to auto-detect sources/styles from
Options:
-c, --config <FILE> Path to configuration file [env: TILESERVER_CONFIG]
--host <HOST> Host to bind to [env: TILESERVER_HOST]
-p, --port <PORT> Port to bind to [env: TILESERVER_PORT]
--public-url <URL> Public URL for tile URLs in TileJSON [env: TILESERVER_PUBLIC_URL]
--ui Enable the web UI (default: true) [env: TILESERVER_UI]
--no-ui Disable the web UI
-v, --verbose Enable verbose logging
-h, --help Print help
-V, --version Print version
Config Resolution Priority
The server resolves configuration in this order:
--config <FILE>— explicit config path (fails if file doesn't exist)- Positional
PATH— auto-detect sources/styles from that path - Default locations:
./config.toml,/etc/tileserver-rs/config.toml - CWD auto-detect — scan the current directory
Zero-config auto-detect discovers .pmtiles, .mbtiles, style.json, fonts directories, and GeoJSON files. See the Auto-Detect Guide for details.
Cargo Features
When building from source, you can enable optional features:
| Feature | Description | Default |
|---|---|---|
postgres | PostgreSQL tile sources (tables + functions) | Yes |
raster | GDAL-based raster/COG tile support | Yes |
mlt | MLT transcoding (MLT to MVT and MVT to MLT) | Yes |
geoparquet | Serve tiles from GeoParquet files on-the-fly | No |
frontend | Embed the Nuxt web UI into the binary | No |
| Feature | Description | Default |
| ---------- | ----------------------------------------------- | ------- |
postgres | PostgreSQL tile sources (tables + functions) | Yes |
raster | GDAL-based raster/COG tile support | Yes |
mlt | MLT transcoding (MLT to MVT and MVT to MLT) | Yes |
duckdb | SQL-driven tile generation via embedded DuckDB | No |
frontend | Embed the Nuxt web UI into the binary | No |
# API-only (no web UI, no GDAL, no PostgreSQL):
cargo build --release --no-default-features
# Default features (postgres + raster + mlt, no web UI):
cargo build --release
# Everything including the web UI:
cargo build --release --all-features
The frontend feature is intentionally not a default feature so the backend can be compiled without building the Nuxt frontend first. Pre-built binaries, Docker images, and CI release builds always include it.