Deployment guide

TileServer GL

Overview

TileServer GL is a self-hosted map tile server. It serves MBTiles and PMTiles vector and raster tile sources with a built-in web map preview, uses MapLibre GL styles, and can render raster PNG tiles from vector data on the server. You get your own maps without a Google Maps or Mapbox API key.

The SecureAnyCloud image installs the pinned official TileServer GL 5.6.0 (Node.js 22) under systemd, with the headless server-side rendering stack (Xvfb and the native MapLibre GL renderer) already configured. It ships a small sample map (OpenMapTiles' Zurich, Switzerland demo extract) on a dedicated data disk, so the preview shows a real map immediately.

TileServer GL itself is bound to loopback only. nginx handles all customer-facing traffic on port 80 and gates the preview UI, TileJSON and tile-render endpoints behind a per-VM HTTP Basic Auth credential generated on first boot, because TileServer GL has no accounts of its own. Backed by 24/7 SecureAnyCloud support.

What you get:

  • TileServer GL 5.6.0 installed via npm (Node.js 22 LTS), running as the tileserver-gl systemd service, headless server-side rendering via Xvfb
  • The built-in web preview UI, TileJSON endpoints and raster tile render endpoints on :80, fronted by nginx with TileServer GL bound to loopback only
  • Per-VM HTTP Basic Auth (user admin) protecting everything, with a unique password generated on first boot
  • A sample map dataset (OpenMapTiles' Zurich, Switzerland extract, (c) OpenMapTiles (c) OpenStreetMap contributors) on a dedicated Azure data disk, so the preview shows a real map out of the box
  • tileserver-gl.service and nginx.service as systemd units, enabled and active
  • An unauthenticated /healthz endpoint on :80 for Azure Load Balancer health probes
  • 24/7 SecureAnyCloud support

Prerequisites

  • An active Azure subscription, an SSH key pair, and a VNet and subnet in the target region
  • A VM size of Standard_B2s (2 vCPU / 4 GiB RAM) or larger. Standard_B2s is a comfortable starting point for a small tileset and light preview traffic. Size up for larger tilesets or heavier concurrent tile-render load.
  • NSG inbound rules: allow 22/tcp from your management network, 80/tcp for the preview UI and tiles, and 443/tcp if you add TLS
  • TileServer GL serves plain HTTP. For production, terminate TLS in front of it with your own domain.

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure portal, choose Create a resource, search the Marketplace for TileServer GL by SecureAnyCloud, and select Create.

On Basics, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) and HTTP (80).

Review the dedicated data disk on the Disks tab, then select Review + create and Create.

Step 2 - Deploy from the Azure CLI

Accept the Marketplace terms once per subscription, then create the VM and open port 80:

# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:tileserver-gl-ubuntu-24-04:default:latest
az vm create \
  --resource-group <your-rg> \
  --name tileserver-gl \
  --image sac1647283583153:tileserver-gl-ubuntu-24-04:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --vnet-name <your-vnet> --subnet <your-subnet> \
  --public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name tileserver-gl --port 80 --priority 1010

Step 3 - Connect to your VM

Connect over SSH as azureuser:

ssh azureuser@<vm-public-ip>

Step 4 - Confirm the services are running

Check both services:

systemctl is-active tileserver-gl.service nginx.service

Both report active. TileServer GL serves the preview UI, TileJSON and tile-render endpoints on the loopback address 127.0.0.1:8080 only. nginx fronts port 80 with the per-VM HTTP Basic Auth gate.

The sample map dataset lives on a dedicated 20 GiB data disk mounted at /var/lib/tileserver-gl.

Step 5 - Retrieve your preview UI password

nginx protects the preview UI and every tile and TileJSON route with HTTP Basic Auth. The username is admin. The first boot of your VM generates a unique password and writes it to a root-only file. Read the file with:

sudo cat /root/tileserver-gl-credentials.txt

The file contains TILESERVER_USERNAME, TILESERVER_PASSWORD, the TILESERVER_URL to open in a browser, the discovered TILESERVER_STYLE_ID (the bundled MapLibre GL style TileServer GL registered for the sample tileset), and a ready-to-use TILESERVER_SAMPLE_TILE_URL.

The password is stored on disk only as a bcrypt hash in /etc/nginx/.tileserver-gl.htpasswd, so no plaintext password ships in the image. Store the password somewhere safe.

Step 6 - Confirm the health endpoint

nginx serves an unauthenticated health endpoint for load balancers and probes. Request it with:

curl -s http://localhost/healthz

It returns ok. This endpoint never requires authentication, so you can use it for an Azure Load Balancer health probe.

Step 7 - Confirm authentication on the preview/tile path

A password is set on first boot, so an unauthenticated request returns HTTP 401 and nobody reaches the map without the password.

Run this check. It reads the per-VM password from the credentials file and proves the round trip: an unauthenticated request is rejected, a wrong password is rejected, and the correct password authenticates.

PW=$(sudo grep '^TILESERVER_PASSWORD=' /root/tileserver-gl-credentials.txt | cut -d= -f2-)
echo "unauth : $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/)"
echo "wrongpw : $(curl -s -o /dev/null -w '%{http_code}' -u admin:wrong-pw http://127.0.0.1/)"
echo "authed : $(curl -s -o /dev/null -w '%{http_code}' -u admin:$PW http://127.0.0.1/)"

It prints unauth : 401, wrongpw : 401, authed : 200. Only the per-VM password reaches the preview UI, TileJSON and tile-render endpoints. TileServer GL itself is bound to loopback, so nginx on port 80 is the only way in.

Step 8 - Fetch a rendered map tile

TileServer GL renders raster PNG tiles from the sample vector tileset on demand. The credentials file already has a ready-to-use URL for a populated tile over Zurich. Fetch a tile and confirm it is a real PNG image:

PW=$(sudo grep '^TILESERVER_PASSWORD=' /root/tileserver-gl-credentials.txt | cut -d= -f2-)
STYLE=$(grep '^TILESERVER_STYLE_ID=' /etc/tileserver-gl/tileserver-gl.env | cut -d= -f2-)
curl -s -o /tmp/sample-tile.png -w 'HTTP %{http_code}, %{size_download} bytes\n' \
  -u admin:$PW "http://127.0.0.1/styles/${STYLE}/256/10/536/359.png" file /tmp/sample-tile.png

It prints HTTP 200, 71806 bytes and PNG image data, 256 x 256, 8-bit/color RGBA, non-interlaced. That is a real rendered 256x256 map tile over Zurich, Switzerland, drawn from the vector sample data.

Step 9 - Sign in and view the sample map

Browse to http://<vm-public-ip>/. When your browser prompts for a username and password, enter admin and the password from Step 5.

TileServer GL opens on its built-in preview page. It lists the registered data source (the Zurich sample) and every bundled MapLibre GL style available for it, each with a live interactive map preview.

Step 10 - Explore the interactive map

Select a style to open its full-page interactive MapLibre GL JS map. Pan and zoom around Zurich, Switzerland. The vector-rendered roads, buildings, water and land-use layers redraw live at every zoom level.

Step 11 - Browse the data/style listing

The preview home page (Step 9) doubles as a directory of everything TileServer GL is serving. It shows the zurich_switzerland data source and every style registered against it, each linking to its own TileJSON and inspection page. The listing grows when you add your own MBTiles or PMTiles tilesets (see Maintenance below).

Step 12 - Inspect the TileJSON / API info page

Every data source exposes a standard TileJSON document that describes its tile URL template, bounds, minimum and maximum zoom, and attribution. Use it to wire the tileset into MapLibre GL JS, Leaflet, OpenLayers or any TileJSON-aware client. Fetch it with:

PW=$(sudo grep '^TILESERVER_PASSWORD=' /root/tileserver-gl-credentials.txt | cut -d= -f2-)
curl -s -u admin:$PW "http://127.0.0.1/data/v3.json" | python3 -m json.tool

Maintenance

  • Password: the first boot sets the preview and tile password and stores it as a bcrypt entry in /etc/nginx/.tileserver-gl.htpasswd. To change it, run sudo htpasswd -B /etc/nginx/.tileserver-gl.htpasswd admin, then sudo systemctl reload nginx.
  • Adding your own tilesets: copy your MBTiles or PMTiles file into /var/lib/tileserver-gl/. Then either point the service at it directly (edit the ExecStart in /etc/systemd/system/tileserver-gl.service to add --file /var/lib/tileserver-gl/<your-file>.mbtiles) or switch to a config.json that references multiple sources (see the TileServer GL documentation). Apply the change with sudo systemctl daemon-reload && sudo systemctl restart tileserver-gl. No third-party maps API key is required for your own data.
  • Sample data attribution: the bundled demo tileset is OpenMapTiles' Zurich, Switzerland extract: (c) OpenMapTiles (c) OpenStreetMap contributors. Keep this attribution visible if you keep the sample tileset in a public-facing deployment.
  • Restrict access: TileServer GL serves plain HTTP on port 80. For production, restrict access to trusted IP ranges in your network security group, and front it with TLS (for example, certbot with your own domain) terminating on :443.
  • Loopback binding: TileServer GL is bound to 127.0.0.1:8080 (--bind 127.0.0.1 in /etc/systemd/system/tileserver-gl.service), so nginx is the only path in. Keep it that way, and do not change the bind address to a public interface.
  • Server-side rendering: the raster tile-render endpoints run under xvfb-run (an off-screen X server), so the MapLibre GL Native renderer works with no GPU. This is invisible in normal operation. If you see rendering errors after adding a very large or unusual style, check journalctl -u tileserver-gl.
  • Data disk: the sample tileset and any tilesets you add live on the dedicated data disk at /var/lib/tileserver-gl. Resize the disk in the Azure portal (Disks tab) as your own datasets grow.
  • Security patches: unattended-upgrades stays enabled, so the OS keeps receiving security updates automatically.

Support

SecureAnyCloud provides 24/7 expert support for this image. Contact sales@secureanycloud.com.

Deploy on Azure

Launch TileServer GL on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure