Map Hosting

Now that you've generated map data, you need to host it to your users.

S3

Pros:

  • Serverless

Cons:

  • Lots of small individual files

Normal server

Expected cost per month:

  • Static IP. As of April 1, 2020, $.004/hour or around $3/month.

Cloudflare

I use Cloudflare to save on bandwidth and improve download speeds. It's free! To point a URL to your server, add A records where the name is the subdomain you want to point, and the IP address is the static IP address of your server.

You could also set up multiple subdomains that point to the same server. This allows for HTTP multiplexing with HTTP 1.1, essentially letting map tile requests be parallelized. It appears with HTTP 2.0 this might be unnecessary, but it's simple and Cloudflare says that currently 10% of my requests are HTTP 1.1 requests.

Essentially, then in the tiles section of the Tile JSON you can list multiple domains, and all of them will return data in parallel.

Server Firewall

Since I'm sending all traffic through Cloudflare, I don't want any other HTTP or HTTPS access to the server. Allowing other access could 1) let attackers DDOS me by pointing directly to the IP address and 2) I could end up with higher egress bandwidth fees from Google since not everything is being cached by Cloudflare.

To set up the firewall, add the current list of Cloudflare IP addresses to a new firewall.

I found that Google got mad if I added IPv6 addresses, so I added just the ones below. Include tcp:80 and tcp:443 as protocols, and apply the firewall to the desired instances.

103.21.244.0/22
103.22.200.0/22
103.31.4.0/22
104.16.0.0/12
108.162.192.0/18
131.0.72.0/22
141.101.64.0/18
162.158.0.0/15
172.64.0.0/13
173.245.48.0/20
188.114.96.0/20
190.93.240.0/20
197.234.240.0/22
198.41.128.0/17

Now the server should only be accessible through your Cloudflare proxy!

Static IP

By default, the IP address you get when you start a Google Cloud server is ephemeral. It often stays the same for a while, but is allowed to change. In order for your DNS provider to make sure your URL always reaches your server, you need to make sure the IP address doesn't change. Here's an article on how to promote an ephemeral IP to address to a static IP address on Google Cloud.

mbtileserver

Now that we've set up the server, we need a program to serve the .mbtiles files. mbtileserver is a nice lightweight server for that, mapping HTTP requests to the desired tile within the SQLite file.

Install

First install Golang, then install the program

go get github.com/consbio/mbtileserver

Then the executable should reside at

~/go/bin/mbtileserver

Using

./go/bin/mbtileserver \
    `# Folder with mbtiles files` \
    -d mbtiles/ \
    `# Port to run on` \
    -p 8000 \
    `# Domain of website. Sets endpoint correctly in the Tile JSON` \
    --domain tiles.example.com \
    `# Should allow server to restart if needed` \
    --enable-reload \
    `# Verbose logging` \
    --verbose

Each mbtiles file is given its own server path. If an mbtiles file is named openmaptiles.mbtiles, the Tile JSON endpoint would be

localhost:8000/services/openmaptiles
and the tiles would be at
localhost:8000/services/openmaptiles/{z}/{x}/{y}.pbf

I haven't figured out how to successfully change the domain name in the Tile JSON yet, see #88.

Caddy

mbtileserver isn't meant to be directly exposed to the internet. For one, it's nice to add Cache-Control headers, which mbtileserver doesn't currently support. I use Caddy as a reverse proxy to call mbtileserver internally on port 8000.

Install

curl https://getcaddy.com | bash -s personal http.nobots,http.ratelimit

If you want to include the nobots and ratelimit extensions, use:

curl https://getcaddy.com | bash -s personal http.nobots,http.ratelimit

The caddy executable should now be on the default PATH.

Using

Add a text file named Caddyfile to the current directory (~ is fine). I use something like:

# Define each of the following websites, each on port 80
example.com:80, a_example.com:80, b_example.com:80 {
    proxy / localhost:8000
    proxy /services/openmaptiles localhost:8000 {
        header_downstream Cache-Control "public, max-age=20000, stale-while-revalidate=1000"
    }
    log caddy_log.log {
        rotate_size 10 # Rotate a log when it reaches 10 MB
        rotate_age  14  # Keep rotated log files for 14 days
        rotate_keep 2  # Keep at most 2 rotated log files
        rotate_compress # Compress rotated log files in gzip format
    }
    errors caddy_errors.log {
        rotate_size 10 # Rotate a log when it reaches 10 MB
        rotate_age  14  # Keep rotated log files for 14 days
        rotate_keep 2  # Keep at most 2 rotated log files
        rotate_compress # Compress rotated log files in gzip format
    }
}

Higher ulimit

Caddy complains that the default ulimit (number of open files at once) is too small. To fix this, in /etc/security/limits.conf I added:

* soft nofile 8192
* hard nofile 64000