How a Small Update Almost Deleted My Entire GPS History – And What I Learned From It
Posted on July 4, 2026 • 12 min read • 2,357 wordsTL;DR
I’ve been living with self-hosted infrastructure for years: email, messaging, photo storage, home automation, and all the small services that quietly support everyday family life.
One of those services is Traccar. Over time, it became much more than a GPS tracker for me. It turned into a private archive of our life with our campervan: road trips across Europe, family travel, everyday driving, detours, and all the movement data that slowly became part of your memory.
This is the story of how I almost lost four years of that GPS history because of a hidden auto-delete rule inside my database, and how that incident pushed me to build a much more robust setup.
If you self-host services at home and think “my backups are probably fine”, this post is for you. You do not have to copy my full Kubernetes stack, but you should know that seemingly simple backup setups can fail in subtle ways.
At the time, my Traccar stack was simple and very typical for a homelab:
That setup fit neatly into the rest of my self-hosted world. I already ran multiple Docker Compose stacks across my NAS and VPS, and I used WireGuard to connect locations, devices, and even our van network back home. Traccar was also part of my Home Assistant ecosystem, where location data feeds into automations and presence-aware logic.
From a practical point of view, this setup felt solid.
For photos, documents, and config files, that kind of layering works really well. For a live PostgreSQL database, however, it is only a partial answer. It protects files, but it does not automatically give me database-aware, transactionally consistent, tested recovery.
At the time, I had never done a full restore test of the Traccar database from those Borg backups. I knew I had redundant copies of the data directory. I did not actually know how cleanly that database would come back in a real recovery scenario.
The incident started with something small.
I opened the Traccar map view to look up a route and noticed that a few older trips I clearly remembered simply were not there.
My first thoughts were harmless enough:
But the more I checked, the clearer it became: this was not a frontend issue. Entire parts of my GPS history were missing, especially older data beyond roughly one year.
For someone using Traccar as a rolling cache of recent movement, that might not be dramatic. For me, it was a serious problem. The whole point of this service was long-term history: years of campervan travel, everyday life, and routes I wanted to be able to revisit.
And the critical part was this: I had never intentionally configured any retention policy to delete old data.
My first instinct was to blame myself and the application config.
Maybe I had enabled some cleanup setting months ago and forgotten about it.
So I checked:
traccar.xmlI found nothing. No obvious retention settings. No cleanup scripts. No scheduled deletion logic.
So if Traccar was not deleting the history, something lower in the stack was.
In simple terms, my database had an invisible “auto-delete after one year” rule that I never realized was there.
It was not configured in the Traccar UI or in the main Traccar config file. It lived inside TimescaleDB’s own automation system as a background job, and it quietly removed older data over time.
That is the part I want lighter readers to take away: your application is not always the only place where important behavior is defined. Sometimes the dangerous default lives deeper in the stack.
Because Traccar uses TimescaleDB on top of PostgreSQL, the next logical place to look was TimescaleDB’s own job framework.
TimescaleDB can run background jobs for things like compression, aggregates, and retention. Retention means exactly what it sounds like: keep data for a defined time, then automatically remove older chunks.
If you are not comfortable with SQL, the important message is simply this: your database can have its own automatic cleanup rules.
If you are comfortable with SQL, this is how I checked my system:
SELECT *
FROM timescaledb_information.jobs
WHERE proc_name = 'policy_retention';That query showed retention jobs attached to the Traccar tables that hold historical data, including the positions table. Their configuration told TimescaleDB to drop data older than about one year.
So what I was seeing on the map was not mysterious at all. It was exactly what these jobs were designed to do.
That was fascinating from a technical perspective and deeply uncomfortable from an operational one.
The crucial detail is that I had never consciously added those retention policies myself.
The most likely explanation is an upstream integration change related to TimescaleDB support in Traccar. In other words, I updated software, the integration improved in one area, and a destructive default appeared in another.
From a time-series database perspective, a one-year retention policy can make sense. For some telemetry workloads, deleting old data is exactly what you want.
But that was absolutely not my use case.
I was not storing disposable metrics. I was storing a personal archive of journeys, routes, and years of movement history. If I had not caught this in time, I would have lost around four years of GPS history.
That was the moment where the issue stopped being “an interesting bug” and became a reminder that defaults are never neutral.
Luckily, two things worked in my favor.
First, I noticed the problem before even more history was deleted. Second, my existing backup chain still contained the older data.
That meant I could:
Removing the policies looked like this:
SELECT remove_retention_policy('public.tc_positions');
SELECT remove_retention_policy('public.tc_events');
SELECT remove_retention_policy('public.tc_actions');And then I verified again:
SELECT *
FROM timescaledb_information.jobs
WHERE proc_name = 'policy_retention';After that, my historical data was back and the silent deletion path was gone.
So yes, my old backup strategy did save me.
But it also exposed a weakness I could no longer ignore.
This incident changed the way I think about backups.
Before that, I mostly thought in layers of file protection:
That is not the same thing as a proper database recovery strategy.
What I took away from this:
This was not just a technical lesson. It was also a mindset shift. I stopped treating PostgreSQL as “just another directory to copy” and started treating it as a first-class system with its own recovery model.
Given my background in Kubernetes, DevOps, and infrastructure, the natural next step for me was to move toward a database setup that is much more intentional.
The target architecture I am building towards looks like this:
I already have a first prototype of this setup running with Listmonk. That gave me a safe place to learn how the operator behaves, how the backup flow works, and how restore procedures feel before moving something as important as Traccar.
The key difference is conceptual.
Instead of indirectly protecting database files with general-purpose file replication and backups, I now want PostgreSQL-aware backup and recovery:
That is a much more serious answer to the question “how do I recover this data if things go really wrong?”
In my k3s cluster, I also added Longhorn as the storage layer for persistent volumes.
That means:
In practice, this replaces the role Syncthing used to play in my old Docker Compose world. The difference is that the responsibilities are much cleaner now.
That separation matters. I no longer expect one backup mechanism to solve every problem.
What really convinced me that this new setup is not just theoretically better was a deliberate worst-case recovery test.
By that point, I already had services like Listmonk, Homarr, and CyberChef running on k3s. So I decided to simulate disaster properly.
I effectively deleted all my k3s nodes.
Once the platform was back, I restored the Longhorn volumes from backup and recovered the database backup from MinIO. Then I brought the services back online and checked that the data was intact.
That let me test the absolute worst case: everything is gone, and I need to rebuild from scratch.
Seeing that:
was the point where I started to trust this setup for real.
You do not have to go this far to build a good self-hosting setup. I do this kind of testing because it is part of my professional mindset and because I genuinely enjoy understanding failure paths.
For most people, it is already a huge step forward to:
That is the real message here. You do not need to rebuild Kubernetes clusters from bare metal to improve your backup story. But you do need at least one serious restore test.
This incident was not only about backups. It was also about change control.
Part of the reason I am moving more of my infrastructure into k3s is that I also want a cleaner GitOps workflow with Argo CD.
That means:
In the old world, it was easier for subtle behavior changes to hide behind “I updated the stack a while ago”.
In the new world, I want far less ambiguity.
If something changes, I want to know when it changed, why it changed, and how to roll it back.
On the surface, this is a story about Traccar, TimescaleDB, and a retention policy.
For me, it is also a story about family memory, about trust in self-hosted systems, and about learning where “good enough” stops being good enough.
A small hidden auto-delete rule almost cost me four years of GPS history.
The only reason I still have that data is that some of my backup layers were good enough to save me this time, and that I noticed the problem before the damage went further.
Yes, the setup I am moving toward is more complex than a single Docker Compose stack with Syncthing and Borg.
But that complexity buys me something very concrete: it turns “I hope this works” into “I have tested the worst case and know how recovery behaves”.
You do not need to replicate my exact stack to benefit from this story.
A very good place to start is simply this:
If this all sounds a little overwhelming, that is okay. Self-hosting gets more complex the moment the data starts to matter.
The important part is not that everyone should run Kubernetes. The important part is recognizing that real backups are more than scheduled copies, and that the data you care about deserves a recovery strategy that has been tested before you need it.