This page is the canonical reference for everything tileserver-rs reads — CLI flags, config keys (TOML or YAML), and environment variables — sourced directly from the Rust structs in crates/tileserver-rs/src/config.rs and crates/tileserver-rs/src/cli.rs.

Config file formats

tileserver-rs accepts both TOML and YAML config files. The format is detected from the file extension:

ExtensionParser
.tomlTOML (default)
.yaml, .ymlYAML
anything elseTOML (fallthrough)

TOML and YAML produce identical Config structs — every option documented on this page works in both formats. See data/configs/example.toml for the canonical exhaustively-commented reference and data/configs/example.yaml for the YAML syntax mirror.

# Either of these works:
tileserver-rs --config config.toml
tileserver-rs --config config.yaml
tileserver-rs --config config.yml

For task-oriented guides see , , , , , and .

Precedence

When the same value is supplied in multiple places, later sources override earlier ones:

  1. Hard-coded defaults (lowest priority)
  2. Config file in TOML or YAML (--config)
  3. Environment variables (TILESERVER_*)
  4. CLI flags (highest priority)

Config file lookup

If --config is not provided, the server resolves what to load in this order:

  1. --config <FILE> — explicit config path (fails if the file does not exist)
  2. Positional PATH — auto-detect sources/styles from that path (see )
  3. Default locations, tried in order: ./config.toml, ./config.yaml, ./config.yml, then /etc/tileserver-rs/config.{toml,yaml,yml}
  4. CWD auto-detect — scan the current directory for .pmtiles, .mbtiles, style.json, and font directories

CLI flags

tileserver-rs [OPTIONS] [PATH] [SUBCOMMAND]
FlagEnv fallbackDefaultDescription
[PATH] (positional)File or directory to auto-detect sources/styles from (zero-config mode)
-c, --config <FILE>TILESERVER_CONFIGPath to TOML config file
--cache-dir <PATH>TILESERVER_CACHE_DIR<tmp>/tileserver-rsScratch / state directory (uploads, on-disk subsystems). Overrides [cache].dir
--host <HOST>TILESERVER_HOSTfrom [server]Override bind address
-p, --port <PORT>TILESERVER_PORTfrom [server]Override bind port
--public-url <URL>TILESERVER_PUBLIC_URLPublic URL embedded in TileJSON tiles entries
--uiTILESERVER_UItrueServe the embedded SPA web UI
--no-uiDisable the embedded SPA web UI
-v, --verbosefalseIncrease log verbosity

Subcommands

SubcommandFeature gateDescription
(none)Run the HTTP server (default)
mcp-stdiomcpRun the server over stdin/stdout for Claude Desktop and other local clients

[server]

