Self-Hosting Expo Updates: OTA Without the SaaS
One of the quiet superpowers of a React Native app built with Expo is over-the-air updates. You can push a JavaScript bundle change to users without going through the app store review cycle, which means a typo fix or a small bug can reach people in minutes instead of days. For Eşlik Et, my events companion app, that delivery path is genuinely critical. So I made a decision that surprises some people: instead of relying on a hosted updates service, I run my own Expo Updates server, a Next.js service sitting behind a subdomain.
This is not the default advice, and for good reason. But for this app it was the right call, and the reasons are worth laying out honestly, including the parts that argue against it.
How OTA updates actually work
At a high level, over-the-air updates rely on a separation between the native shell of your app and the JavaScript bundle it runs. The native binary, the thing in the app store, changes rarely. The JavaScript bundle, where most of your product logic lives, can change often. OTA updates let you swap that bundle without shipping a new binary.
The mechanism is a manifest. When the app launches, it asks an updates server whether there is a newer bundle than the one it currently has. The server responds with an update manifest describing the available update, and if there is one, the app downloads the new bundle and runs it on the next launch. The manifest is the contract between your server and every installed copy of your app. Whoever controls that endpoint controls what code your users run.
Why I chose to self-host
That last sentence is exactly why I wanted to own it. A few reasons drove the decision.
The first is control over rollout. I want to decide precisely who gets an update and when, and I want the freedom to design that logic myself rather than work within someone else's dashboard. The second is cost. As an app grows, hosted update services bill on usage, and a path I own runs on infrastructure I already pay for. The third is data residency. The updates endpoint sees information about my users' app versions and devices, and there is value in keeping that within my own boundary. The fourth, and most important, is dependency risk. The updates server is a critical delivery path. If it goes down, I cannot ship fixes. I was not comfortable putting that single point of failure entirely in a third party's hands.
Signing and manifests are the part you cannot get wrong
The most security-sensitive piece of self-hosting OTA is signing. Because the update mechanism delivers executable code to your users, the app needs to verify that an update genuinely came from you and was not tampered with in transit or served by an impostor. Expo supports signing update manifests with a private key, and the app verifies them against a public key baked into the build.
When you self-host, you own that key material. That is power and burden in equal measure. Lose control of the signing key and someone could serve malicious code to your entire user base. Lose the key entirely and you may struggle to ship trusted updates. So the signing keys get treated with the same seriousness as any production secret: stored carefully, never committed, and handled as if the safety of every install depends on them, because it does.
Staged rollouts and the operational reality
Owning the server means I can implement staged rollouts on my own terms, releasing a new bundle to a slice of users first, watching for crashes or error spikes, and widening the rollout only once it looks healthy. That ability to contain a bad update is, for me, one of the strongest arguments for owning the path.
But I want to be honest about the cost, because this is the part people gloss over. Self-hosting OTA means you now run a piece of critical infrastructure. You own its uptime, its scaling under launch-day traffic, its monitoring, its key rotation, and its security patches. If that endpoint is slow or down, every app launch feels it. You have traded a vendor's reliability engineering for your own, and that work never really ends. It is a standing operational commitment, not a one-time setup.
When hosted is the smarter choice
So I will say the unglamorous thing: for most teams, the hosted updates service is the right answer, and I would recommend it without hesitation. If your update delivery is not so critical that an occasional dependency on a vendor is unacceptable, if you do not have specific data residency requirements, and if you would rather spend your time on product than on running infrastructure, hosted wins easily. You get signing, rollouts, and reliability handled by people whose full-time job is exactly that.
I self-host for Eşlik Et because the combination of control, cost at scale, and dependency risk tipped the balance for this particular app, and because I was willing to take on the operational weight that comes with it. That is the real decision. Not whether self-hosting is clever, but whether you genuinely want to own a critical delivery path and everything that comes with keeping it alive. For most people, most of the time, the honest answer is no, and there is no shame in that.