SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

Self-hosted location tracking apps compared

Traccar, OwnTracks, Dawarich or Home Assistant: pick the self-hosted location tracker that fits, and keep the ingest endpoint off the open internet.

Which self-hosted location tracking app fits your case

Self-hosted location tracking splits on one question: are you keeping a history for yourself, or watching a live position together with other people? Dawarich answers the first. It is a self-hosted replacement for Google Maps Timeline, and it imports the history you already have. Traccar answers the second. It was built for GPS hardware and fleets, and it treats a phone as one more tracker. OwnTracks sits underneath both as the phone app and the message format. Home Assistant already knows where your phone is if you run it, but it deletes the trail after ten days by default.

Pick by what you want to look at next month:

  • A map of everywhere you went last March, with places and trips: Dawarich.
  • A live map of several devices, with geofences and event rules: Traccar.
  • One phone posting its position into something you already run: OwnTracks in HTTP mode.
  • Home and away automations, with no interest in history: Home Assistant on its own.

None of it works until the phone can reach an HTTPS endpoint you control, so the server side gets its own section below. If you are still working out what else belongs on the box, the wider list of what is worth self-hosting this year shows where a tracker sits next to everything else.

Traccar: GPS hardware, fleets, and live position

Traccar is a tracking server. It speaks the protocols that dedicated GPS trackers use, each one listening on its own TCP and UDP port in the 5000 to 5150 range, and it puts a live map, geofences, event rules and reports on top. The web interface listens on port 8082 by default.

For phones there is the Traccar Client app for Android and iOS. It reports over the OsmAnd protocol, which listens on port 5055 by default. Its settings are the ones that decide your battery life and your row count. Distance asks for an update every N metres while moving. Interval gives time based reporting when distance is 0. Angle triggers an update on a heading change, in degrees. Stationary Heartbeat covers what happens when the device is not moving. Traccar's own documentation says the exact result is not guaranteed for any of them, because the phone decides.

Traccar ships with an embedded H2 database so the installer works with no setup, and the project does not recommend H2 for production. It points at MySQL or MariaDB for smaller servers, and PostgreSQL, optionally with TimescaleDB, for large ones. Move the database before you accumulate history, because converting an H2 file later is a manual job with no supported tool.

Where Traccar fits badly: it is a console for watching things now. The reports are fleet reports, trips and stops and summaries, so a year of your own weekends looks like a query result rather than a timeline.

OwnTracks: a phone app and an endpoint, nothing else

OwnTracks is the minimal classic setup. The app publishes a small JSON payload each time it decides the device has moved, and it can publish over MQTT (message queuing telemetry transport) or over plain HTTP. In HTTP mode the endpoint is a URL of the form http[s]://[user[:password]@]host[:port]/path, authenticated with HTTP Basic, and the project strongly recommends the https:// scheme. Against the OwnTracks Recorder the path is /pub.

The monitoring mode matters more than any server setting. Quiet publishes only when you ask. Manual adds region monitoring and a low power location request. Significant is the normal automatic mode. Move publishes as soon as the device has travelled locatorDisplacement metres or locatorInterval seconds have passed, whichever comes first, with defaults of 100 metres and 300 seconds. OwnTracks documents Move mode as using battery at the level of a navigation app, and suggests it for a trip or while charging rather than as an everyday setting.

The OwnTracks Recorder stores what arrives and serves a small map. Plenty of people never install it, because the payload is identical whether the app points at Recorder, at Dawarich, or at Home Assistant. That is the reason to start here: the app is a stable, boring producer, and you can change your mind about the consumer later.

Dawarich: a self-hosted Google Maps Timeline

Dawarich is AGPL-3.0 and runs from a Docker Compose file. A working install is four containers: the Rails application, a Sidekiq worker for background jobs, PostgreSQL, and Redis. The app serves port 3000. Import sources include Google Maps Timeline, OwnTracks, Strava, Immich, GPX and GeoJSON files, and the EXIF data in your photos. That import list is what makes it a timeline rather than a live map, because it can absorb history that predates the server.

Ingest is an HTTP endpoint carrying an API key from your account page. OwnTracks posts to /api/v1/owntracks/points?api_key=... and Overland posts to /api/v1/overland/batches?api_key=.... GPSLogger reuses the OwnTracks endpoint.

Notice where that key travels. It is in the query string, and nginx's default log format writes the full request line, so the key lands in /var/log/nginx/access.log in cleartext on every single point. Treat that log as a secret, rotate the key if you ship logs to anywhere central, and never paste a working URL into a support thread.

For the compose file itself, the volumes and the restart policy, use the container mechanics for running compose on a VPS. This page stops at the decision.

Home Assistant: good at presence, wrong for history

If you already run Home Assistant you already have device tracking. The companion app reports position, and the OwnTracks integration gives you a webhook URL and an encryption key to paste into the app. With MQTT configured, that integration listens for MQTT messages instead of HTTP.

The limit is retention. Home Assistant's recorder defaults to purge_keep_days: 10, and auto purge runs every night at 04:12 local time to stop the database growing without bound. That default is correct for a home automation database and wrong for a location archive. Home Assistant answers "is anyone home". It will not answer "where was I on 14 March" unless you send the same stream to something that keeps it.

Continuous tracking or occasional sharing?

Continuous tracking means the app publishes all day whether you are moving or not, and the archive it builds is the whole point. Occasional sharing means people watch each other during a trip, and the archive is a side effect nobody asked for. The same software serves both, with different settings.

For continuous tracking, favour displacement based reporting over a short timer, keep the database on storage you actually back up, and decide retention on the first day rather than the day the disk fills. For occasional sharing, raise the reporting rate only while it matters and put the phone back to a low power mode afterwards. OwnTracks makes that a control in the app, and Traccar Client does the same through its distance and interval fields.

If what you want is a two hour share link for a single trip, check the release notes of the server you choose before you commit. That feature is the one that changes most between versions, and it is the weakest area of every option here.

Do you need an MQTT broker at all?

Start without one. HTTP mode is a URL, a password and TLS (transport layer security), and every server named above accepts it. A broker earns its place when more than one thing needs the same stream at the same moment, such as Home Assistant reacting to a zone while a recorder writes history, or family members seeing each other.

MQTT is a publish and subscribe protocol, and Mosquitto is the usual broker. It listens on 1883 in cleartext and 8883 for TLS, and you want 8883 only. Access control is per topic, so one account can be allowed to publish owntracks/alice/phone and to read nothing else. That is the real argument for MQTT in a family setup: who sees whom is enforced by the broker, below the application, so an application bug cannot widen it.

The cost is one more service with its own certificate, its own user list and its own failure mode. A broker that stops accepting connections looks exactly like a phone with no signal, and neither one tells you.

How many position rows will one phone produce?

The figures below are arithmetic, not measurements. They assume a fixed reporting interval, one row per report, and nothing else.

ChartPosition rows per phone at a fixed reporting interval (arithmetic)
The data behind this chart
[
  {
    "label": "Every 30 seconds",
    "points_30d": "86,400",
    "points_365d": "1,051,200"
  },
  {
    "label": "Every 60 seconds",
    "points_30d": "43,200",
    "points_365d": "525,600"
  },
  {
    "label": "Every 5 minutes",
    "points_30d": "8,640",
    "points_365d": "105,120"
  },
  {
    "label": "Every 15 minutes",
    "points_30d": "2,880",
    "points_365d": "35,040"
  }
]

A phone reporting once a minute writes 43,200 rows a month and 525,600 rows a year. Back the interval off to fifteen minutes and the year costs 35,040 rows. Real apps are untidier than this, because they report on displacement as well as on time and they go quiet when the phone is still, so read the top row as a ceiling rather than a forecast.

Half a million rows a year is small for PostgreSQL. The cost that bites first is drawing them: a map asked to render a year of points at full resolution stalls the browser long before the database notices, which is why these apps aggregate history into trips and places. Measure the real number on your own box instead of trusting a figure from a blog post:

SELECT pg_size_pretty(pg_total_relation_size('tc_positions'));

That is Traccar's positions table on PostgreSQL. Run the equivalent for whichever server you pick after a month of ordinary use, then multiply by twelve. None of these servers deletes old points on its own. They keep what you send until you remove it, so pick a retention rule while the table is still small, and make sure the backup covers the database and not only the config, because a location history cannot be reconstructed from anything else.

The battery cost is your phone's decision, not your server's

This is the part self-hosting does not change. The operating system decides when the GPS wakes, how long a background app may run, and whether it survives the night. Android's power management suspends background work aggressively, and iOS hands out significant location changes rather than a continuous feed. Your server has no vote in any of it, so a shorter reporting interval in the app is a request, and the phone answers it however it likes.

What you control sits on the phone: grant background location permission for all the time, exclude the tracking app from battery optimisation, and match the reporting mode to the day. Move mode all week is how a phone ends up flat by four in the afternoon.

The server side symptom is a burst of rows carrying old timestamps. Both Traccar Client and OwnTracks buffer points while offline and flush them when the network comes back, so the write time and the fix time are different values. When your map draws a straight line across a city, that is a gap in the fixes, not a road.

The server side: TLS and an authenticated ingest path

The phone needs a hostname it can resolve and a certificate it trusts. A self-signed certificate is the most common reason a fresh install receives nothing at all: the app fails the TLS handshake and gives up, and the server log stays empty because the request never completed. An empty log looks like a phone problem and is not one.

There are two clean routes. Terminate TLS in nginx with a real certificate, which is a Let's Encrypt certificate in front of nginx, and open 443 only. Or expose no inbound port at all with a Cloudflare Tunnel so nothing is listening on the public IP, which suits Dawarich and OwnTracks because their ingest is ordinary HTTP.

One limit on the tunnel route. Traccar's OsmAnd protocol is HTTP, so a phone client passes through a reverse proxy or a tunnel without trouble. The binary protocols that dedicated GPS hardware speaks are raw TCP on their own ports, and those need a genuinely open port, opened on the VPS firewall and on your provider's separate network firewall, which is a different control panel on most hosts.

Once it answers, prove the path with one request instead of guessing from the app:

curl -si -u alice:secret -H 'Content-Type: application/json' \
  -d '{"_type":"location","lat":51.5,"lon":-0.12,"tst":1788480000}' \
  https://track.example.com/pub | head -1

A working endpoint replies with a 2xx status. A 401 means the credentials are wrong and the transport is fine. A curl error before any status line means DNS or TLS, so fix the certificate before you touch the app.

An open ingest endpoint leaks your location

An ingest URL with no authentication hands your position to whoever finds it, and people do find it.

  • Anyone holding the URL can write points into your history. False points are hard to notice and tedious to remove.
  • A URL is not a secret. It sits in the phone's app settings, in your reverse proxy access log, in your browser history if you ever tested it in a tab, and in whatever screenshot you attach when you ask for help.
  • An endpoint that answers unauthenticated GET requests will hand your latest position to a crawler that guessed the path.
  • The hostname is public whatever you do. A newly issued certificate appears in certificate transparency logs within minutes, so an address you never published is still discoverable.

Use HTTP Basic over TLS for OwnTracks, the API key for Dawarich, and Traccar's account model with a unique device identifier. Put the admin interface behind the same authentication as the ingest path, since the interface is where the whole trail is readable. Then look at who has been knocking after the first week:

sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

Your phone's mobile IP addresses should dominate that list. Anything else with a high count deserves a look, and a rate limit on the ingest location in nginx costs nothing.

A tracker also fails quietly. The container stops, the phone keeps buffering, and you learn about it weeks later when a trip is missing. Point a container health check or a systemd OnFailure= at your own ntfy server so the silence reaches you on the same phone that is doing the reporting.

Who else can see the trail, and how that is enforced

Traccar enforces access with users and device permissions: an account sees the devices an administrator granted it, and the API uses a session or a token. OwnTracks over MQTT enforces it in the broker with topic ACLs, which is the strongest of these, because an account that cannot subscribe to a topic cannot read it no matter what any application does. OwnTracks over HTTP has no equivalent layer, so whatever sits behind the endpoint is the whole access control story. Dawarich is built around one person reading their own history, so when several people need to watch each other live, Traccar or a broker is the closer fit.

Family tracking is a consent question as well as a technical one. Everyone whose phone publishes should know that it publishes and how to stop it, and a tracker that a person cannot turn off is not a family feature.

What self-hosting does not fix

  • The phone's operating system decides when the GPS wakes, so battery drain and gaps in the trail are mostly its doing and not your server's.
  • Google has changed how Timeline data is exported. Read Dawarich's current import documentation before you plan a migration, because the file Google hands you today is not the one people were importing two years ago.
  • Your endpoint sees your mobile IP address on every request, and so does every network in between. Self-hosting changes who holds that record. It does not remove the record.
  • A location history is irreplaceable. Back up the database, restore it once to prove the backup works, and keep the copy encrypted, because it is the most sensitive file on the server.

FAQ

Which self-hosted app replaces Google Maps Timeline?

Dawarich. It is built as a self-hostable alternative to Google Timeline, it imports a Google Maps Timeline export alongside OwnTracks, Strava, Immich, GPX and GeoJSON data and photo EXIF, and it presents history as places and trips rather than as a live map. Traccar can store the same points, but its interface is a fleet console and its reports are fleet reports. Check Dawarich's import documentation for the export format it expects before you plan the migration, because Google's export has changed.

Can I use OwnTracks without an MQTT broker?

Yes. Set the app to HTTP mode and give it a URL of the form http[s]://[user[:password]@]host[:port]/path, which is /pub for the OwnTracks Recorder. It sends the same JSON payload, authenticated with HTTP Basic, and OwnTracks strongly recommends the https:// scheme. Add a broker later, when two services need the same stream at once, or when you want per topic access control so the broker decides who can see whom.

How much storage does a year of location history need?

Plan in rows rather than in gigabytes. One report a minute is 525,600 rows a year per phone, and one every fifteen minutes is 35,040. Either is small for PostgreSQL. The practical limit is the browser drawing a year of points on one map, which is why these apps aggregate. Measure your own table after a month with pg_total_relation_size and multiply by twelve.

Why does my location history have gaps?

The phone's operating system decides when to wake the GPS and when to stop a background app, so most gaps begin there. Grant the app background location permission for all the time, exclude it from battery optimisation, and check the reporting mode. In OwnTracks, Move mode publishes every locatorDisplacement metres or locatorInterval seconds, defaulting to 100 metres and 300 seconds, and it draws battery at the level of a navigation app. If rows arrive late in a burst carrying old timestamps, that is the offline buffer flushing, so the fixes were taken and only the upload was delayed.

Is a secret URL enough to protect a location endpoint?

No. An unauthenticated ingest endpoint lets anyone who learns the URL write false points into your history, and a URL is not a secret: it lives in the app settings, in your reverse proxy access log, in your browser history, and in any screenshot you share. The hostname is discoverable on its own, because a new certificate shows up in certificate transparency logs shortly after issuance. Use HTTP Basic over TLS or an API key, rate limit the ingest path, and keep the admin interface behind the same authentication.

#self-hosting#privacy#traccar#owntracks#dawarich#gps