[server]
host = "0.0.0.0"
port = 8080
cors_origins = ["*"]
admin_bind = "127.0.0.1:0"
public_url = "https://tiles.example.com"   # optional
upload_dir = "/var/lib/tileserver/uploads" # optional
upload_max_size_mb = 500
disable_render = false                     # optional — turn off raster routes
disable_ogc = false                        # optional — turn off OGC API routes
KeyTypeDefaultDescription
hoststring"0.0.0.0"Bind address for the public tile server
portu168080Bind port for the public tile server
cors_originsstring["*"]CORS allow-list — supports literals, glob (*), and regex (/.../); see Pattern syntax below
admin_bindstring"127.0.0.1:0"Separate bind for admin endpoints (/__admin/reload, /__admin/oauth/*). :0 disables. Use 127.0.0.1:8081 to enable.
public_urlstring?(none)Public URL embedded in TileJSON. Falls back to the bind address
upload_dirpath?system tmpDirectory for drag-and-drop uploads (creates a tmp source per upload)
upload_max_size_mbu32500Maximum per-file upload size in megabytes
extra_response_headerstable?(none)User-defined response headers applied to every HTTP response — see [server.extra_response_headers] below
disable_renderboolfalseUnregister raster render routes (raster tiles + static images) at startup. Style metadata (style.json, sprites, WMTS) stays served. Disabled routes return 404.
disable_ogcboolfalseUnregister OGC API routes (/ogc/*) at startup. No effect unless built with the ogc feature. Disabled routes return 404.

CORS origin patterns

The cors_origins list accepts three syntaxes, dispatched per entry:

Entry syntaxInterpretation
"*"Wildcard — allow any origin (default; emits a startup warning)
"https://example.com"Exact literal match (unchanged from previous behaviour)
"*.example.com"Glob — * matches one or more characters in the Origin URL
"preview-*.example.com"Glob — * can appear anywhere in the pattern
"/^https://.+\\.example\\.com$/"Regex — enclosed in /.../, matched against the full Origin
[server]
cors_origins = [
  "https://example.com",
  "*.cdn.example.com",
  "preview-*.example.com",
  "/^https://.+\\.team-[a-z0-9]+\\.app\\.example\\.com$/",
]

Notes:

  1. Entries are tried in order; the first match short-circuits per request.
  2. A list containing "*" ALWAYS resolves to wildcard regardless of other entries — that is the existing semantics, preserved.
  3. Pure-literal lists (no *, no /.../) use the same allocation-free AllowOrigin::list fast-path as before. Adding a glob or regex switches the layer to AllowOrigin::predicate.
  4. Invalid regex (e.g. unbalanced [) fails fast at startup with the offending entry quoted in the error.
  5. Glob entries are compiled by anchoring with ^...$ and replacing * with .* — so *.x.com matches foo.x.com and https://foo.x.com, but NOT x.com (no leading subdomain) or foo.x.com.evil.com (suffix injection blocked).

[server.extra_response_headers]

A user-defined map of HTTP response headers that are applied (in SetResponseHeaderLayer::overriding mode) to every response served by tileserver-rs — tile, style, OGC, admin, MCP, everything. Useful for:

Use caseExample header
Edge / CDN cache controlCDN-Cache-Control, Surrogate-Control
Browser cache controlCache-Control (overrides any default)
Block search-engine indexingX-Robots-Tag: noindex, nofollow
HSTS for TLS terminatorsStrict-Transport-Security
Content securityX-Content-Type-Options: nosniff
APM correlationServer-Timing, custom X-* audit headers
[server.extra_response_headers]
"Cache-Control" = "public, max-age=86400, immutable"
"CDN-Cache-Control" = "max-age=604800"
"X-Robots-Tag" = "noindex, nofollow"
"X-Content-Type-Options" = "nosniff"
"Strict-Transport-Security" = "max-age=31536000; includeSubDomains"
"Server-Timing" = "tileserver;dur=0"

Equivalent YAML:

server:
  extra_response_headers:
    Cache-Control: public, max-age=86400, immutable
    CDN-Cache-Control: max-age=604800
    X-Robots-Tag: noindex, nofollow

Rules:

  1. Header names must conform to RFC 7230 token grammar (alphanumeric + !#$%&'*+-.^_\|~`).
  2. The transport-managed headers Content-Length, Transfer-Encoding, Date, Connection are rejected at startup — the server fails fast.
  3. An empty string value ("X-Foo" = "") removes any pre-existing X-Foo header from outgoing responses.
  4. User values are overriding — they win over anything an upstream middleware emits.
  5. Hot reload (POST /__admin/reload) picks up changes without a restart.

[[sources]]

Each [[sources]] block configures one tile source. The type field determines which optional fields are read.

Common fields

KeyTypeRequiredDescription
idstringyesUnique identifier (becomes /data/<id>/* route)
typeenumyesOne of: pmtiles, mbtiles, postgres, cog, vrt, geoparquet, duckdb, stac (some feature-gated)
pathstringyesFile path, HTTP(S) URL, or cloud URL (s3://, gs://, az://)
namestring?noDisplay name shown in the viewer
attributionstring?noAttribution text included in TileJSON
descriptionstring?noFree-form description
minzoomu8?noOverride the minzoom from source metadata
maxzoomu8?noOverride the maxzoom from source metadata
serve_asenum?noTranscode on the fly. E.g. serve_as = "mlt" on a PBF source emits MLT. Requires mlt feature
optionstablenoKey-value map forwarded to cloud backends (S3 credentials, GCS keys, etc.) — see Cloud sources

Type-specific fields

type = "pmtiles" / type = "mbtiles"

These read all metadata (zoom range, bounds, layers) from the file itself. No extra config needed beyond id, type, and path.

type = "dir"

Serve a {z}/{x}/{y}.{ext} pyramid straight from disk — no PMTiles/MBTiles packaging step. The lowest-friction source: point it at tippecanoe --output-to-directory output, a wget --recursive mirror, or any on-disk pyramid. Zero startup index cost; every tile fetch is a direct filesystem read.

[[sources]]
id = "tippecanoe-output"
type = "dir"
path = "/data/tiles/openmaptiles/"
# tile_path_template = "{z}/{x}/{y}@2x.png"  # optional, default {z}/{x}/{y}.{ext}
# tms = true                                  # optional, default false (XYZ)

minzoom, maxzoom, and bounds are derived lazily by walking the directory on the first TileJSON request, then cached. Missing tiles behave like every other source. For tilesets with millions of files, the natural {z}/{x}/{y} sharding keeps per-directory entry counts manageable.

type = "tar"

Serve a .tar bundle of {z}/{x}/{y}.{ext} tiles — a single portable file, ideal for airgapped / offline / scp-everywhere deployments. The archive is decompressed once at startup and an in-memory (z, x, y) index is built; tiles are then served via random access. Compression is auto-detected by extension: .tar, .tar.gz / .tgz, .tar.br, .tar.zst.

[[sources]]
id = "regional-tiles"
type = "tar"
path = "/data/tiles.tar.gz"  # or .tar / .tar.br / .tar.zst

Vector layer metadata is introspected from the first MVT tile, exactly like the PMTiles source. Tradeoff: the whole archive is held resident in memory (the index alone is ~16 bytes per tile). For planet-scale tilesets, PMTiles remains the production choice — its hierarchical directory avoids keeping every tile in memory.

dir / tar shared options

Both filesystem-style sources accept two extra fields:

OptionDefaultDescription
tile_path_template{z}/{x}/{y}.{ext}Layout template. Must contain {z}, {x}, {y}; {ext} is optional. Example: {z}/{x}/{y}@2x.png.
tmsfalsetrue flips the Y axis for TMS (south-up) archives. Default false serves XYZ (north-up), what tippecanoe emits and web clients expect.

type = "postgres" (feature: postgres)

Combines the common fields with one of two patterns: functions or tables. Defined inside the top-level [postgres] block, not directly on the source. See the .

type = "cog" / type = "vrt" (feature: raster)

KeyTypeDefaultDescription
resamplingenum?bilinearGDAL resampler. One of: nearest, bilinear, cubic, cubicspline, lanczos, average, mode
colormaptable?(none)See ColorMap below

type = "geoparquet" (feature: geoparquet)

KeyTypeRequiredDescription
layer_namestring?noOverride the layer name in emitted MVT tiles
geometry_columnstring?noGeometry column name (auto-detected when GeoParquet metadata is present)
querystring?noDuckDB SQL filter applied before tile materialization

type = "duckdb" (feature: duckdb)

Same layer_name, geometry_column, query as GeoParquet.

type = "stac" (feature: stac)

KeyTypeDefaultDescription
collectionstring?(none)STAC collection ID
asset_rolestring"visual"Which asset role to render (e.g. "visual", "thumbnail", "data")
dynamicboolfalseEnable on-demand STAC search per tile request (vs. one-time discovery at startup)
max_itemsusize100Max items considered per tile request when dynamic = true
stac_bboxf644?(none)Override the bbox passed to STAC search
pixel_selectionenumfirstMosaic strategy. See Pixel selection

Pixel selection (feature: stac or raster)

VariantShort-circuitDescription
first (default)yesFirst valid pixel wins. Lowest latency
highestnoPer-pixel max across all bands. Highlights bright features
lowestnoPer-pixel min. Highlights shadows
meannoPer-pixel arithmetic mean
mediannoPer-pixel median. Robust to outliers
stdevnoPer-pixel std-dev. Change detection
countnoPer-pixel valid-input count (debug viz, encodes count in red channel)
lowestcloudcoveryesPick the STAC asset with lowest eo:cloud_cover, then first ordering

ColorMap (feature: raster)

[[sources]]
id = "ndvi"
type = "cog"
path = "/data/ndvi.tif"

[sources.colormap]
type = "discrete"        # or "continuous"
rescale_mode = "static"  # or "dynamic" / "none"
nodata_color = "#00000000"

[[sources.colormap.entries]]
value = -1.0
color = "#0033cc"

[[sources.colormap.entries]]
value = 0.0
color = "#888888"
KeyTypeDefaultDescription
typeenumdiscretediscrete = stepped, continuous = interpolated
rescale_modeenumstaticstatic = use entry values verbatim. dynamic = stretch to per-tile data range. none = no rescale
entriestablerequiredList of {value, color} stops
nodata_colorstring?(none)Hex color (incl. alpha) for nodata pixels

[[styles]]

[[styles]]
id = "osm-bright"
path = "/data/styles/osm-bright/style.json"
name = "OSM Bright"
KeyTypeRequiredDescription
idstringyesStyle identifier (becomes /styles/<id>/* route)
pathstringyesPath to a MapLibre style JSON file
namestring?noDisplay name shown in the viewer
attributionstring?noAttribution text

[render]

Native MapLibre raster renderer pool. See .

[render]
pool_size = 4
render_timeout_secs = 30
KeyTypeDefaultDescription
pool_sizeusize4Concurrent renderer worker threads
render_timeout_secsu6430Per-request render timeout. Requests exceeding this are dropped

[cache]

Global in-process tile cache (moka backend).

[cache]
enabled = true
max_size_mb = 512
ttl_seconds = 3600
dir = "/var/lib/tileserver"   # optional — scratch / state directory
KeyTypeDefaultDescription
enabledboolfalseEnable the global tile cache
max_size_mbu64512Maximum cache size in megabytes
ttl_secondsu643600Time-to-live for cache entries
dirpath?<tmp>/tileserver-rsScratch / state directory for on-disk subsystems (today: uploads). Storage location only — not a cache eviction policy.
Info

dir sets where on-disk state (e.g. uploads) lives, not the in-memory tile cache. Resolution precedence (highest first): --cache-dir flag → TILESERVER_CACHE_DIR env → [cache].dir<tmp>/tileserver-rs.

[compression]

Negotiates Accept-Encoding per request and serves tile bodies as brotli or zstd when the client accepts them. By default tiles are served in whatever encoding the source stores them in (gzip for most PMTiles); enabling brotli or zstd negotiation produces 15–40% smaller payloads on typical vector tiles.

[compression]
br_quality = 5                # brotli quality 0-11 (default 5)
zstd_level = 3                # zstd level 1-22 (default 3)
minimal_recompression = false # if true, never re-encode — always passthrough
KeyTypeDefaultDescription
br_qualityu85Brotli quality (0–11). Higher = smaller payload, slower encode.
zstd_leveli323Zstandard level (1–22). Higher = smaller payload, slower encode.
minimal_recompressionboolfalseWhen true, never decode/re-encode — serve the source encoding as-is.

How negotiation works per request:

  1. If the source's stored encoding is already in the client's Accept-Encoding set, it is served as-is (no decode/re-encode).
  2. Otherwise the tile is decoded once and re-encoded to the highest-preference acceptable encoding, then cached under (source, z, x, y, encoding) so the re-encode cost is paid once per tile + encoding pair.
  3. Tiles smaller than 200 bytes skip compression — the framing overhead exceeds the savings.

Every tile response carries Vary: Accept-Encoding so CDNs cache one copy per encoding rather than serving a wrong-encoding copy to a client that cannot decode it.

Info

Brotli at quality 10–11 can exceed 100 ms per tile. The default q5 favours first-paint latency; raise it only if you precompute tiles in batch. Set minimal_recompression = true to disable negotiation entirely and serve source bytes verbatim.

[raster] (feature: raster)

[raster]
default_resampling = "bilinear"
tile_size = 256
KeyTypeDefaultDescription
default_resamplingenumbilinearDefault resampler. Per-source resampling overrides this
tile_sizeu32256Output tile size in pixels (set to 512 for retina-native tiles)

[telemetry]

OpenTelemetry tracing + Prometheus metrics. See .

[telemetry]
enabled = true
endpoint = "http://localhost:4317"
service_name = "tileserver-rs"
sample_rate = 1.0
metrics_enabled = true
metrics_export_interval_secs = 30
prometheus_bind = "127.0.0.1:9100"
prometheus_path = "/metrics"
metrics_label_cardinality = "strict"
KeyTypeDefaultDescription
enabledboolfalseEnable OTLP trace export
endpointstring"http://localhost:4317"OTLP gRPC endpoint
service_namestring"tileserver-rs"service.name resource attribute
sample_ratef641.0Sampling rate (0.0–1.0). 1.0 = export every span
metrics_enabledbooltrueEnable OTLP metrics export (requires enabled = true)
metrics_export_interval_secsu6430OTLP metrics push interval
prometheus_bindstring?(none)Bind for standalone Prometheus /metrics listener (independent of OTLP). E.g. "127.0.0.1:9100"
prometheus_pathstring"/metrics"HTTP path for the Prometheus exposition endpoint
metrics_label_cardinalityenum"strict"strict (bucketed zoom, no tile coords), standard (alias of strict), verbose (full zoom 0..22)

[postgres] (feature: postgres)

Top-level PostgreSQL pool + per-source registries. See the .

[postgres]
connection_string = "postgres://user:pass@localhost:5432/db"
pool_size = 10
pool_wait_timeout_ms = 5000
pool_pre_warm = true

[postgres.cache]
size_mb = 256
ttl_seconds = 300

[[postgres.functions]]
id = "trip_aggregates"
schema = "public"
function = "mvt_trip_aggregates"
minzoom = 6
maxzoom = 14

[[postgres.tables]]
id = "buildings"
schema = "public"
table = "osm_buildings"
geometry_column = "geom"
minzoom = 12
maxzoom = 18
extent = 4096
buffer = 64

[postgres] root

KeyTypeDefaultDescription
connection_stringstringreqPostgres connection URI
pool_sizeusize10Connections in the pool
pool_wait_timeout_msu645000Max ms to wait for a free connection
pool_create_timeout_msu645000Max ms to wait when opening a new connection
pool_recycle_timeout_msu645000Max ms to wait recycling a stale connection
pool_pre_warmboolfalseOpen all pool connections at startup
ssl_cert, ssl_key, ssl_root_certpath?(none)mTLS / verify-full SSL material

[postgres.cache]

KeyTypeDefaultDescription
size_mbu64256Max cache size for Postgres MVT tiles
ttl_secondsu64300TTL per entry

[[postgres.functions]]

KeyTypeDefaultDescription
idstringreqSource ID
schemastringreqPostgres schema
functionstringreqFunction name (takes z/x/y, returns bytea)
minzoomu80Min zoom
maxzoomu822Max zoom
boundsf644?(none)Geographic bounds

[[postgres.tables]]

KeyTypeDefaultDescription
idstringreqSource ID
schemastringreqPostgres schema
tablestringreqTable name
geometry_columnstring?geomGeometry column
id_columnstring?(none)Optional integer ID column carried into MVT features
propertiesstring?allColumn allow-list for MVT properties
minzoomu80Min zoom
maxzoomu822Max zoom
extentu324096MVT extent (4096 = standard)
bufferu3264Per-tile geometry buffer in pixels (auto-set to 0 for POINT/MULTIPOINT)
max_featuresu32?(none)Hard cap on emitted features per tile
writableboolfalseEnable OGC API Features POST/PATCH/DELETE on this table

[mcp] (feature: mcp)

See the for transport + tool details.

[mcp]
enabled = true
auth_token = "supersecret"           # bearer-token mode
cors_origins = ["https://claude.ai"]

[mcp.oauth]
enabled = false                       # mutually exclusive with auth_token
issuer_url = "https://tiles.example.com"
signing_key_path = "/etc/tileserver/mcp-jwt-key.pem"
token_ttl_secs = 3600

[mcp] root

KeyTypeDefaultDescription
enabledboolfalseMount the /mcp Streamable HTTP service
auth_tokenstring?(none)Static bearer token. Mutually exclusive with oauth.enabled = true
cors_originsstring["*"]CORS allow-list for /mcp (lock down in production)

[mcp.oauth]

KeyTypeDefaultDescription
enabledboolfalseEnable RFC 7591 DCR + JWT RS256 OAuth flow
issuer_urlstring?(none)Required when enabled = true. Must match the public URL
signing_key_pathpath?(none)Required when enabled = true. RSA PKCS#8 PEM private key
token_ttl_secsu643600Access-token TTL (clamped to 86400)

Top-level fields

KeyTypeDefaultDescription
fontspath?(none)Directory of PBF glyph files served at /fonts/{stack}/{range}.pbf
filespath?(none)Directory of static files served at /files/{filename}

Cloud sources

For PMTiles hosted on S3, GCS, or Azure, set the URL on path and pass credentials via options:

[[sources]]
id = "global"
type = "pmtiles"
path = "s3://tiles-bucket/global.pmtiles"

[sources.options]
aws_region = "us-east-1"
aws_access_key_id = "..."
aws_secret_access_key = "..."

Keys in options are forwarded verbatim to object_store::parse_url_opts. See the object_store docs for the full list per backend.

Cargo features

When , toggle feature flags to slim the binary or enable optional integrations. The values below are the source of truth, mirrored from crates/tileserver-rs/Cargo.toml.

FeatureDefaultDescription
postgresYesPostgreSQL / PostGIS tile sources (tables + functions); OGC API Features; CQL2 filter
rasterYesGDAL-based COG / VRT raster sources with colormap + band math
mltYesMLT (MapLibre Tiles) ↔ MVT on-the-fly transcoding
cloudYesCloud PMTiles (S3 / GCS / Azure / HTTPS) via object_store
stacYesSTAC catalog source (implies raster)
geoparquetNoServe tiles from GeoParquet files on-the-fly (Arrow + Parquet)
duckdbNoEmbedded DuckDB SQL-driven tile source
mcpNoMCP server (stdio + Streamable HTTP) with optional OAuth
mcp-persistenceNoDisk-backed SQLite store for the MCP OAuth state (implies mcp)
frontendNoEmbed the Nuxt web UI into the binary (always-on for prebuilt artifacts)
# Minimal API-only build (no GDAL, no Postgres, no MLT, no cloud):
cargo build --release --no-default-features

# Default features (postgres + raster + mlt + cloud + stac):
cargo build --release

# Everything, including the web UI and MCP server:
cargo build --release --all-features
Info

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 Homebrew bottles always ship with it included.

Environment variables

Server (Rust binary)

VariableTypeDescription
TILESERVER_CONFIGpathEquivalent to --config <FILE>
TILESERVER_CACHE_DIRpathEquivalent to --cache-dir <PATH> (overrides [cache].dir)
TILESERVER_HOSTstringEquivalent to --host <HOST>
TILESERVER_PORTu16Equivalent to --port <PORT>
TILESERVER_PUBLIC_URLurlEquivalent to --public-url <URL>
TILESERVER_UIboolEquivalent to --ui / --no-ui (string "true"/"false")
RUST_LOGlog spectracing_subscriber::EnvFilter directives, e.g. tileserver_rs=debug

Client (Nuxt dev server)

These only affect pnpm dev:client. Production builds embed the SPA in the Rust binary and serve same-origin, so no proxy is involved.

VariableDefaultDescription
TILESERVER_BACKEND_PORT8080Port of the public Rust backend the Vite dev proxy forwards to
TILESERVER_ADMIN_PORT8081Port of the admin backend ([server].admin_bind) for /__admin/* proxying
Info

The client env vars are deliberately not prefixed NUXT_. The NUXT_* prefix is reserved by Nuxt for runtimeConfig auto-mapping; these are build-time env vars read by nuxt.config.ts directly.

See for more.