SelfHost Hub SelfHost Hub
中文
← Back to all services

GitHub repository information

Fetched · June 5, 2026
★ 94 Latest: v2.8.0 Updated: June 3, 2026
README
# Geo2Tz

[![QA](https://github.com/noandrea/geo2tz/actions/workflows/quality.yml/badge.svg)](https://github.com/noandrea/geo2tz/actions/workflows/quality.yml) [![GoDoc](https://godoc.org/github.com/noandrea/geo2tz?status.svg)](https://godoc.org/github.com/noandrea/geo2tz) [![Go Report Card](https://goreportcard.com/badge/github.com/noandrea/geo2tz)](https://goreportcard.com/report/github.com/noandrea/geo2tz)

A self-hostable service to get the timezone given geo-coordinates (lat/lng)

Timezone data comes from [github.com/evansiroky/timezone-boundary-builder](https://github.com/evansiroky/timezone-boundary-builder).

[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://stand-with-ukraine.pp.ua/)

## Maturity Level

This project is considered mature and stable, having undergone extensive testing and refinement over time. It is now in a state where it can be reliably used in production environments. The badge below shows the number of Docker pulls for the project:

<!-- ![Docker Pulls](https://img.shields.io/docker/pulls/noandrea/geo2tz?style=for-the-badge) -->
![GHCR Pulls](https://ghcr-badge.elias.eu.org/shield/noandrea/geo2tz/geo2tz)

### Contributing
We value your feedback and contributions! If you encounter any bugs or have ideas for new features, please don't hesitate to [open an issue](https://github.com/noandrea/geo2tz/issues/new). Your input is crucial in helping us improve and evolve the project.

## Motivations

Geo-coordinates can be sensitive information. This project provides a privacy-friendly, self-hosted solution that ensures coordinates are not leaked to third-party services.

## API

The service exposes two endpoints: one to look up the timezone for a pair of coordinates, and one to report the version of the timezone database in use.

### Timezone lookup

```http
GET /tz/${LATITUDE}/${LONGITUDE}
```

Returns a JSON reply (`http/200`), for example:

```console
curl -s http://localhost:2004/tz/51.477811/0 | jq
```

```json
{
  "coords": {
    "lat": 51.47781,
    "lon": 0
  },
  "tz": "Europe/London"
}

```

On invalid input it returns a `4xx` response, for example:

```console
curl -v http://localhost:2004/tz/51.477811/1000 | jq
*   Trying 127.0.0.1:2004...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to localhost (127.0.0.1) port 2004 (#0)
> GET /tz/51.477811/1000 HTTP/1.1
> Host: localhost:2004
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 400 Bad Request
< Content-Type: application/json; charset=UTF-8
< Vary: Origin
< Date: Fri, 23 Jun 2023 19:09:29 GMT
< Content-Length: 54
<
{ [54 bytes data]
100    54  100    54    0     0  89403      0 --:--:-- --:--:-- --:--:-- 54000
* Connection #0 to host localhost left intact

```

```json
{
  "message": "lon value 1000 out of range (-180/+180)"
}
```

### Database version

The version of the database in use is exposed at `/tz/version`:

```console
curl -s http://localhost:2004/tz/version | jq
```

```json
{
  "version": "2026b",
  "url": "https://github.com/evansiroky/timezone-boundary-builder/releases/tag/2026b",
  "geo_data_url": "https://github.com/evansiroky/timezone-boundary-builder/releases/download/2026b/timezones-with-oceans.geojson.zip"
}
```

Coordinates are decimal degrees in the ranges `[-90, 90]` for latitude and `[-180, 180]` for longitude.

### Authorization

Geo2Tz supports a basic token authorization mechanism, if the configuration value for `web.auth_token_value` is a non-empty string, geo2tz will check the query parameter value to authorize incoming requests.

For example, running the service with:

```sh
docker run --pull=always -p 2004:2004 -e GEO2TZ_WEB_AUTH_TOKEN_VALUE=secret ghcr.io/noandrea/geo2tz:latest
```

will enable authorization. With the authorization enabled, a query that does not specify the token will fail with an HTTP code 401:

```sh
> curl -sv http://localhost:2004/tz/41.902782/12.496365 | jq
```

```
*   Trying 127.0.0.1:2004...
* Connected to localhost (127.0.0.1) port 2004 (#0)
> GET /tz/41.902782/12.496365 HTTP/1.1
> Host: localhost:2004
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< Content-Type: application/json; charset=UTF-8
< Vary: Origin
< Date: Sun, 31 Jul 2022 20:06:56 GMT
< Content-Length: 27
<
{ [27 bytes data]
* Connection #0 to host localhost left intact
{
  "message": "unauthorized"
}
```

Passing the token in the query parameters will succeed instead:

```sh
> curl -s http://localhost:2004/tz/41.902782/12.496365\?t\=secret | jq
```

```json
{
  "coords": {
    "lat": 41.902782,
    "lon": 12.496365
  },
  "tz": "Europe/Rome"
}
```

## Configuration

Geo2Tz is configured via environment variables (prefixed with `GEO2TZ_`) or an optional config file. Defaults are listed below.

| Environment variable | Default | Description |
| --- | --- | --- |
| `GEO2TZ_WEB_LISTEN_ADDRESS` | `:2004` | Address the HTTP server binds to. |
| `GEO2TZ_WEB_AUTH_TOKEN_VALUE` | (empty) | When non-empty, enables token authorization. |
| `GEO2TZ_WEB_AUTH_TOKEN_PARAM_NAME` | `t` | Query-parameter name carrying the auth token. |
| `GEO2TZ_TZ_DATABASE_NAME` | bundled tz DB | Path to the timezone GeoJSON database. |
| `GEO2TZ_TZ_VERSION_FILE` | bundled version file | Path to the version metadata file. |

A config file is loaded automatically when present at `/etc/geo2tz/config.{yaml,toml,json}`. A custom path can be passed with `--config`. Keys mirror the env vars but are nested under `web.*` / `tz.*` (e.g. `web.auth_token_value`).

## Docker

Docker image is available at [geo2tz](https://github.com/noandrea/geo2tz/pkgs/container/geo2tz)

```sh
docker run --pull=always -p 2004:2004 ghcr.io/noandrea/geo2tz:latest
```

The image is built `FROM` [scratch](https://hub.docker.com/_/scratch), so it contains only the `geo2tz` binary and the bundled timezone database — no shell, package manager, or utilities.

## Docker Compose

Docker Compose YAML example:

```yaml
services:
  geo2tz:
    container_name: geo2tz
    image: ghcr.io/noandrea/geo2tz:latest   # pin to a release tag for production deployments
    ports:
      - "2004:2004"
    restart: unless-stopped
    # uncomment to enable authorization via request token
    # environment:
    #   GEO2TZ_WEB_AUTH_TOKEN_VALUE: ${GEO2TZ_TOKEN}
    #   GEO2TZ_WEB_AUTH_TOKEN_PARAM_NAME: t
    #   GEO2TZ_WEB_LISTEN_ADDRESS: ":2004"
```

The `version` top-level field has been removed from the Compose spec and is no longer needed. The image is built `FROM scratch`, so it has no shell or `wget`/`curl` — Compose healthchecks based on those will not work; use an external probe instead.

## K8s

Kubernetes configuration example:

```yaml
---
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: geo2tz
  name: geo2tz
spec:
  replicas: 1
  revisionHistoryLimit: 3
  selector:
    matchLabels:
      app: geo2tz
  template:
    metadata:
      labels:
        app: geo2tz
    spec:
      containers:
        - name: geo2tz
          image: ghcr.io/noandrea/geo2tz:latest   # pin to a release tag for production deployments
          imagePullPolicy: Always
          ports:
            - name: http
              containerPort: 2004
          # if GEO2TZ_WEB_AUTH_TOKEN_VALUE is non-empty, token authorization is enabled
          # env:
          #   - name: GEO2TZ_WEB_AUTH_TOKEN_VALUE
          #     value: "secretsmaybebetter" # default is empty
          #   - name: GEO2TZ_WEB_AUTH_TOKEN_PARAM_NAME
          #     value: "t"                  # default value
          #   - name: GEO2TZ_WEB_LISTEN_ADDRESS
          #     value: ":2004"              # default value
          readinessProbe:
            httpGet:
              path: /tz/version
              port: http
            initialDelaySeconds: 2
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /tz/version
              port: http
            initialDelaySeconds: 10
            periodSeconds: 30
          resources:
            requests:
              cpu: 50m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi
---
# Service for the above deployment
apiVersion: v1
kind: Service
metadata:
  name: geo2tz-service
spec:
  type: ClusterIP
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: http
  selector:
    app: geo2tz
```

## Development notes

To update the timezone database you have a few options:

1. download the version specified in the `tzdata/version.json` file

```console
geo2tz update current
```

2. update to the latest version available

```console
geo2tz update latest
```

3. update to a specific version

```console
geo2tz update 2023b
```


The `update` command downloads the timezone GeoJSON zip and writes a version file into the `tzdata` directory; the version file is used to track the current version of the database.

Discover more