Deployment guide

TileServer GL 5.6.0

Overview

TileServer GL is MapTiler's open-source map tile server. It serves vector tiles from MBTiles and PMTiles files and renders them to raster PNG, JPEG and WebP tiles on the server with MapLibre Native. It also serves MapLibre GL styles, fonts and TileJSON, draws static map images, and ships web viewers for every style and data source. Your web maps (MapLibre GL JS, Leaflet, OpenLayers) and GIS desktop tools (through WMTS) load maps from your own server, with no third-party maps API key.

This guide covers launching and using the TileServer GL 5.6.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud Hardened EHRbase & GIS, Health and Research Platforms AMIs listing on AWS Marketplace. You select it as the software version when you subscribe and launch.

What you get:

  • HTTPS from first boot. nginx serves everything on port 443 with a certificate generated on your instance's first boot, never baked into the image. Port 80 redirects to HTTPS.
  • A per-instance login. The maps, tiles and viewers sit behind HTTP Basic Auth (user admin) with a random password that is generated on first boot and stored bcrypt-hashed. One command switches to open, read-only access when you publish a basemap (Step 9).
  • TileServer GL on loopback only, as an unprivileged user. The tile server listens on 127.0.0.1:8080, runs as the tileserver system user in a systemd sandbox, and reads its code, data and styles read-only. The only open ports are 22, 80 and 443.
  • CORS you control. The image switches off TileServer GL's default Access-Control-Allow-Origin: *. Cross-origin access is allowed only for your instance's own HTTPS address plus the origins you add with tileserver-gl-allow-origin (none in the image).
  • Fixed host name and real client addresses. The instance drops requests for a host name it does not serve. Every TileJSON, style and viewer URL uses your configured host name, never one that a request supplied. nginx logs the real client address rather than forged forwarding headers.
  • Metadata service blocked. TileServer GL and nginx cannot reach the instance metadata service, so a style or data source that names a remote URL can never reach it. The AMI requires IMDSv2, and remote marker icons in static maps are off.
  • A patched dependency tree. TileServer GL 5.6.0 is installed from a pinned lock file with newer versions of handlebars, sharp, tar and morgan. These clear every high and critical npm audit advisory of the upstream release. The MapLibre GL JS 5.22.0 viewer bundle that the image serves carries the fix for GHSA-jrc7-96c5-q579 (an attribution sanitizer bypass). node-canvas and sqlite3 are compiled from source against the Ubuntu libraries.
  • A sample map so a map renders on first boot: the TileServer GL maintainers' own Zurich, Switzerland extract (OpenMapTiles schema, zoom 0 to 14), (c) OpenMapTiles (c) OpenStreetMap contributors, Open Database License (ODbL), with the bundled Basic preview style.
  • A separate data volume for your MBTiles and PMTiles at /var/lib/tileserver-gl, which you can resize on its own.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on for Ubuntu and for Node.js 22.

The image contains TileServer GL 5.6.0, Node.js 22 LTS, nginx, Xvfb (the off-screen display server that rendering uses), the sample data and style, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

  • Default user: ubuntu
  • Architecture: x86_64
  • Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair, and a VPC subnet with a route to the internet (or a private route that you use for management).
  • A security group that allows TCP 22 (SSH) from your management network, and TCP 443 (maps and tiles) and TCP 80 (redirects to HTTPS) from the networks that will use your maps. Never open port 8080.
1

Launch the instance

In the AWS Marketplace listing, choose Continue to Subscribe and then Continue to Configuration. Pick TileServer GL 5.6.0 on Ubuntu 24.04 as the software version, choose your Region, and choose Launch through EC2. Choose m5.large, your key pair and a security group as described in Prerequisites.

Server-side rendering uses CPU, so choose a larger instance for busy raster or static map traffic. Size the data volume to the MBTiles and PMTiles files you plan to host.

Alternatively, launch with the AWS CLI. Replace the values in angle brackets:

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=tileserver-gl}]'
2

