High availability
The open-source Mercure hub is a serious piece of software. A single instance comfortably handles tens of thousands of concurrent connections on modest hardware (benchmark: 40k concurrent on a t3.micro). For most production workloads, one node is enough.
What one node can't give you is redundancy. If the box goes down, every subscriber reconnects to nothing. If the disk fails, the BoltDB history is gone. If you want to survive that, you need more than one node. And more than one node needs a transport that synchronizes between them.
This page covers your options.
What the open-source build gives you
| Capability | Open-source |
|---|---|
| Concurrent connections | Unlimited (hardware-bound) |
| Publish rate | Unlimited |
| History buffer | Unlimited (disk-bound) |
| Number of nodes | 1 |
| Transports | BoltDB, local |
| TLS, HTTP/2, HTTP/3 | Yes |
| Authorization | Full JWT support |
| Metrics, profiling | Full Prometheus + pprof |
| Subscription events | Yes |
The "1 node" line is the only ceiling. Everything else is unbounded by the license; only by what your hardware and network can deliver.
When one node isn't enough
Three reasons people graduate to multi-node:
Redundancy. A single replica is a single point of failure. For real-time SLOs (sub-second delivery, no perceptible reconnect), you need more than one replica.
Throughput beyond a single host. A box can usually push as much as its NIC allows, but multi-host gives you horizontal scale for fan-out: 1M-subscriber broadcasts split across nodes.
Geo-distribution. Multi-region deployments need a transport that crosses regions cheaply.
Connection counts alone rarely justify multi-node. A single hub at 100k concurrent connections is normal.
The two paths beyond single-node
Mercure Cloud (managed)
A hub provisioned on the Mercure.rocks Cloud. High-availability infrastructure, TLS, custom domains, SRE on call. You don't run anything.
| Tier | €/month | Connections | History |
|---|---|---|---|
| Free | 0 | 25 | None |
| Hobby | 35 | 1,000 | 100 messages |
| Pro | 120 | 5,000 | 500 messages |
| Business | 450 | 20,000 | 5,000 messages |
The buffer caps exist because managed hubs need predictable storage. If you need more history per topic, run Self-Hosted instead.
The protocol is identical. Migrate later by changing one URL.
Self-hosted Mercure (multi-node, on your infrastructure)
A licensed build of the same hub with multi-node transports added. You run it on your servers (bare metal, your own Kubernetes, your own clouds). Data never leaves your infrastructure. Useful for GDPR data residency, HIPAA, and internal compliance.
| Tier | €/year | Connections | Nodes | History | Support |
|---|---|---|---|---|---|
| Open Source | 0 | Unlimited | 1 | Unlimited | Community |
| Startup | 1,500 | 1,000 | 2 | Unlimited | |
| Business | 5,000 | 10,000 | 3 | Unlimited | Priority next-day |
| Corporate | 12,000 | Unlimited | Unlimited | Unlimited | Priority + SLA |
| Elite | Custom | Unlimited | Unlimited | Unlimited | 24/7 + SLA |
A separate Managed On-Premise add-on (€5,000/year) covers remote setup, monitoring, and managed updates if you want the binaries on your infra without running them yourself.
To purchase, email contact@mercure.rocks.
Self-hosted transports
Redis / Valkey
The default for low-latency multi-node. Good fit when the hub is one of several services and the data is volatile.
# Redis / Valkey mercure { transport redis { url rediss://default:p@ssw0rd@redis.example.com:6379 stream mercure } # ... }
| Feature | Supported |
|---|---|
| History | ✅ |
| Subscription API | ✅ |
| Custom event ID | ✅ |
Options:
| Option | Description |
|---|---|
url | Redis connection URI (spec). |
stream | Redis stream name. Default: mercure. |
max_length | Approximate maximum stream size. 0 for unlimited. |
gob | Use Go gob instead of JSON. Faster, but can't be read by other clients. |
addresses | Multiple Redis nodes (cluster). |
username, password, tls | Authentication and transport security. |
Reuse an existing Caddy storage Redis (when you also use caddy-storage-redis) by passing address caddy-storage-redis.alt.
PostgreSQL
The Postgres transport uses LISTEN/NOTIFY for pub/sub and SQL tables for history. Right when you want events queryable from the rest of your data.
# PostgreSQL mercure { transport postgres { url postgres://user:password@db.example.com/mercure } }
| Feature | Supported |
|---|---|
| History | ✅ |
| Subscription API | ❌ (planned) |
| Custom event ID | ✅ |
The Postgres transport doubles as an event store. You can join Mercure events with your application data in a single query, which is useful for audit, analytics, and replays.
Apache Kafka
Use Kafka when it's already in your stack and you want Mercure to ride on it. Otherwise, prefer Redis or Postgres.
# Apache Kafka mercure { transport kafka { addresses host1:9092 host2:9092 topic mercure consumer_group hub-pod-3 } }
| Option | Description |
|---|---|
addresses | Broker addresses. |
topic | Kafka topic. All hub instances must share the same topic. |
consumer_group | Consumer group. Must be unique per hub instance. |
user, password, tls | SASL credentials. |
| Feature | Supported |
|---|---|
| History | ✅ |
| Subscription API | ❌ |
| Custom event ID | ✅ |
Apache Pulsar
# Apache Pulsar mercure { transport pulsar { url pulsar://pulsar.example.com:6650 topic mercure subscription_name hub-pod-3 } }
| Feature | Supported |
|---|---|
| History | ✅ |
| Subscription API | ❌ |
| Custom event ID | ❌ (planned) |
Picking a Mercure self-hosted transport
| Need | Transport |
|---|---|
| Lowest latency, simplest setup | Redis / Valkey |
| Queryable history alongside app data | PostgreSQL |
| Already running Kafka | Kafka |
| Already running Pulsar | Pulsar |
| Single node, no extra infra | BoltDB (open-source) |
When in doubt, Redis. It's the recommended default for Self-Hosted.
Custom Mercure transports
The transport interface is small and public. If none of the above fits, write your own. See transport.go and build a custom hub with xcaddy.
License keys
Self-Hosted is gated by a license key passed via MERCURE_LICENSE. The check runs in-process; the hub doesn't call back to a license server.
# License keys MERCURE_LICENSE=<key> \ MERCURE_PUBLISHER_JWT_KEY=... \ MERCURE_SUBSCRIBER_JWT_KEY=... \ ./mercure run
The license enforces node count and connection caps. Going over the cap doesn't crash the hub; it returns 429 Too Many Requests to publishers and refuses new subscribers.
Mercure migration paths
| From | To | What changes |
|---|---|---|
| Open-source single node | Cloud | Change the hub URL on clients. JWT keys move to the dashboard. |
| Open-source single node | Self-Hosted | Same binary structure, with a license and a multi-node transport. Subscribe and publish APIs are byte-for-byte the same. |
| Cloud | Self-Hosted | Migrate the hub URL and the keys. Keep the same JWTs. |
There is no protocol fork: every tier speaks the same Mercure protocol. Code written against the open-source hub runs on Cloud and Self-Hosted unchanged.
Mercure vs. Pusher and ably: pricing comparison
For people coming from SaaS-only real-time platforms, here's the rough picture:
| Feature | Mercure Pro (€120) | Pusher Business ($499) | Ably Pro ($399+) |
|---|---|---|---|
| Concurrent connections | 5,000 | 2,000 | 5,000 |
| History buffer | 500 messages | Limited | 2 minutes default |
| Messages | Unlimited | Daily cap | Usage-based billing |
| Self-hostable? | Yes | No | No |
Mercure is the only one of these you can run on your own infrastructure if you need to. That's by design.
Mercure support channels
Self-Hosted / Cloud: contact@mercure.rocks
Open-source: GitHub Discussions, Stack Overflow
mercuretag,#mercureon the Symfony Slack
Next steps for Mercure high availability
Rolling updates: graceful drain in any deployment.
Health monitoring: knowing the hub is healthy.
Load testing: figure out what the hardware can do before users do.