Older documentation

This documentation hasn't been updated in a while. Some information might no longer be valid.

There may be more up to date information in the new documentation section.

Self-hosting a map tile server for location sharing

Thanks to MSC3488, the ability to share your location is coming soon to various Matrix clients, including FluffyChat (which has had this for some time) and Element.

When we are sharing information about our location, we need to be extra-cautious that it is kept private. Because Matrix provides robust, publicly-audited end-to-end encryption, we can be confident when we share a location, that the message we are sending is only going to be readable for the people in the room.

In addition to the messages we are sending, though, we need to consider other ways our information might leak out. In particular, when we are drawing maps on the screen, we need to talk to an additional server to fetch the map information, and the sections of map we are looking for (known as 'tiles') can reveal our approximate location if they get into the wrong hands.

Element is funding the hosting of a tile server that will be used by default by the Element clients, and will not share information with your homeserver, meaning that there is separation between the people who see which tiles are being requested, and the people who have access to metadata like the pattern of your Matrix activity.

But Matrix is all about giving you control over your own information and infrastructure, so here is a guide that will help you enjoy fully-featured location sharing in the Element client, while ensuring that all the tile requests go to servers that you control.

We will describe how to run OSM tile server stack, and how to add the config Element needs to use it to display maps.

Not production ready

Note: this is not a guide on how to host a robust, production-ready map tile server for many users. To do that you will need serious hardware, and further research on how to provide high availability, monitoring and everything else you will need in your own hosting environment.

This guide should give you the information you need to run a tile server locally, so you can begin learning what you need to bring one up in production.

(For a little more of what you will need for production-readiness, see Self-Host a Matrix Map Server, which includes some ansible scripts and hardware requirements.)

Prerequisites

This guide is built on Docker, so you will need an environment that supports Docker, or a compatible container engine.

The setup below was tested on a modern (2022) laptop running Ubuntu Linux 21.10, but should run fine on any Docker-compatible Linux, and maybe even elsewhere. For production use by large numbers of users, you will need very powerful machines, and lots of bandwidth.

All the map information we use below is freely available under open licenses from OpenStreetMap and Geofabrik, and we think what we recommend complies with their terms of use - please let us know if not!

Running the tile server

The OSM tile server stack is a map tile server that provides tiles via HTTP, and provides a simple web UI to browse the map data:

Download the raw data

In order to run a tile server, we need some map data, that we will then process and provide as map tiles. (Tiles are images of a particular area of the map, whereas the raw data contains the exact locations of roads, buildings etc.)

You can download map data (PBF files) from download.geofabrik.de. For example, to download data for the UK, run this command:

wget 'https://download.geofabrik.de/europe/great-britain-latest.osm.pbf'

Import it

Once we have the data, we need to import it into the tile server. Run these commands:

(Change "great-britain" to match what you downloaded.)

docker volume create openstreetmap-data
docker volume create openstreetmap-rendered-tiles
docker run \
    -v $PWD/great-britain-latest.osm.pbf:/data.osm.pbf \
    -v openstreetmap-data:/var/lib/postgresql/12/main \
    -v openstreetmap-rendered-tiles:/var/lib/mod_tile \
    -e THREADS=24 \
    overv/openstreetmap-tile-server:1.3.10 \
    import

This expects the downloaded PFB file to be in the current directory, and saves the imported data into the two docker volumes created by the first two commands.

The import process may take quite a while. On a reasonably powerful 2022 laptop the UK data took 39 minutes to import.

If you are running your tile server as a service, you will probably want to automate the process of downloading new data and importing it. Updating should involve re-running the download command from "Download the raw data", then re-running the import command above.

Run the tile server

Once the data has been imported, you can run your server like this:

docker run \
    -p 8080:80 \
    -v openstreetmap-data:/var/lib/postgresql/12/main \
    -v openstreetmap-rendered-tiles:/var/lib/mod_tile \
    -e THREADS=24 \
    -e ALLOW_CORS=enabled \
    -d overv/openstreetmap-tile-server:1.3.10 \
    run

This should launch the docker container in the background, which you can check with docker ps.

Test it

Once the server is running, you can grab a single file by going to http://127.0.0.1:8080/tile/0/0/0.png, or interact with the web UI at http://127.0.0.1:8080.

When you first start the service, it will take a lot of CPU and be quite unresponsive, but after a few minutes it should perform reasonably smoothly.

Providing the JSON file the client needs

Once the tile server is running, we need to provide the config so that Element (and any similar clients) can use it.

Element uses the MapLibre GL library to render maps, and MapLibre requires a style file that describes how to find map tiles. Here's how it looks:

The style file is quite simple. To describe a tile server on this machine, we need to create a file style.json like this:

{
  "version": 8,
  "sources": {
    "localsource": {
      "type": "raster",
      "tiles": [
        "http://127.0.0.1:8080/tile/{z}/{x}/{y}.png"
      ],
      "tileSize": 256,
      "attribution": "Maps Copyright 2022 <a href=\"http://www.geofabrik.de/\">Geofabrik GmbH</a> and <a href=\"http://www.openstreetmap.org/\">OpenStreetMap Contributors</a>"
    }
  },
  "layers": [
    {
      "id": "locallayer",
      "source": "localsource",
      "type": "raster"
    }
  ]
}

We need this file to be served up by the tile server, but that is easily done by slightly modifying the command we use to run it.

First stop your tile server. Use docker ps to find the CONTAINER ID of your tile server, then use docker kill <CID> (substituting in the CONTAINER ID instead of <CID>) to stop it.

Now re-launch the tile server, asking it to serve up our style file as well:

docker run \
    -p 8080:80 \
    -v $PWD/style.json:/var/www/html/style.json \
    -v openstreetmap-data:/var/lib/postgresql/12/main \
    -v openstreetmap-rendered-tiles:/var/lib/mod_tile \
    -e THREADS=24 \
    -e ALLOW_CORS=enabled \
    -d overv/openstreetmap-tile-server:1.3.10 \
    run

(Notice the extra line mentioning style.json. This assumes style.json is in the current directory.)

Now if I edit the config of my Element client to point to that style file, it will use our tile server to display its maps.

If you are self-hosting Element Web, you can add this to the top level of its config.json file:

{
   ... other config ...
   "map_style_url": "http://127.0.0.1:8080/style.json",
   ... other config ...
}

Also, if you run a home server, the Element client will prefer a tile server you specify in your .well-known area over its own config setting. You should serve up a file from your server root called /.well-known/matrix/client containing information like this:

{
  ... other info ...
  "m.tile_server": {
    "map_style_url": "http://mytileserver.example.com/style.json"
  }
  ... other info ...
}

Once you have one of those settings in place, your Element client should use your very own tile server every time it displays a map!

Further reading and acknowledgements

julian posted an excellent guide building on this one, which includes ansible scripts and some comments on the hardware needed:

This guide was constructed by reading the following links. Credit and thanks are due to their authors:

Some other useful links: