[{"data":1,"prerenderedAt":192},["ShallowReactive",2],{"seo-verification":3,"blog-self-host-centrifugo-vps-real-time-server-en":6},{"google":4,"bing":5},"EycwPY2XMyTkVzas3n1ygeNJFGAH513qrMjfDljzsMQ","",{"key":7,"data":8},"blog-self-host-centrifugo-vps-real-time-server-en",{"id":9,"slug":10,"slugs":11,"title":15,"excerpt":16,"readTime":17,"views":18,"isPinned":19,"noindex":20,"publishedAt":21,"updatedAt":22,"category":23,"categories":28,"featuredImage":30,"bgImage":31,"posterImage":32,"relatedSolution":33,"intro":36,"sections":37,"ctaTitle":120,"ctaBody":121,"ctaButton":122,"ctaUrl":123,"relatedPosts":124},569,"self-host-centrifugo-vps-real-time-server",{"fr":12,"en":10,"ar":13,"es":14},"self-host-centrifugo-vps","استضافة-centrifugo-على-vps-خادم-زمن-حقيقي","autoalojar-centrifugo-vps-servidor-tiempo-real","Centrifugo on a VPS: Self-Hosted Real-Time Server","Install Centrifugo on a VPS: an open-source real-time server to push WebSocket messages from your backend, with JWT tokens and a publish API.",7,0,false,true,"2026-11-07T00:00:00+00:00","2026-11-08T01:13:02+00:00",{"id":17,"name":24,"slug":25,"color":26,"icon":27},"Self-hosting","self-hosting","bg-indigo-500\u002F10 text-indigo-400","cloud",[29],{"id":17,"name":24,"slug":25,"color":26,"icon":27},null,"\u002Fblog\u002Fcovers\u002Fbg.svg","\u002Fblog\u002Fcovers\u002Fself-host-centrifugo-vps-poster.svg",{"categorySlug":34,"appSlug":35},"development","centrifugo","Showing a notification the moment an order is paid, running a chat, updating a dashboard without reloading the page: all of this needs a connection kept open between the server and the browser. Services such as Pusher or Ably do it very well, but they bill every connection and every message, and your data goes through them. Centrifugo is an open-source real-time messaging server (Apache-2.0 license), written in Go, that does the same job on your own VPS. This guide installs it with Docker Compose, puts it behind nginx over HTTPS, then shows how to publish from your backend and connect a browser with a JWT.",[38,42,52,79,82,95,99,102,105,108,111,114,117],{"type":39,"title":40,"body":41},"h2","How Centrifugo works","Centrifugo sits between your application and its users. Your backend — Laravel, Django, Express, Rails or anything else — keeps no connection open: it signs a token for each user, then publishes messages with a plain HTTP call. Centrifugo maintains the WebSocket connections of browsers and mobile apps, and delivers each message to the subscribers of the relevant channel.\n\nThis split has a concrete benefit: your application stays stateless and easy to deploy, and the real-time part fits in a single Go binary that uses a few tens of MB of RAM at rest.",{"type":43,"title":44,"items":45},"ul","What Centrifugo brings",[46,47,48,49,50,51],"**WebSocket connections** — with official client SDKs for JavaScript, Dart\u002FFlutter, Swift, Java\u002FAndroid, Go and Python.","**Publish API** — an HTTP POST with the `X-API-Key` header, from any language.","**JWT tokens** — your backend decides who connects and to which channels, Centrifugo checks the signature.","**Server-side subscriptions** — a user's channel list travels inside their token.","**Admin console** — node status, connected clients and API commands from the browser.","**Careful defaults** — no anonymous connection, no publishing from the browser without explicit permission.",{"type":53,"title":54,"headers":55,"rows":58},"comparison","Centrifugo versus a hosted service",[5,56,57],"Centrifugo on a VPS","Pusher, Ably",[59,63,67,71,75],[60,61,62],"Billing","VPS price, no message limit","By connections and messages",[64,65,66],"Where data goes","On your server","Through the provider",[68,69,70],"Client protocol","WebSocket and official SDKs","WebSocket and proprietary SDKs",[72,73,74],"Authentication","JWT signed by your backend","Service-specific signature",[76,77,78],"Operations","Your responsibility","Handled by the provider",{"type":39,"title":80,"body":81},"Prerequisites","An Ubuntu 24.04 VPS with 1 GB of RAM and 10 GB of disk, Docker and the Compose plugin installed, and a subdomain pointing to the VPS, for example `rt.example.com`. HTTPS is not optional: a site served over HTTPS can only open `wss:\u002F\u002F` connections, and your API key must never travel in clear text.",{"type":83,"title":84,"steps":85},"steps","Install Centrifugo with Docker Compose",[86,89,92],{"title":87,"body":88},"Generate the secrets","Create the application directory and a `.env` file holding four distinct secrets: the console password, the key that signs its sessions, the server API key and the HMAC key for client tokens.\n```bash\nmkdir -p \u002Fopt\u002Fcentrifugo && cd \u002Fopt\u002Fcentrifugo\n{\n  echo \"ADMIN_PASSWORD=$(openssl rand -hex 12)\"\n  echo \"SECRET_KEY=$(openssl rand -hex 24)\"\n  echo \"ENCRYPTION_KEY=$(openssl rand -hex 16)\"\n  echo \"ENCRYPTION_KEY_HEX64=$(openssl rand -hex 32)\"\n} > .env\nchmod 600 .env\n```",{"title":90,"body":91},"Write the docker-compose.yml file","The port is published on the loopback interface only: only nginx reaches it. Replace `https:\u002F\u002Fapp.example.com` with your site's address.\n```yaml\nservices:\n  centrifugo:\n    image: centrifugo\u002Fcentrifugo:v6.10.2\n    restart: unless-stopped\n    command: [\"centrifugo\"]\n    ports:\n      - \"127.0.0.1:8000:8000\"\n    environment:\n      CENTRIFUGO_ADMIN_ENABLED: \"true\"\n      CENTRIFUGO_ADMIN_PASSWORD: \"${ADMIN_PASSWORD}\"\n      CENTRIFUGO_ADMIN_SECRET: \"${SECRET_KEY}\"\n      CENTRIFUGO_HTTP_API_KEY: \"${ENCRYPTION_KEY}\"\n      CENTRIFUGO_CLIENT_TOKEN_HMAC_SECRET_KEY: \"${ENCRYPTION_KEY_HEX64}\"\n      CENTRIFUGO_CLIENT_ALLOWED_ORIGINS: \"https:\u002F\u002Fapp.example.com\"\n      CENTRIFUGO_HEALTH_ENABLED: \"true\"\n    ulimits:\n      nofile:\n        soft: 65535\n        hard: 65535\n```",{"title":93,"body":94},"Start and check","Start the container and query the health endpoint; it should answer `{}`.\n```bash\ndocker compose up -d\ncurl -fsS http:\u002F\u002F127.0.0.1:8000\u002Fhealth\n```",{"type":96,"title":97,"body":98},"tip","Never publish port 8000 on the public IP","The same port serves client connections, the server API and the admin console. Keep it on `127.0.0.1` and always go through the HTTPS reverse proxy. And never put `ENCRYPTION_KEY` or `ENCRYPTION_KEY_HEX64` in code sent to the browser: the first lets anyone publish to any channel, the second lets anyone forge tokens for any user.",{"type":39,"title":100,"body":101},"Put Centrifugo behind nginx","A WebSocket connection starts with an HTTP request asking to switch protocols. nginx must forward that request, and must not cut a connection that stays quiet for a few minutes. In the `server` block of your subdomain, with a Let's Encrypt certificate:\n```nginx\nlocation \u002F {\n    proxy_pass http:\u002F\u002F127.0.0.1:8000;\n    proxy_http_version 1.1;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection \"upgrade\";\n    proxy_set_header Host $host;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_read_timeout 3600s;\n}\n```\nAfter `nginx -t` and a reload, `https:\u002F\u002Frt.example.com` shows the console login screen.",{"type":39,"title":103,"body":104},"Publish from your backend","Publishing means sending a JSON object to a channel. Any language that can make an HTTP request will do:\n```bash\ncurl -X POST https:\u002F\u002Frt.example.com\u002Fapi\u002Fpublish \\\n  -H \"X-API-Key: $ENCRYPTION_KEY\" \\\n  -d '{\"channel\": \"commandes\", \"data\": {\"id\": 1042, \"statut\": \"payee\"}}'\n```\nThe `{\"result\":{}}` response confirms the message was accepted. Without the `X-API-Key` header, or with a wrong key, Centrifugo answers `401`.",{"type":39,"title":106,"body":107},"Sign connection tokens","A browser only connects with a JWT signed by your backend with `ENCRYPTION_KEY_HEX64`. The `sub` claim carries the user ID, `exp` the expiry date, and `channels` the list of channels the user is subscribed to automatically. Example in Python:\n```python\nimport time, jwt  # pip install pyjwt\n\ndef centrifugo_token(user_id: str) -> str:\n    claims = {\"sub\": user_id, \"exp\": int(time.time()) + 3600, \"channels\": [\"commandes\"]}\n    return jwt.encode(claims, ENCRYPTION_KEY_HEX64, algorithm=\"HS256\")\n```\nExpose this function behind an authenticated route of your application: only a user logged in to your app should be able to get a token.",{"type":39,"title":109,"body":110},"What a client is allowed to do","The settings of this installation are deliberately closed. A browser can neither subscribe to a channel on its own nor publish to it: it only receives the channels listed in the `channels` claim of its token. Your backend decides, each time it issues a token, who listens to what — a customer only sees their own orders, a support agent only their team's queue.\n\nFor a chat, publishing therefore goes through your backend as well: the browser sends the message to your application, which validates it, stores it in the database, then publishes it through the API. This detour costs a few milliseconds and keeps you in control of moderation and history.\n\nFinally, give tokens a short lifetime, one hour for example. Instead of passing a fixed token, let the SDK ask your application for a new one when it expires:\n```javascript\nconst client = new Centrifuge('wss:\u002F\u002Frt.example.com\u002Fconnection\u002Fwebsocket', {\n  getToken: async () => {\n    const res = await fetch('\u002Frealtime\u002Ftoken', { credentials: 'include' });\n    return (await res.json()).token;\n  },\n});\n```\nThe `\u002Frealtime\u002Ftoken` route belongs to your application; it checks the user's session before signing anything.",{"type":39,"title":112,"body":113},"Connect a browser","On the client side, the official `centrifuge` SDK (npm) opens the connection and delivers the messages:\n```javascript\nimport { Centrifuge } from 'centrifuge';\n\nconst client = new Centrifuge('wss:\u002F\u002Frt.example.com\u002Fconnection\u002Fwebsocket', {\n  token: tokenFromYourBackend,\n});\nclient.on('publication', (ctx) => console.log(ctx.channel, ctx.data));\nclient.connect();\n```\nIf the connection fails, check the logs first (`docker compose logs centrifugo`). The message “request Origin is not authorized” means your site's origin is missing from `CENTRIFUGO_CLIENT_ALLOWED_ORIGINS`; “invalid token” points to a badly signed or expired token.",{"type":39,"title":115,"body":116},"The admin console","Open `https:\u002F\u002Frt.example.com` and enter `ADMIN_PASSWORD`: the console does not ask for a username. It shows the node version, the number of connected clients and users, and lets you send API commands — publish a test message, list active channels — without writing code. It is the fastest way to check that a message goes out before looking for the fault in the browser.",{"type":39,"title":118,"body":119},"Scaling and updates","This guide uses the memory engine: a single node, with no dependency. It suits most applications, and a modest VPS already holds a large number of simultaneous connections, hence the raised open-file limit in `docker-compose.yml`. The day one node is no longer enough, Centrifugo can spread the load across several nodes linked by Redis. To update, change the image tag after reading the release notes, then run `docker compose pull && docker compose up -d`; connections come back on their own, as the client SDKs reconnect automatically.","Your real-time server on your VPS","A ServOrbit Ubuntu 24.04 VPS with 1 GB of RAM is enough to start Centrifugo. One-click install from the Marketplace, HTTPS and WebSocket on your domain or on the VPS's free subdomain, password and keys generated for you.","Install Centrifugo","\u002Fapplications\u002Fdevelopment\u002Fcentrifugo",[125,150,174],{"id":126,"slug":127,"slugs":128,"title":132,"excerpt":133,"readTime":134,"views":135,"isPinned":19,"noindex":19,"publishedAt":136,"updatedAt":137,"category":138,"categories":144,"featuredImage":30,"bgImage":31,"posterImage":146,"relatedSolution":147},194,"self-host-ntfy-on-a-vps-push-notifications-for-scripts-and-cicd",{"fr":129,"en":127,"ar":130,"es":131},"self-host-ntfy-vps","استضافة-ntfy-على-vps-إشعارات-فورية-من-سكريبتاتك-وcicd","autoalojar-ntfy-en-un-vps","Self-host ntfy on a VPS: push notifications for scripts and CI\u002FCD","Deploy ntfy on your VPS and send push notifications from any script, cron job or CI\u002FCD pipeline with a plain curl — no SaaS, no API key, no subscription.",16,2,"2026-07-28T00:00:00+00:00","2026-11-04T16:30:26+00:00",{"id":139,"name":140,"slug":141,"color":142,"icon":143},8,"Security & Monitoring","securite-monitoring","bg-rose-500\u002F10 text-rose-400","security",[145],{"id":139,"name":140,"slug":141,"color":142,"icon":143},"\u002Fblog\u002Fcovers\u002Fself-host-ntfy-vps-poster.svg",{"categorySlug":148,"appSlug":149},"monitoring-observability","ntfy",{"id":151,"slug":152,"slugs":153,"title":157,"excerpt":158,"readTime":159,"views":18,"isPinned":19,"noindex":19,"publishedAt":160,"updatedAt":161,"category":162,"categories":168,"featuredImage":30,"bgImage":31,"posterImage":170,"relatedSolution":171},58,"hosting-redis-on-a-vps",{"fr":154,"en":152,"ar":155,"es":156},"heberger-redis-vps","استضافة-redis-على-خادم-vps","alojar-redis-en-un-vps","Hosting Redis on a VPS: the complete guide","Deploy Redis on your Cloud VPS: RAM\u002FCPU requirements, security (ACL, TLS), RDB\u002FAOF persistence, troubleshooting, and high availability.",10,"2026-04-23T00:00:00+00:00","2026-09-08T21:59:59+00:00",{"id":163,"name":164,"slug":165,"color":166,"icon":167},6,"Databases","bases-de-donnees","bg-teal-500\u002F10 text-teal-400","database",[169],{"id":163,"name":164,"slug":165,"color":166,"icon":167},"\u002Fblog\u002Fcovers\u002Fheberger-redis-vps-poster.svg",{"categorySlug":172,"appSlug":173},"databases","redis-stack",{"id":175,"slug":176,"slugs":177,"title":181,"excerpt":182,"readTime":183,"views":18,"isPinned":19,"noindex":19,"publishedAt":184,"updatedAt":185,"category":186,"categories":187,"featuredImage":30,"bgImage":31,"posterImage":189,"relatedSolution":190},60,"hosting-supabase-on-a-vps",{"fr":178,"en":176,"ar":179,"es":180},"heberger-supabase-vps","استضافة-supabase-على-خادم-vps","alojar-supabase-en-un-vps","Hosting Supabase on a VPS in 2026","Self-host Supabase on your Cloud VPS: Postgres, Auth, Storage and REST API behind Envoy. PG 15→17 migration, Kong→Envoy, GoTrue CVE.",15,"2026-04-21T00:00:00+00:00","2026-11-08T01:13:14+00:00",{"id":163,"name":164,"slug":165,"color":166,"icon":167},[188],{"id":163,"name":164,"slug":165,"color":166,"icon":167},"\u002Fblog\u002Fcovers\u002Fheberger-supabase-vps-poster.svg",{"categorySlug":172,"appSlug":191},"supabase",1794101168613]