Connect to your instance

Version SSH user

TileServer GL 5.6.0 on Ubuntu 24.04 ubuntu

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the services

First boot takes a few seconds. After that, the three units are active:

systemctl is-active tileserver-gl nginx tileserver-gl-firstboot
active active active

TileServer GL (the node process) listens on loopback only, and nginx serves ports 80 and 443:

sudo ss -tlnp | awk 'NR==1 || /node|nginx/' | awk '{print $1, $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g'
State Local Peer LISTEN 127.0.0.1:8080 users:(("node")) LISTEN 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx")) LISTEN 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx")) LISTEN [::]:80 users:(("nginx"),("nginx"),("nginx")) LISTEN [::]:443 users:(("nginx"),("nginx"),("nginx"))
4

Read your login and URLs

First boot writes this instance's URL, login and password to a root-only file. Display the file:

sudo cat /root/tileserver-gl-credentials.txt
# TileServer GL 5.6.0 on Ubuntu 24.04 (sac AWS Marketplace image)
# Written at first boot. These values are unique to this instance; keep them safe. TILESERVER_URL=https://<public-dns-name>/ TILESERVER_USERNAME=admin TILESERVER_PASSWORD=<TILESERVER_PASSWORD> TILESERVER_ACCESS=private TILESERVER_STYLE_ID=basic-preview TILESERVER_TILEJSON_URL=https://<public-dns-name>/data/v3.json TILESERVER_STYLE_URL=https://<public-dns-name>/styles/basic-preview/style.json TILESERVER_SAMPLE_TILE_URL=https://<public-dns-name>/styles/basic-preview/256/10/536/359.png
#
# Sign in at TILESERVER_URL as admin with TILESERVER_PASSWORD (the certificate is self-signed until you add your own).
# Open access (no login):
sudo tileserver-gl-access public New password:
sudo tileserver-gl-reset-password
# CORS allow-list:
sudo tileserver-gl-allow-origin add https://your-map-site.example
# Served name:
sudo tileserver-gl-set-hostname <your-dns-name>
# Your data: /var/lib/tileserver-gl/data (MBTiles/PMTiles), /etc/tileserver-gl/config.json
# Sample data licence:
cat /var/lib/tileserver-gl/DATA-LICENSES.txt ((c) OpenMapTiles (c) OpenStreetMap contributors, ODbL)
# User guide: https://secureanycloud.com/stacks/tileserver-gl-5-6-0/

The host name is your instance's public DNS name from the instance metadata service, or its public or private IP address when it has no DNS name. Step 11 shows how to serve the instance under your own name.

5

Open TileServer GL in your browser

Browse to the TILESERVER_URL from Step 4. The certificate is self-signed and unique to your instance, so your browser asks you to accept it once. Step 11 replaces it with your own.

Sign in as admin with the TILESERVER_PASSWORD. The front page lists every style and data source, with links to their viewers and to the GL Style, TileJSON, WMTS and XYZ service URLs.

Viewer opens the style in MapLibre GL JS, drawn in your browser from the vector tiles (the picture at the top of this guide). Raster opens the same style in Leaflet, built from PNG tiles that TileServer GL renders on the server.

Inspect on a data source shows its vector layers, color-coded, so you can see what a tile set contains.

6

Request tiles from the command line

Without a login, the server refuses every request. On the instance, the commands below read the password from the credentials file through a curl config, so it never appears on a command line. A raster tile rendered on the server is a real PNG:

curl -sk -o /dev/null -w 'without a login: HTTP %{http_code}\n' https://127.0.0.1/data/v3.json
curl -sk -K <(sudo sed -n 's/^TILESERVER_PASSWORD=\(.*\)/user = "admin:\1"/p' /root/tileserver-gl-credentials.txt) -o /tmp/tile.png -w 'rendered raster tile: HTTP %{http_code} %{content_type}, %{size_download} bytes\n' https://127.0.0.1/styles/basic-preview/256/10/536/359.png file /tmp/tile.png; rm -f /tmp/tile.png
without a login: HTTP 401 rendered raster tile: HTTP 200 image/png, 71806 bytes /tmp/tile.png: PNG image data, 256 x 256, 8-bit/color RGBA, non-interlaced

