The Song Analyzer's backend runs on an Intel N100 with 8 GB of RAM, under WSL2, on a Windows machine. Every instinct said move it. It's the weakest box in the house, it's running Linux inside Windows inside a virtual switch, and the whole tool is only online when that PC is. A $4-a-month cloud VM would be faster, always up, and native Linux. It seemed less like a decision than an obvious upgrade.
So I went looking for the best way to do it, and found the reason not to.
Every IP address belongs to a published block owned by an organization, identified by an ASN. That's public registry data — anyone can look up an address and tell whether it belongs to a consumer ISP or to a hosting provider. And that single bit carries enormous signal, because of an asymmetry that has nothing to do with anyone's intentions:
A residential address probably has a household behind it. A datacenter address definitely has a machine behind it. Nobody's living room is in a rack. Legitimate human viewing essentially never originates from a datacenter, and bulk automated downloading almost always does — so "is this a hosting ASN?" is a cheap, accurate prior for "is this automation?"
There's a nastier second dimension: reputation is inherited. Cloud providers recycle addresses. The IP a fresh VM gets may have spent last month running someone else's scraper. You can be flagged before making a single request, which is why "but I only download a few songs a day" is no defense.
"The machine's greatest weakness — being a cheap PC in a house — was inseparable from its greatest strength: being in a house."
It isn't a binary block, which is what makes it confusing to diagnose. It's a ladder: an attestation challenge asking the client to prove it's a real browser; then Sign in to confirm you're not a bot; then HTTP 429; then a 403 on the media URL; then an outright block.
The rung that fools people sits in the middle, because it isn't an error at all. YouTube hands over a perfectly valid URL and then trickles the bytes — a few KB per second, fast enough to avoid a socket timeout, slow enough that a 4 MB file blows past the worker timeout. Rate limiting disguised as bad luck. The backend already carries a scar from this one: a throughput floor that abandons a throttled URL and re-extracts a fresh one rather than crawling toward a timeout.
And the IP is only the loudest signal, not the only one. Request pattern matters more than volume — a dozen downloads spread across an hour reads very differently from a dozen in ninety seconds. That's why the pacing gate exists, and why the fetch now waits a randomized moment before each request: perfectly regular timing is itself a tell, because no human produces it.
The obvious software fix is a proof-of-origin token — a provider that solves the browser attestation challenge on your behalf. It was the standard answer for years. It has also, as of recent updates, stopped bypassing the bot check in the majority of cases. Building a hosting plan on it would be building on something already sinking.
What genuinely works is routing the request through a residential proxy: an IP that belongs to a consumer ISP, so the request looks like it came from a house. Which is the moment the whole argument turns over. The cloud plan was: pay for a VM, lose the residential IP, then pay again — per gigabyte — to rent one back. All to replace a machine that already has one, for free, permanently.
So the analyzer stays home. Not as a budget compromise, but because for this specific workload, a residential IP is a capability, and it happens to be one you can't buy as cheaply as you can already own.
None of this would have surfaced if a git pull hadn't failed with Could not resolve host. Raw connectivity was fine — pinging a public resolver by IP came back in 36 ms. Only name resolution was dead, and only inside WSL2.
The first fix didn't take, for an instructive reason: /etc/resolv.conf was a symlink into systemd's stub resolver, so writing a nameserver into it wrote straight through the link into the file systemd manages, changing nothing about where queries actually went. Deleting the symlink first and putting a real file in its place was the difference between editing the configuration and editing a shadow of it.
Even then, public resolvers stayed unreachable — refused, not timed out, which is the signature of something actively rejecting DNS rather than a network that's simply down. A VPN, still holding the door shut after the tunnel was gone. Pointing the resolver at the Windows host instead — the machine that was resolving names perfectly well the whole time — fixed it in one line.
The part worth remembering isn't the fix. It's that the service reported itself healthy throughout. Cached songs replayed, uploads analyzed, the site looked fine. But yt-dlp needs DNS like anything else, so every YouTube download had been failing — a whole feature dark, behind a green status light, until an unrelated command happened to need the same broken thing.
Two things, neither of them about DNS or YouTube specifically.
The first is that "upgrade" is a claim about a specific workload, not a property of hardware. Faster CPU, better uptime, real Linux — every generic metric favored the cloud, and every one of them was beside the point, because the binding constraint was a property of the address, not the machine. Auditing what a workload actually depends on beats comparing spec sheets.
The second is that a health check that only proves the process is running proves very little. The status light was green while a core path was completely broken. Monitoring is only worth what it would have caught — which is why the backend now answers a real question about its own state, including whether the things that silently degrade it are actually configured.