The backend uses a Spring Boot standalone webserver and is written in Kotlin. A PostgreSQL database is used to store backend data and needs to be available when starting the backend.
Descriptors which are processed by TorMap are either consensus or server descriptors. A consensus descriptor contains network information which has been collected by Tor's directory authorities. A server descriptor contains detailed information about an individual relay that it published itself.
Processing of descriptors does not necessarily happen in a chronological order, but one month of descriptors is always processed together. Different descriptor types can be handled in parallel. Frontend features like family grouping or relay details will only be available, if the corresponding relay server descriptors have been processed.
- At least 50 GB of free disk space (for downloaded archives)
- Docker
- Oracle JDK
/ OpenJDK (
JAVA_HOMEshould point to a version >= 11)
- Go into
backenddirectory - Start PostgreSQL database:
docker compose up -d database - Run
./gradlew bootRunor on Windowsgradlew.bat bootRun - Backend should be available at http://localhost:8080
Run ./gradlew test, to only build & tests.
In a freshly cloned repository the backend will start to download an archive >33 GB in size
from TorProject. The total time for the whole archive will depend
on your bandwidth and hardware, but can be expected to take at least several hours.
For development purposes, an interactive Swagger UI is available under http://localhost:8080/swagger and the specification can also be viewed in raw JSON under http://localhost:8080/openapi. In the production environment, Swagger UI and API docs are disabled by default.
A selection of actuator endpoints are enabled by default at the management layer and exposed via HTTP Basic Auth
only. Ensure your deployment exposes only the actuator endpoints you need (Spring property
management.endpoints.web.exposure.include).
To enable admin access to /actuator/** endpoints. The admin username is configured via Spring
properties:
spring.security.user.name(default:admin)
The admin password is env-only and must be provided via one of the following environment variables:
TORMAP_ADMIN_PASSWORD(plaintext)TORMAP_ADMIN_PASSWORD_BCRYPT(BCrypt hash starting with$2a$,$2b$, or$2y$)
If neither env var is set, a warning is logged and actuator endpoints remain unavailable.
Recommended (production):
- Provide
TORMAP_ADMIN_PASSWORD_BCRYPTvia your secret manager (Kubernetes Secret, Docker secret, env etc.). - Do not put passwords/hashes into
application.yml.
Generate a BCrypt hash:
# Requires Docker; will prompt for password and print the hash to stdout
docker run --rm -it httpd:2.4-alpine htpasswd -nBC 12 adminbackend/src/main/resources/application.yml- main backend configbackend/src/main/resources/application-prod.yml- production specific overwritesbackend/src/main/resources/logback-spring.xml- logging optionsbackend/build.gradle.kts- dependencies are managed with Gradledocker-compose.yml- example docker compose file for production deploymentdocker-compose.override.yml- overridesdocker-compose.ymlfor local development.env.examplecan be copied to.env- control docker compose environment variables for local development
It is recommended to set following production environment variables via a secret manager:
TORMAP_DATABASE_PASSWORD- Overrides the database passwordTORMAP_ADMIN_PASSWORD- Optionally set admin password for accessing actuator endpointsNEW_RELIC_INGEST_KEY- Collect metrics to https://newrelic.com
TorMap uses DB files in MaxMind DB file format ( .mmdb) to map IPv4 addresses of Tor relays to geolocations and autonomous systems. The mapping is applied when descriptors are being processed and missing autonomous systems info is also updated regularly.
IP ranges and their geographic location changes over time. We only use the current IP to location data although the
location of some relays in the past might have been different. If desired, the .mmdb DB files can be updated every few
months, to keep the IP ranges up to date.
Replacing existing DB files:
- Download latest MMDB file from https://db-ip.com/db/download/ip-to-city-lite
- Replace zip archive
backend/ip-lookup/location/dbip-city-lite-<DATE>.zip - Create account and download latest GeoLite2 ASN MMDB file from https://www.maxmind.com/
- Replace zip archive
backend/ip-lookup/autonomous-system/GeoLite2-ASN-<DATE>.zip
First make sure you have installed all requirements for development.
Build fat JAR:
- Go to
backenddirectory where filegradlewis located - Run command:
./gradlew bootJar - A fat jar containing all packages should now be located in
backend/build/libs/. - Run command
java -jar <backend jar file> - Backend should be available at http://localhost:8080
Build docker image:
- Go to
backenddirectory where filegradlewis located - Make sure docker is installed and the docker daemon is running
- Run command:
./gradlew bootBuildImage - Run new image in a container with:
docker run -p 8080:8080 tormap/backend - Backend should be available at http://localhost:8080
Prebuild docker images are available at https://hub.docker.com/r/tormap/backend. An example docker compose file for
production deployment is available at docker-compose.yml. To execute it without development overrides, run
docker compose -f docker-compose.yml up -d.
- 50 GB of free disk space (for downloaded archives)
- 500 MB of RAM (typical actual backend usage is ~300 MB). It is recommended to set a JVM max heap size of 500 MB or
more (e.g.,
-Xmx500m) - Additional resources if PostgreSQL is deployed on the same machine
The backend enables CORS so specific frontend browser origins can call the public API. Configuration:
- Dev (default):
app.security.cors.allowed-origins: http://localhost:3000 - Prod:
application-prod.ymloverrides tohttps://tormap.org,https://www.tormap.org
Public GET endpoints under /relay/** can return Cache-Control headers to allow browser and CDN/proxy caching.
- Dev/default: caching headers are disabled (
app.http.cache.public.max-age-secondsdefaults to0). Responses are typically not cached unless a controller explicitly sets caching headers. - Prod:
application-prod.ymlsetsapp.http.cache.public.max-age-seconds: 300, so/relay/**responses includeCache-Control: public, max-age=300.