Vector tiles come straight from the MBTiles file:

curl -sk -K <(sudo sed -n 's/^TILESERVER_PASSWORD=\(.*\)/user = "admin:\1"/p' /root/tileserver-gl-credentials.txt) -o /tmp/tile.pbf -w 'vector tile: HTTP %{http_code} %{content_type}, %{size_download} bytes\n' https://127.0.0.1/data/v3/14/8580/5737.pbf rm -f /tmp/tile.pbf
vector tile: HTTP 200 application/x-protobuf, 392165 bytes

From another machine, use the full URL and your login. For example, run curl -k -u admin https://<public-dns-name>/styles/basic-preview/256/10/536/359.png -o tile.png. curl asks for the password.

7

TileJSON, styles and static maps

Map clients read the tile URL template, zoom range, bounds and attribution from TileJSON:

curl -sk -K <(sudo sed -n 's/^TILESERVER_PASSWORD=\(.*\)/user = "admin:\1"/p' /root/tileserver-gl-credentials.txt) https://127.0.0.1/data/v3.json | python3 -c 'import json,sys; d=json.load(sys.stdin); print(json.dumps({k: d.get(k) for k in ("tilejson","name","format","minzoom","maxzoom","bounds","attribution","tiles")}, indent=2))'
{ "tilejson": "3.0.0", "name": "OpenMapTiles", "format": "pbf", "minzoom": 0, "maxzoom": 14, "bounds": [ 8.275, 47.225, 8.8, 47.533 ], "attribution": "<a href=\"https://www.openmaptiles.org/\" target=\"_blank\">&copy; OpenMapTiles</a> <a href=\"https://www.openstreetmap.org/copyright\" target=\"_blank\">&copy; OpenStreetMap contributors</a>", "tiles": [ "https://<public-dns-name>/data/v3/{z}/{x}/{y}.pbf" ] }

The tiles URL always carries your configured host name, even when the request arrived through another address (here 127.0.0.1). A MapLibre GL JS map loads the style from https://<public-dns-name>/styles/basic-preview/style.json.

TileServer GL also draws static map images of any size, for reports and previews:

curl -sk -K <(sudo sed -n 's/^TILESERVER_PASSWORD=\(.*\)/user = "admin:\1"/p' /root/tileserver-gl-credentials.txt) -o /tmp/zurich.png -w 'static map: HTTP %{http_code} %{content_type}, %{size_download} bytes\n' 'https://127.0.0.1/styles/basic-preview/static/8.54,47.37,13/800x600.png' file /tmp/zurich.png; rm -f /tmp/zurich.png
static map: HTTP 200 image/png, 266384 bytes /tmp/zurich.png: PNG image data, 800 x 600, 8-bit/color RGBA, non-interlaced
8

Security checks

The instance refuses writes, drops unknown host names, gives a foreign web origin no CORS grant, and walls both services off from the instance metadata service. Run these checks:

curl -sk -o /dev/null -w 'PUT: HTTP %{http_code}\n' -X PUT https://127.0.0.1/data/v3.json
curl -s -o /dev/null -w 'unknown Host on port 80: HTTP %{http_code} (connection closed)\n' -H 'Host: attacker.example' http://127.0.0.1/ || true
curl -sk -D - -o /dev/null -K <(sudo sed -n 's/^TILESERVER_PASSWORD=\(.*\)/user = "admin:\1"/p' /root/tileserver-gl-credentials.txt) -H 'Origin: https://attacker.example' https://127.0.0.1/data/v3.json | grep -ci '^access-control-allow-origin' | sed 's/^/ACAO headers for a foreign origin: /' || true systemctl show -p IPAddressDeny tileserver-gl nginx
PUT: HTTP 405 unknown Host on port 80: HTTP 000 (connection closed) ACAO headers for a foreign origin: 0 IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32 IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32

