Field notes from a hosting migration for an NHS food-diary proof of concept. A certificate checker was quietly lying about whether a fix had worked, and two sessions rejected the same hosting idea twice, four days apart, without either reading the other's reasoning. The move itself was still on hold when this was written. This is a proof-of-concept, not an NHS product, built by one technical person working alongside an NHS dietetic team. No patient data exists anywhere in it. The app stores everything on the client device and nothing else. The trigger for all of this was mundane: Netlify's free tier caps a project at 15 credits per production deploy, 300 a month, which works out to roughly twenty deploys before the meter runs dry. The project was already rationing pushes against that ceiling, and shared hosting on Namecheap, already being set up for another project the same person runs, looked like a way out. Two rejections, four days apart GitHub Pages came up early, and the project turned it down fast. On the free plan it only serves public repositories. A private repo needs a paid GitHub plan, and even then the site still publishes to a public URL with no login wall short of Enterprise. Netlify's free tier deploys from a private repo to an unlisted one instead, which fit the stated preference to keep the code itself private. No patient data was ever going to sit in that repo either way. The preference was about the source, not about anything a patient had typed. Four days later, GitHub Pages came up again. Not from a different person with a different view. A coding session working on the project proposed the exact idea an earlier session had already rejected, because nobody had reread the decisions file before speaking. The occasion was almost incidental: a different, newer domain had surfaced, already live on GitHub Pages with a working certificate, and for a moment it looked like an obvious destination. That session looked into it, then declined it again, for the same reason as the first time. That reason had not moved. It had simply never been reread. This round did turn up one genuinely new blocker: the food-diary repo and the newer domain lived under two different GitHub accounts, and GitHub blocks a custom domain from being claimed by a second account once it's verified on the first, specifically to stop that kind of takeover. A real finding, arrived at by accident, in the course of re-litigating a question that had already been settled. A decisions log nobody rereads is decoration. It was there, it had the right answer sitting in it, and a session still asked the question again anyway. A certificate checker that had been lying The SSL side had its own real bug and its own fake one. The real bug: the domain's nameservers were pointed at Namecheap's basic DNS, which only handles URL forwarding, not actual hosting. Switched to the proper hosting DNS and that half resolved. An earlier assumption, picked up from a web search during planning, claimed the hosting tier included self-service AutoSSL. It didn't. Triggering AutoSSL turned out to require WHM-level (the hosting provider's own backend) server-administrator access, which an ordinary shared-hosting account never gets. That was confirmed against Namecheap's own documentation once the free "Run AutoSSL" button turned out not to exist anywhere in the dashboard. The free certificate that does ship with the hosting plan has to be issued from Namecheap's own side, and nothing on the customer's end can force it. The fake bug was worse, because it looked like a real one for a while. This laptop's own antivirus intercepts TLS connections and reissues every certificate itself, so anything running on the machine, curl and openssl included, was reporting the antivirus's own certificate instead of the real one. The tool being used to answer "has the SSL fix landed yet" could not tell the truth about it even in principle. Checking from a phone worked around it, and later a tool that runs the request server-side, outside the interception entirely, confirmed it for good. Two support agents tried force-validating the certificate from Namecheap's own side with no result, and a follow-up ticket went in to track it as a pure backend wait. Nothing on the customer side could shortcut that part. The certificate issued not long after, and a server-side check confirmed clean HTTPS. Two things the plan hadn't accounted for First: SSH turned out to be off by default on shared hosting, though the migration plan had been written without confirming that. The fix was self-service, a control panel option called "Manage Shell," which gives no hint it's SSH, listening on a non-standard port rather than the usual one. Second: pushing to the new remote from an automated shell hung indefinitely with no error, because a global credential setting that keeps two separate GitHub accounts from colliding also blocks the plain per-repo push path unless a repo-level override is set. That global setting has to stay exactly as it is. The only new part is the missing per-repo override. A smaller trap surfaced once both hosts were finally level with GitHub again: the control panel's Git tool has an "Update" button that only saves configuration. It doesn't fetch, pull, or deploy anything, and those actions live on a separate tab entirely. For a repository that was deliberately cloned through the control panel's own "Clone a Repository" tool, with a specific option left unchecked to preserve its automatic deploy hook, even that tab's own "Update from Remote" option turns out to be a no-op. It's a push target, not a pull source. Reading the UI correctly took longer than reading the DNS panel had. Deleting a task is not a local edit One step in the migration plan retired Netlify outright once the new host was live. Once Namecheap turned out to be an interim stop rather than the final destination, the task was dropped, which seemed like a small, contained change. It wasn't. That same task had been referenced as a precondition in five other places across the plan, and one of those was dangerous on its own: a later step told a future session to delete the Netlify credit-cap constraint from the project's own guide file, once hosting had moved. Hosting hadn't moved. A session following that instruction literally would have silently removed a still-live limit from the record that exists to track it. All five references were found and corrected before that could happen. What actually changed the private-repo rule The preference that had driven both GitHub Pages rejections wasn't overturned by a technical argument. It was relaxed on purpose, once the user actually named a real gap instead of waving it off: the diary's free-text fields, a description typed next to a photo, have no structural way to stop someone entering something identifying. Going public meant accepting that gap rather than pretending it away. In the user's own words, it was "hard to guard... loose enough to say go public." The account-split blocker from the second rejection turned out not to matter here either, since the plan kept the repo under its existing account rather than moving it to the newer brand's. A bigger swap, investigated instead of just refused A smaller question, whether a branded subdomain could dodge a shared hosting username, led somewhere larger: making the newer, higher-traffic domain the account's designated "Main Domain," on the assumption that the label itself would grant it more subdomain headroom. That assumption didn't survive a check. The account's subdomain quota is shared regardless of which domain holds that label, so an ordinary addon domain gets exactly the same headroom. What an actual swap would require turned out to be much bigger than the label suggested: a different project's own blog would have to leave its free, automatic hosting for manual shared-hosting deploys with none of that automation and the same multi-day certificate process already documented here. That's not a call this project gets to make about another one. It was declined without touching anything, and the idea went out as two information-only notes to the two other projects' own owners, for them to decide on their own terms. How it actually landed The reversal, when it came, was decided outside any of these sessions, by the one person who owns all three projects at once: Netlify was retired entirely, and the food diary moved to GitHub Pages after all, kept under its existing account rather than a newer pseudonymous one. That keeps a tool built under a real identity separate from a brand built under a pseudonym, and it sidesteps the cross-account domain restriction outright rather than working around it. The account question that had paused the move turned out simpler than the plan in flight at the time suggested. Rather than standing up a new account, the existing one kept its history and its repos exactly where they were and simply changed its own display name. Same account throughout, just renamed partway through. The migration is complete now. Both old hosts are fully retired, and nothing is left dual-hosted. It ended without a clever fix. Two things went wrong here that weren't code. Both were caught the same way, by someone who actually checked instead of assuming. A certificate check that had every reason to look trustworthy was quietly answering a different question than the one being asked. And a written decision only held once someone actually reopened the file before speaking a second time. Originally published at thekilted.dev/nobody-reread.