
Every blog owner eventually asks the same question: does anybody actually read this thing? The honest answer on the modern web is uncomfortable — most of your “traffic” is robots. So I did what any sensible sysadmin does instead of installing yet another JavaScript tracker: I wrote a little Python script, pointed it at my own nginx-ingress logs, and counted the humans by hand. Well, by regex. 🐍
Here’s what one week on www.apt-upgrade.me actually looks like once you strip the machines out.
📉 The funnel of disappointment (and relief)
| Total HTTP requests | 22,666 |
| IPs that behaved like a browser | 119 |
| …of those: cloud / datacenter / scrapers 🤖 | 45 |
| Actual regular visitors 🧑💻 | 74 |
≈ 16 humans/day, or roughly ~110/week. Out of twenty-two thousand requests. Let that sink in. The internet is, statistically, a server room talking to itself. 🕳️
🕵️ Why raw “unique IPs” is a lie
If you just sort | uniq -c your access log, you’ll report a beautiful, fat, completely fictional number. The overwhelming majority of hits are crawlers and scanners — and a huge share of them send a perfectly ordinary Mozilla/5.0 … Chrome/… User-Agent while doing it. A naïve “count the browser-looking UAs” approach on this dataset reported 959 visitors. The real number is an order of magnitude smaller.
So the script runs three filters, each one narrower than the last:
| # | Filter | What it catches |
|---|---|---|
| 1️⃣ | User-Agent blocklist | The bots that politely admit they’re bots (bot, crawl, spider, python, curl, zgrab, masscan…). |
| 2️⃣ | Asset correlation 💪 | A real browser fetches the CSS, JS and images the page references. A scraper grabs the HTML and leaves. “Did you also load ≥3 assets?” is by far the strongest single signal. |
| 3️⃣ | Cloud netblocks + rDNS | Whatever survives step 2 is mostly headless Chrome on Tencent Cloud, OVH, Alibaba & friends — a “browser” that geolocates to wherever the VM happens to sit. |
🌍 Where the humans came from
After the machines are filtered out, here’s the geographic breakdown of the 74 survivors (country from a local GeoLite2 database — no data leaves the server, more on that below):
| Country | Visitors | Share | |
|---|---|---|---|
| 🇺🇸 United States | 13 | 17.6% | █████████ |
| 🇩🇪 Germany | 10 | 13.5% | ███████ |
| 🇨🇳 China | 8 | 10.8% | ██████ |
| 🇬🇧 United Kingdom | 6 | 8.1% | ████ |
| 🇨🇦 Canada | 5 | 6.8% | ████ |
| 🇳🇱 Netherlands | 4 | 5.4% | ███ |
| 🇮🇹 Italy | 3 | 4.1% | ██ |
| 🇧🇷 Brazil | 2 | 2.7% | █ |
| 🇨🇿 Czechia | 2 | 2.7% | █ |
| 🇫🇷 France | 2 | 2.7% | █ |
| 🇩🇰 Denmark | 2 | 2.7% | █ |
| 🌐 17 more countries (1 each) | 17 | 23.0% | 🇲🇽🇰🇿🇱🇧🇦🇺🇺🇦🇱🇹🇯🇵🇰🇼🇶🇦🇷🇴🇮🇱🇸🇪🇳🇿🇮🇳🇦🇷🇭🇺 |
| Total | 74 | 100% |
A genuinely global little audience for a nerdy German-Linux-and-security blog. Hello, whoever you are in Kazakhstan and Kuwait — I see exactly one of you, and I appreciate you. 👋
🔧 How the script works (and where to steal it)
It’s about 330 lines of dependency-light Python. The pipeline in a nutshell:
parse nginx-ingress logs (CRI-wrapped combined format) → drop private IPs, non-GET, self-declared bots → split each IP's hits into "HTML pages" vs "assets" → keep only IPs with a real page + ≥3 assets # the human test → subtract cloud netblocks & datacenter rDNS → geolocate locally, mask to /24, aggregate, print
The whole thing is open-source — no secrets, just the analysis logic — in my infrastructure repo:
👉 github.com/aptupgrademe/www_k3s (scripts/analytics/visitor-stats.py). Clone it, point it at your own ingress logs, and find out how lonely your corner of the web really is. 😄
🛡️ No tracker, no cookie banner, no data leaving the box
Here’s the part I’m quietly proud of: there is no tracking on this blog at all. No Google Analytics, no beacon, no third-party pixel, no “we value your privacy” popup. This is pure server-log analysis — the same lines nginx writes anyway. And because visitor IPs are personal data under the GDPR, the script is deliberately built so that:
- 🔒 Nothing ever leaves the server — geolocation uses a local GeoLite2 database, not some third-party API.
- 🎭 Output is masked to /24 (or /48 for IPv6) — enough to see “a visitor from Italy”, never enough to fingerprint a person.
- 🧹 It runs on-demand and keeps no profile — no cookies means it literally cannot tell a returning reader from a new one. And that’s fine.
⚠️ Honesty section (because numbers lie)
- An IP is not a person. Mobile users rotate addresses, households share one, CGNAT hides hundreds behind one. These counts are an order of magnitude, not a headcount.
- “Per week” is extrapolated. k3s rotates container logs at 10 MB and keeps very few, so in practice only ~4.7 days sit on disk. The weekly figure is those days scaled up — take it with a grain of salt. 🧂
- My own traffic is excluded. Editing a post touches more pages in one evening than a month of real readers, so my home prefix is filtered out. Otherwise I’d be the blog’s biggest fan.
💡 Why bother? (the money question)
Let me be completely transparent: I make exactly €0 from this blog. No ads, no affiliate links, no “buy me a coffee”, no course funnel. Nothing. It costs me time and a server bill, and it earns me nothing but the occasional visitor from Qatar. 🪙❌
And yet — even with a lot of hours poured in, and a lot of AI assistance along the way — it’s genuinely fun. Every one of these little projects (hardening the stack, chasing scanner bots, building this visitor counter) is an excuse to get my hands dirty with Linux and security, and I learn something concrete every single time. That’s the whole payoff, and honestly it’s plenty. If a handful of those 74 humans learned something too, that’s a bonus. 🐧❤️
There’s one more way I’ve come to see this whole thing. In German there’s a wonderful word — Ehrenamt — for unpaid civic work you take on out of a sense of duty and genuine joy: the honorary role, done for the community rather than for a paycheck. It has a long and proud tradition. And with my own skills and interests, I feel I’ve found my personal Ehrenamt for the third millennium right here — a definition of volunteering that genuinely fits me, in an age of digitalisation and IT, expressed in the medium I actually know and love: Linux, servers and security. 🐧
Concretely, that’s the part that matters to me:
- 📢 Ad-free, always. No banners, no trackers, no sponsored posts, nothing to sell. What you read is simply what I wanted to write.
- 🎁 No advantage sought. There’s nothing in it for me commercially — it’s given freely, and that’s rather the whole point.
- 🎓 Knowledge transfer. Everything I figure out — hardening a stack, catching scanner bots, measuring TTFB — gets written down so the next person can skip the painful part.
- 🌍 No borders. A blog reaches anyone with a browser, from Germany to Kazakhstan to Kuwait (hi again 👋). Reach that once stopped at the edge of town is now worldwide, for free.
- 🧑💻 Open source, end to end. The whole thing — Ansible playbooks, Kubernetes manifests, this very visitor script — lives in the open. Take it, learn from it, run it yourself.
And here I can be more precise than “open source”. The repository ships under the MIT licence, which the Free Software Foundation recognises as a proper Free Software licence (permissive and GPL-compatible). So this isn’t merely source code you’re allowed to look at — it’s software you are genuinely free to run, study, share and modify, for any purpose, no strings attached. In the fullest sense of the word, that feels like exactly the right way to give something back in 2026. 🌐❤️
📦 Full source, infra-as-code and the visitor script: github.com/aptupgrademe/www_k3s — no trackers were harmed in the making of this post.