Port 80 only redirects to HTTPS. Confirm the redirect:

curl -s -o /dev/null -w 'http://127.0.0.1/ -> HTTP %{http_code} %{redirect_url}\n' http://127.0.0.1/
http://127.0.0.1/ -> HTTP 301 https://<public-dns-name>/
9

Private or public access

The image starts private: every map, tile and viewer needs the login. TileServer GL has no accounts, no administration pages and no write endpoints. When you publish a basemap for your own web maps, you can open read-only access to everyone who can reach port 443. Switch the mode with these commands:

sudo tileserver-gl-access public
sudo tileserver-gl-access private

Check the current mode:

sudo tileserver-gl-access status
private

To replace the password with a new random one, run the command below. It writes the new password to /root/tileserver-gl-credentials.txt.

sudo tileserver-gl-reset-password
10

Let your map site use the tiles (CORS)

A map front end on another web origin (for example https://maps.example.com) needs a CORS grant to fetch your tiles from the browser. Add that origin to the allow-list, and nginx reloads immediately. An origin is https://host or https://host:port, with no path. The command refuses *.

sudo tileserver-gl-allow-origin add https://maps.example.com
sudo tileserver-gl-allow-origin remove https://maps.example.com

List the origins you have added. The image ships with none, so only the instance's own HTTPS address is allowed.

sudo tileserver-gl-allow-origin list

A browser on another origin does not send your login with tile requests, so cross-origin maps also need public access (Step 9). Browsers fetch cross-origin tiles only from a server whose certificate they trust, so first serve the tiles under your own DNS name and certificate (Step 11).

11

Your own DNS name and certificate

Point a DNS record at the instance, and then set the name that the instance serves. The command mints a new certificate for that name and pins it in nginx. It also updates every TileJSON, style and viewer URL and the credentials file (the password is kept), and it records the name so that it survives reboots.

sudo tileserver-gl-set-hostname maps.example.com

To use a certificate from a public CA instead of the self-signed one, run the command above first. Then replace /etc/ssl/tileserver-gl/tileserver-gl.crt (full chain) and /etc/ssl/tileserver-gl/tileserver-gl.key (mode 0600, owned by root) with your own files, and reload nginx:

sudo nginx -t && sudo systemctl reload nginx
12

Serve your own data

TileServer GL reads /etc/tileserver-gl/config.json. Paths are relative to the data volume /var/lib/tileserver-gl, with tile files in data/, styles in styles/ and glyphs in fonts/. Display the configuration:

cat /etc/tileserver-gl/config.json
{ "options": { "paths": { "root": "/var/lib/tileserver-gl", "fonts": "fonts", "styles": "styles", "sprites": "sprites", "icons": "icons", "files": "files", "mbtiles": "data", "pmtiles": "data" }, "allowRemoteMarkerIcons": false, "allowInlineMarkerImages": false, "serveAllStyles": false, "serveAllFonts": false }, "styles": { "basic-preview": { "style": "basic-preview/style.json", "tilejson": { "bounds": [ 8.275, 47.225, 8.8, 47.533 ] } } }, "data": { "v3": { "mbtiles": "zurich_switzerland.mbtiles" } } }

Copy your MBTiles or PMTiles file to the data folder and add it under data (for example "my-area": {"mbtiles": "my-area.mbtiles"} or {"pmtiles": "my-area.pmtiles"}). Then point a style's source at it with mbtiles://{my-area}.

The Basic preview style expects the OpenMapTiles schema, so an OpenMapTiles extract of your own region (from MapTiler or built with the OpenMapTiles tools) drops straight in under the v3 name. Then restart the service:

sudo install -o root -g root -m 0644 my-area.mbtiles /var/lib/tileserver-gl/data/ sudoedit /etc/tileserver-gl/config.json
sudo systemctl restart tileserver-gl

The full configuration reference is in the TileServer GL documentation (tileserver.readthedocs.io). Styles that name remote sources, sprites or glyphs work too, and the metadata block above stays in force for them.

To grow the data volume, resize it in EC2 and run sudo resize2fs on its device.

13

Versions, build provenance and licenses

node -v
node -p 'require("/opt/tileserver-gl/node_modules/tileserver-gl/package.json").version' sed -n '1,9p' /usr/share/doc/tileserver-gl/BUILD-PROVENANCE.txt
v22.23.3 5.6.0 TileServer GL 5.6.0 - sac build (AWS Marketplace image) package tileserver-gl@5.6.0 from registry.npmjs.org (npm sha512 integrity in /opt/tileserver-gl/package-lock.json) lock sha256 c76c5e659ad4457ad6b8e1fae0e180437813b19645f39e618b51a87b88bb2b4e installed tileserver-gl@5.6.0 handlebars@4.7.10 morgan@1.12.1 sharp@0.35.5 tar@7.5.22 maplibre-gl@6.15.0 canvas@3.2.3 sqlite3@5.1.7 @maplibre/maplibre-gl-native@6.4.1 tileserver-gl-styles@2.0.0 express@5.2.1 runtime Node.js v22.23.3 (NodeSource node_22.x apt repository, signing key fingerprint 6F71F525282841EEDAF851B42F59B5F99B1BE0B4) dependency patches (d-102, npm overrides): handlebars 4.7.10, morgan 1.12.1, sharp 0.35.5, tar 7.5.22; node_modules/maplibre-gl 6.15.0 (copy source only; never served) npm audit --omit=dev: critical=0 high=0 moderate=0 low=5 native modules canvas 3.2.3 and sqlite3 5.1.7 compiled from source (node-gyp) against the Ubuntu 24.04 libraries;

The full provenance, including the MapLibre Native binary checksum and the viewer fix, is in /usr/share/doc/tileserver-gl/BUILD-PROVENANCE.txt. The instance also attributes the sample data and the style:

grep -A4 '^Sample map data' /var/lib/tileserver-gl/DATA-LICENSES.txt
Sample map data: /var/lib/tileserver-gl/data/zurich_switzerland.mbtiles (Zurich, Switzerland, zoom 0-14) The tileserver-gl maintainers' own quickstart dataset (GitHub release v1.3.0). (c) OpenMapTiles (https://openmaptiles.org/) (c) OpenStreetMap contributors (https://www.openstreetmap.org/copyright) OpenStreetMap data is available under the Open Database License (ODbL) v1.0: https://opendatacommons.org/licenses/odbl/1-0/ - credit "(c) OpenStreetMap contributors" wherever you show it.

Keep the (c) OpenMapTiles (c) OpenStreetMap contributors attribution whenever you serve or redisplay the sample tiles. TileServer GL shows it in its viewers and TileJSON.

The Basic preview style derives from the Mapbox Open Styles (code BSD, design CC BY 3.0), and its Noto Sans glyphs use the SIL Open Font License.

Server components

Component Version Path

TileServer GL 5.6.0 /opt/tileserver-gl/node_modules/tileserver-gl

Node.js (NodeSource) 22 LTS /usr/bin/node

nginx (HTTPS front door) Ubuntu 24.04 package /etc/nginx/sites-available/sac-tileserver-gl

Sample data Zurich, zoom 0 to 14 /var/lib/tileserver-gl/data/zurich_switzerland.mbtiles

Style and glyphs Basic preview, Noto Sans /var/lib/tileserver-gl/styles, /var/lib/tileserver-gl/fonts

Filesystem layout

Path Description

/var/lib/tileserver-gl The data volume: data/ (MBTiles, PMTiles), styles/, fonts/, sprites/, icons/

/etc/tileserver-gl/config.json TileServer GL configuration (styles and data sources)

/etc/tileserver-gl/tileserver-gl.env TILESERVER_PUBLIC_URL, the base of every URL TileServer GL returns (set by tileserver-gl-set-hostname)

/etc/sac/tileserver-gl-cors-origins Your CORS allow-list (managed by tileserver-gl-allow-origin)

/etc/sac/tileserver-gl-hostname The served name you set with tileserver-gl-set-hostname

/etc/nginx/tileserver-gl.htpasswd The bcrypt hash of the login password

/etc/ssl/tileserver-gl/ This instance's TLS certificate and private key

/root/tileserver-gl-credentials.txt This instance's URL, login and password, written at first boot

/usr/share/doc/tileserver-gl/ Licences, build provenance and data licences

Managing the services

systemctl status tileserver-gl --no-pager | head -5

After you change config.json or add data, restart TileServer GL with sudo systemctl restart tileserver-gl. Read its log with journalctl -u tileserver-gl -n 50 --no-pager.

Both services start automatically at boot, after the first boot unit has created the password and the certificate.

Security recommendations

  • Keep TCP 22 limited to your management network, and open 443 and 80 only to the networks that need your maps.
  • Keep private access while you evaluate the image or when you serve data that is for your organization only. Switch to public only for maps you mean to publish (Step 9).
  • Serve under your own DNS name and a certificate from a public CA before you publish a map site (Step 11).
  • Add only the web origins that really need cross-origin access (Step 10).
  • Load only map data, styles and TileJSON sources that you trust. The viewers render the attribution text that those files carry in each visitor's browser. The MapLibre GL JS 5.22.0 viewer bundle in this image includes the upstream fix for its attribution sanitizer (GHSA-jrc7-96c5-q579). However, upstream has no TileServer GL release that ships a fixed MapLibre yet. The viewers take their style and data only from your server's own configuration, never from the page address.
  • Unattended upgrades apply Ubuntu security updates and Node.js 22 updates. Reboot when /var/run/reboot-required appears.

Troubleshooting

Maps or tiles do not load. Check systemctl is-active tileserver-gl nginx. Both services wait for the first boot unit, and journalctl -u tileserver-gl-firstboot --no-pager shows what that unit did. If tileserver-gl is not active, read journalctl -u tileserver-gl -n 100 --no-pager. A typing error in config.json or a missing data file stops the service at startup.

The browser keeps asking for a password. Use the user admin and the TILESERVER_PASSWORD from /root/tileserver-gl-credentials.txt, or switch to public access (Step 9).

The browser shows a certificate warning. The certificate is self-signed and unique to your instance. Accept it for testing, or install your own name and certificate (Step 11).

A tile request returns 204 or 404. Tiles outside the data's bounds or above its maximum zoom (14 for the sample) do not exist. The data path is /data/<id>/{z}/{x}/{y}.pbf and the rendered path is /styles/<style>/256/{z}/{x}/{y}.png.

Your map site's browser console reports a CORS error. Add the site's origin with sudo tileserver-gl-allow-origin add https://<site> (Step 10), switch to public access, and make sure that the browser trusts the tile server's certificate.

Support and licensing

TileServer GL is open-source software by MapTiler under the BSD 2-Clause license. The TileServer GL code in this image is the upstream 5.6.0 release. SecureAnyCloud installs it with newer versions of some dependencies and backports one upstream MapLibre GL JS fix into the viewer bundle that it serves. Both changes are listed in the build provenance file.

SecureAnyCloud provides 24/7 technical support for this image by email and live chat at sales@secureanycloud.com, with a 24 hour response SLA.

All product names are trademarks of their respective owners. SecureAnyCloud is not affiliated with or endorsed by MapTiler, OpenMapTiles, the OpenStreetMap Foundation or the MapLibre project.

Deploy on AWS

Launch TileServer GL 5.6.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS