WorldMonitor puts news, markets, energy, supply chains, conflicts and natural disasters on one map, with a set of panels around it. The hosted version lives at worldmonitor.app, and the project can also be run on your own machine. There are now four FreeBSD packages around it, and they do very different things, so the first step is to know which one you want.
| port | version | what it is | needs |
|---|---|---|---|
www/worldmonitor | 2.11.8 | the dashboard itself, running on your own machine | amd64, Redis, a web server |
www/worldmonitor-cli | 0.2.1 | a terminal client for the hosted service | Node.js |
devel/py-worldmonitor-sdk | 0.1.2 | a Python client for the API | nothing but Python |
devel/rubygem-worldmonitor | 0.1.2 | a Ruby client for the API | nothing but Ruby |
Only the first one is a server. The other three are clients, and they talk to worldmonitor.app, not to your own installation. Most of this article is about the first one, because that is where the decisions are.
Installing it
Every port is available as a package and from the ports tree. The dashboard is the one to think about:
# pkg install worldmonitor nginx
nginx is named here because the package does not pull in a web server, you may already have one you prefer. Redis, curl and Node.js come in as dependencies. The clients are separate packages:
# pkg install worldmonitor-cli
# pkg install py312-worldmonitor-sdk
# pkg install rubygem-worldmonitor
The name of the Python package carries your Python version, so use the flavor that matches yours.
From the ports tree it is the usual:
# make -C /usr/ports/www/worldmonitor install clean
This is the only one of the four that takes a moment of thought. www/worldmonitor is built for amd64 only, because the Node.js modules it builds with contain prebuilt binaries for that platform. It builds the frontend from the upstream sources with Node.js 24, and the port description recommends at least 4 GB of memory for the build as well as for running it. The Node.js modules are not fetched at build time, they come as three archives from a release on GitHub.
Installing starts nothing and changes no configuration. What is left to do is in the next section.
The smallest setup that works
The dashboard is not one program. It is five services and a web server, and the package installs all of them but starts none:
rediskeeps the data. The dashboard shows only what is in Redis.worldmonitor_redis_restis an HTTP front for Redis, which is what the API handlers talk to.worldmonitor_relaycollects the live feeds: aircraft, ships, sirens and Telegram channels.worldmonitoris the local API server that runs the handlers.worldmonitor_seedis a loop that refills Redis every fifteen minutes.
nginx then serves the frontend and forwards everything below /api/ to the local API server. It is not installed as a dependency, which is why the install line above names it.
The file /usr/local/etc/worldmonitor.env is created from a sample and contains only example values. Four of them are secrets that you generate yourself:
$ openssl rand -hex 32
Put one value each into LOCAL_API_TOKEN, WM_SESSION_SECRET, UPSTASH_REDIS_REST_TOKEN and RELAY_SHARED_SECRET, and give WORLDMONITOR_RELAY_KEY the same value as RELAY_SHARED_SECRET. Then enable and start everything:
# sysrc redis_enable=YES worldmonitor_redis_rest_enable=YES worldmonitor_relay_enable=YES worldmonitor_enable=YES worldmonitor_seed_enable=YES
# service redis start
# service worldmonitor_redis_rest start
# service worldmonitor_relay start
# service worldmonitor start
# service worldmonitor_seed start
For nginx, the port ships a ready server block for port 8080:
# cp /usr/local/share/examples/worldmonitor/nginx-server.conf /usr/local/etc/nginx/worldmonitor.conf
In that file, replace change-me-token with the value of LOCAL_API_TOKEN and make sure only root and nginx can read it. The token is how nginx proves to the API server that a request came through it. Then add one line inside the http block of nginx.conf:
include worldmonitor.conf;
Enable and start nginx, and the dashboard is at http://host:8080/. Expect empty panels for a while: the seed service has to fill Redis first, and you can watch it work in /var/log/worldmonitor_seed.log.
The defaults that will surprise you
The services refuse to start with example secrets. A value that is empty or still starts with change-me stops the rc script with an error that names the variable. That is deliberate: a dashboard with a public example token is worse than one that does not start.
Only nginx listens on the network. The Redis REST proxy, the relay, the local API server and Redis all bind to 127.0.0.1. The rc scripts set the bind addresses of the proxy and the relay to loopback unless your environment file says otherwise, so an old file without those variables stays safe.
$ sockstat -4l | grep -E ':(8079|3004|46123|8080|6379)'
Redis has no password and no memory limit. It listens on loopback only, but every local user can read it. All the services run as www. On a shared machine, that is worth a thought.
The environment file is readable by root and the group www, nothing else. The package sets that, because the file holds the secrets.
The first run is slow, and the memory matters. The seed service runs six seeders at a time and pauses fifteen minutes between rounds. At least 4 GB of memory are recommended. On a smaller machine, lower the parallelism in /etc/rc.conf:
worldmonitor_seed_parallel="2"
A plain curl on the API gets a 401. That is not a fault. The API answers API key required to anything that does not carry a session token, and the browser fetches one first. By hand it takes two steps, and the token goes into the X-WorldMonitor-Key header, not into Authorization:
$ T=$(curl -s -X POST -H 'Content-Type: application/json' -d '{}' \
http://127.0.0.1:8080/api/wm-session | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
$ curl -s -H "X-WorldMonitor-Key: $T" http://127.0.0.1:8080/api/bootstrap | head -c 200
API keys for the data sources
Many panels need a key of the data provider behind them, and most of those are free. The environment file lists them as commented lines: FRED, EIA, Finnhub, Alpha Vantage, ACLED, NASA FIRMS, AISStream, OpenAQ, ENTSO-E and a few more. A panel without its key stays empty, the rest is not affected. After you add one, restart worldmonitor, worldmonitor_relay and worldmonitor_seed, because all three read the file at start.
The Pro panels
Pro is a paid plan of worldmonitor.app, and a self-hosted installation has no accounts of its own. From version 2.11.8 on, which is the one in the ports tree, the Pro panels can still be unlocked, with a desktop key:
- The subscriber creates a desktop key on worldmonitor.app under Settings, API Keys.
- The key is entered in the Settings of your dashboard.
- Your installation checks it with World Monitor and, if it is active, forwards that browser’s Pro requests to World Monitor with the key.
Every user enters their own key, and what they use counts against their own allowance. There is no operator key that unlocks Pro for every visitor, and your server sees each user’s key when it forwards, so users who do not run the installation themselves should create a separate key for it.
The check is easy to try without a real key. A well formed key that does not exist comes back as invalid, a malformed one is refused with 400:
$ curl -s -c jar -X POST -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:8080/api/wm-session > /dev/null
$ curl -s -b jar -X POST -H 'Content-Type: application/json' \
-d '{"key":"wm_0000000000000000000000000000000000000000"}' http://127.0.0.1:8080/api/selfhost-key
{"enabled":true,"status":"invalid","stale":false,"verifiedAt":1793884187333}
Three settings belong in the environment file for this:
WORLDMONITOR_SELFHOST_RELAY=offstops the forwarding and the key field in the Settings, for operators who do not want their server to relay anything.WORLDMONITOR_PUBLIC_HTTPS=1tells the installation that users arrive over HTTPS through a TLS proxy in front of it, so that the cookie holding the key is marked secure.WORLDMONITOR_VALID_KEYSis a separate thing: keys that unlock the premium API routes and the MCP server of your installation. These need no desktop key.
If you put your own proxy in front of the local API server instead of using the supplied server block, pass the browser’s Host header on as X-Forwarded-Host. Over plain HTTP the installation uses it to refuse requests from sibling pages on other ports.
When it does not work
Start with the status of the services, then the logs. Each service writes its own file:
# service worldmonitor status
# tail /var/log/worldmonitor.log /var/log/worldmonitor_relay.log \
/var/log/worldmonitor_redis_rest.log /var/log/worldmonitor_seed.log
A service will not start. Read the message. It names the variable that is empty or still has its example value.
The page loads, but the panels are empty. Check that Redis is filling up. After the first round of the seed service it holds a few thousand keys:
$ redis-cli dbsize
If the number stays near zero, the seed log says why. If it is high and one panel is empty, that panel is waiting for an API key.
A key is refused in the Settings. Only desktop keys work. A key of an API plan is reported as not being a desktop key.
The relay logs that curl is missing. The rc script of the relay needs /usr/local/bin in its path, because the relay calls curl. Packages from version 2.11.7 on set it.
The clients
The three clients talk to worldmonitor.app. Most of their data calls need a subscription API key of that service, passed in WORLDMONITOR_API_KEY.
www/worldmonitor-cli installs the commands worldmonitor and wm. The terminal dashboard also starts without a key, with the public data.
The two SDKs have no dependencies beyond the standard library of their language. Python:
from worldmonitor_sdk import Client
client = Client(api_key="wm_...")
client.list_tools()
client.country_risk("IR")
client.conflict_events(country="IR", limit=5)
And Ruby:
require "worldmonitor"
client = WorldMonitor::Client.new(api_key: "wm_...")
client.list_tools
client.country_risk("IR")
Listing the tools needs no key. Every call that returns data, except one source listing, does.
Removing it
After pkg delete, the configuration file, the data below /var/db/worldmonitor, the logs and the lines in rc.conf stay where they are, so a later install picks up where the last one stopped. Redis, curl and Node.js stay installed, and pkg autoremove takes them away if nothing else needs them.
What it does not do
It does not run Docker. LOCAL_API_MODE=docker in the environment file is the name upstream gives to the self-hosted mode, and it is the right value for this port.
It does not give anybody Pro without a subscriber’s own key, and it does not create accounts: sign-in with a worldmonitor.app account unlocks nothing on a self-hosted installation.
The military bases panel is built from open sources only, OpenStreetMap, MIRTA and a curated list. A fourth source needs a key that belongs to a third party and is not part of the port.
And the clients do not connect to your own installation. They are for the hosted service.
Where it is
The four ports, with package state, vulnerability status and the branches they are in:
The upstream project and its documentation on self-hosting: github.com/koala73/worldmonitor and worldmonitor.app/docs/self-hosting
The dashboard is AGPL-3.0, the clients are MIT.