Tg
Application Stacks AWS

TileServer GL 5.6.0 on AWS User Guide

| Product: TileServer GL 5.6.0 on Ubuntu 24.04 on AWS

Overview

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

TileServer GL is MapTiler's open source map tile server. It serves vector tiles from MBTiles and PMTiles files, renders them to raster PNG, JPEG and WebP tiles on the server with MapLibre Native, 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.

The cloudimg image adds:

  • HTTPS from the 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 are behind HTTP Basic Auth (user admin) with a random password 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. TileServer GL's default Access-Control-Allow-Origin: * is switched off. 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 and real client addresses. Requests for a host name the instance does not serve are dropped, every TileJSON, style and viewer URL uses your configured host name (never one a request supplied), and 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, and the AMI requires IMDSv2. 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 that clear every high and critical npm audit advisory of the upstream release, and the MapLibre GL JS 5.22.0 viewer bundle it 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), © OpenMapTiles © 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 that 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 TileServer GL vector viewer drawing the Zurich sample map with the Basic preview style

What is on the image: TileServer GL 5.6.0, Node.js 22 LTS, nginx, Xvfb (the off screen display server 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 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.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick TileServer GL 5.6.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above. Server side rendering uses CPU; choose a larger instance for busy raster or static map traffic, and size the data volume to the MBTiles and PMTiles you plan to host.

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}]'

Step 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>

Step 3: Check the services

First boot takes a few seconds. Then 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; 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"))

Step 4: Read your login and URLs

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

sudo cat /root/tileserver-gl-credentials.txt
# TileServer GL 5.6.0 on Ubuntu 24.04 (cloudimg 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://www.cloudimg.co.uk/guides/tileserver-gl-5-6-0-on-ubuntu-24-04-aws/

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

Step 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:

The TileServer GL front page listing the Basic preview style and the OpenMapTiles data source

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:

The Leaflet viewer showing raster tiles rendered on the server from the Zurich vector data

Inspect on a data source shows its vector layers, colour coded, so you can see what a tile set contains:

The data inspector showing the OpenMapTiles vector layers of the Zurich extract

Step 6: Request tiles from the command line

Without a login every request is refused. 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 curl -k -u admin https://<public-dns-name>/styles/basic-preview/256/10/536/359.png -o tile.png (curl asks for the password).

Step 7: TileJSON, styles and static maps

Map clients discover 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

Step 8: Security checks

Writes are refused, unknown host names are dropped, a foreign web origin gets no CORS grant, and both services are walled off from the instance metadata service:

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:

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>/

Step 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, so when you publish a basemap for your own web maps you can open read only access to everyone who can reach port 443:

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 (written to /root/tileserver-gl-credentials.txt):

sudo tileserver-gl-reset-password

Step 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; nginx reloads immediately. An origin is https://host or https://host:port, with no path, and * is refused:

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 only fetch cross origin tiles from a server whose certificate they trust, so serve the tiles under your own DNS name and certificate (Step 11) first.

Step 11: Your own DNS name and certificate

Point a DNS record at the instance, then set the name the instance serves. This mints a new certificate for that name, pins it in nginx, updates every TileJSON, style and viewer URL and the credentials file (the password is kept), and records the name so 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, 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 after running the command above, then reload nginx:

sudo nginx -t && sudo systemctl reload nginx

Step 12: Serve your own data

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

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, add it under data (for example "my-area": {"mbtiles": "my-area.mbtiles"} or {"pmtiles": "my-area.pmtiles"}), and 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; 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.

Step 13: Versions, build provenance and licences

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 - cloudimg 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 (the MapLibre Native binary checksum and the viewer fix) is in /usr/share/doc/tileserver-gl/BUILD-PROVENANCE.txt. The sample data and the style are attributed on the instance:

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 © OpenMapTiles © 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/cloudimg-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/cloudimg/tileserver-gl-cors-origins Your CORS allow-list (managed by tileserver-gl-allow-origin)
/etc/cloudimg/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

Restart TileServer GL after you change config.json or add data with sudo systemctl restart tileserver-gl, and 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 or for internal data; 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 publishing 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 you trust. The viewers render the attribution text 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), but 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 wait for the first boot unit: journalctl -u tileserver-gl-firstboot --no-pager shows what it 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 it 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 the browser trusts the tile server's certificate.

Support and licensing

TileServer GL is open source software by MapTiler under the BSD 2-Clause licence. The TileServer GL code in this image is the upstream 5.6.0 release; cloudimg installs it with newer versions of some dependencies and backports one upstream MapLibre GL JS fix into the viewer bundle it serves (both listed in the build provenance file). cloudimg provides 24/7 technical support for this image by email and live chat: support@cloudimg.co.uk, with a 24 hour response SLA.

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