What matters first is the delivery contract your app truly depends on, because PubNub hides a stack of decisions behind channels and SDKs: transport, ordering, replay, presence, access tokens, and global routing. Self-hosting forces those choices back into your architecture. Decide whether clients need raw WebSocket, MQTT, or a higher-level pub-sub protocol, and how much durability and replay you actually require. For browser and mobile fanout, brokers like NATS and RabbitMQ speak lightweight protocols, including MQTT and STOMP over WebSocket; for durable, replayable event streams, Kafka and Pulsar store an ordered log you can re-read.
The hard gaps are operational, not syntactic. PubNub's managed edge network, client SDK behavior, presence primitives, message history, and access manager are things teams often treat as background plumbing until they have to run them. Presence and occupancy are rarely portable as data, since they represent live connection state, and mobile push usually needs a separate service entirely. If your workload is device telemetry rather than app chat, an IoT platform such as ThingsBoard or Magistrala bundles device management, protocols, and rules you would otherwise assemble yourself.
Plan migration around an inventory of channels, payloads, access rules, and history settings. If message history was enabled, export it through PubNub's history APIs before cutover, because messages that were never stored cannot be recovered afterward. Most teams add a compatibility layer and dual-publish from the backend to PubNub and the new stack while clients move over gradually. Channel names and JSON payloads often survive; auth tokens, presence state, and retry behavior usually need rebuilding and test coverage.