Skip to content

Self-Hosting

Self-hosting is for users who want to control data paths, authentication policy, databases, private transports, and MCP/OAuth. The Gateway is a Rust service. HTTP APIs, WSS, and MCP/OAuth share one HTTP listener; QUIC and Raw TCP use separate listening addresses.

  • You do not want notification, event, or entity state to pass through a public Gateway.
  • You need your own database, backups, logs, monitoring, and capacity policy.
  • You want lower-latency Android private transport synchronization.
  • You want MCP/OAuth on your own domain.
  • You need a gateway-level Bearer token to restrict callers.

If you only want to try PushGo, use the public Gateway and follow Getting Started.

LevelBest forMain configuration
MinimalLocal testing, single-user scriptsSQLite + HTTP API
Production baseLong-running public domainHTTPS reverse proxy + persistent database + Bearer token
Private transportsAndroid low-latency syncWSS, then optional QUIC / Raw TCP
AI integrationMCP clients and AI assistantsMCP/OAuth + PUSHGO_PUBLIC_BASE_URL

The minimal setup only needs a database and HTTP listener.

Terminal window
mkdir -p /var/lib/pushgo
docker run -d --name pushgo-gateway \
-p 6666:6666 \
-e PUSHGO_HTTP_ADDR=0.0.0.0:6666 \
-e PUSHGO_DB_URL='sqlite:///var/lib/pushgo/pushgo.db?mode=rwc' \
-v /var/lib/pushgo:/var/lib/pushgo \
ghcr.io/aldenclark/pushgo-gateway:latest

Test it:

Terminal window
curl -X POST http://127.0.0.1:6666/message \
-H "Content-Type: application/json" \
-d '{
"channel_id": "YOUR_CHANNEL_ID",
"password": "YOUR_CHANNEL_PASSWORD",
"title": "Private Gateway test",
"body": "This message came from your own Gateway."
}'

The minimal setup is useful for validation. Do not expose it directly to the public internet.

For production, at least:

  1. Bind the Gateway to localhost or a private network.
  2. Put Nginx, Caddy, or a load balancer in front with HTTPS.
  3. Set PUSHGO_TOKEN for gateway-level Bearer authentication.
  4. Use persistent storage and include it in backups.
  5. Set PUSHGO_PUBLIC_BASE_URL and PUSHGO_TOKEN_SERVICE_URL explicitly.
Terminal window
docker run -d --name pushgo-gateway \
-p 127.0.0.1:6666:6666 \
-e PUSHGO_HTTP_ADDR=0.0.0.0:6666 \
-e PUSHGO_DB_URL='postgres://user:pass@db:5432/pushgo' \
-e PUSHGO_TOKEN='replace-with-gateway-token' \
-e PUSHGO_PUBLIC_BASE_URL='https://gateway.example.com' \
-e PUSHGO_TOKEN_SERVICE_URL='https://token.pushgo.dev' \
ghcr.io/aldenclark/pushgo-gateway:latest

After setting PUSHGO_TOKEN, API requests need:

Authorization: Bearer replace-with-gateway-token

The channel ID and channel password still belong in the request body. See Authentication for the difference between the two layers.

When PUSHGO_TOKEN is set, /healthz and /readyz also require the same Bearer token; MCP/OAuth discovery routes are the exception when MCP is enabled.

If the Gateway needs token-service, configure the region explicitly.

RegionGatewaytoken-service
Globalhttps://gateway.pushgo.dev/https://token.pushgo.dev/
Mainland Chinahttps://gateway.pushgo.cn/https://token.pushgo.cn/

A private Gateway may still use the public token-service, or switch to another service as your deployment evolves.

HTTP APIs, WSS, and MCP/OAuth share the HTTP listener. The reverse proxy must support normal HTTP and WebSocket upgrade.

server {
listen 443 ssl http2;
server_name gateway.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:6666;
}
}

PUSHGO_PUBLIC_BASE_URL must be the externally reachable HTTPS root. Otherwise MCP issuer metadata, bind links, and client profile hints may contain internal addresses.

Private transports are enabled with PUSHGO_PRIVATE_TRANSPORTS. Start with wss; it reuses HTTPS and is the least complex option.

Terminal window
PUSHGO_PRIVATE_TRANSPORTS=wss
PUSHGO_PUBLIC_BASE_URL=https://gateway.example.com

Add QUIC / Raw TCP when you need lower latency or operate in a controlled network.

Terminal window
PUSHGO_PRIVATE_TRANSPORTS=quic,tcp,wss
PUSHGO_PRIVATE_QUIC_BIND=0.0.0.0:5223
PUSHGO_PRIVATE_QUIC_PORT=5223
PUSHGO_PRIVATE_TCP_BIND=0.0.0.0:5223
PUSHGO_PRIVATE_TCP_PORT=5223
PUSHGO_PRIVATE_TLS_CERT=/certs/fullchain.pem
PUSHGO_PRIVATE_TLS_KEY=/certs/privkey.pem
SettingDescription
PUSHGO_PRIVATE_TRANSPORTSfalse, true, none, or explicit list such as wss or quic,tcp,wss.
PUSHGO_PRIVATE_QUIC_BINDLocal UDP address the Gateway listens on.
PUSHGO_PRIVATE_QUIC_PORTQUIC port advertised to clients.
PUSHGO_PRIVATE_TCP_BINDLocal TCP address the Gateway listens on.
PUSHGO_PRIVATE_TCP_PORTRaw TCP port advertised to clients.
PUSHGO_PRIVATE_TLS_CERT / PUSHGO_PRIVATE_TLS_KEYRequired for QUIC; also required for Raw TCP unless TLS is offloaded.
PUSHGO_PRIVATE_TCP_TLS_OFFLOADWhether edge infrastructure handles Raw TCP TLS.
PUSHGO_PRIVATE_TCP_PROXY_PROTOCOLWhether the Raw TCP entrypoint expects PROXY protocol v1.

PushGo QUIC uses a custom ALPN (pushgo-quic) and cannot simply share the same UDP/443 entrypoint with HTTP/3. Use a separate UDP port or confirm that your edge proxy can route by protocol correctly.

Enable MCP with:

Terminal window
PUSHGO_MCP_ENABLED=true
PUSHGO_PUBLIC_BASE_URL=https://gateway.example.com

Common settings:

Environment variableDefaultDescription
PUSHGO_MCP_DCR_ENABLEDtrueEnables Dynamic Client Registration.
PUSHGO_MCP_PREDEFINED_CLIENTSnonePredefined OAuth clients in client_id:client_secret format; separate multiple clients by newline or semicolon.

See MCP Reference for tools and authorization flow.

CLI / env varDefaultDescription
--http-addr / PUSHGO_HTTP_ADDR127.0.0.1:6666HTTP API, WSS, and MCP/OAuth listener.
--db-url / PUSHGO_DB_URLrequiredDatabase URL; supports SQLite, PostgreSQL, and MySQL.
--runtime-profile / PUSHGO_RUNTIME_PROFILEsmallRuntime sizing profile: small for private/light deployments, public for high-load deployments.
--token / PUSHGO_TOKENnoneGateway-level Bearer token. Empty means disabled.
--token-service-url / PUSHGO_TOKEN_SERVICE_URLhttps://token.pushgo.devtoken-service URL. Set explicitly in production.
--public-base-url / PUSHGO_PUBLIC_BASE_URLnoneExternal HTTPS root URL.
--sandbox-mode / PUSHGO_SANDBOX_MODEfalseSandbox mode, including APNs sandbox endpoint.
--observability-profile / PUSHGO_OBSERVABILITY_PROFILEprod_minObservability profile: prod_min, ops, incident, debug.
--observability-log-level / PUSHGO_OBSERVABILITY_LOG_LEVELwarnNative tracing log level.
  • Include the database in backups; channels, devices, MCP grants, and entity state depend on persistent storage.
  • SQLite is suitable for personal or light deployments; prefer PostgreSQL for multi-user or high-concurrency use.
  • For high load, check the active PUSHGO_RUNTIME_PROFILE, Gateway logs, and downstream provider or database health before tuning infrastructure.
  • Use PUSHGO_OBSERVABILITY_PROFILE=ops for production troubleshooting; temporarily raise to incident or debug for deeper investigation.
  • For Android private transport issues, start with /gateway/profile and externally reachable ports.

Runtime capacity:

Runtime capacity is profile-owned in Gateway v1.2.9. Use PUSHGO_RUNTIME_PROFILE=small for private or light deployments and PUSHGO_RUNTIME_PROFILE=public for high-load public deployments. Low-level queue, dispatch, provider, DB-pool, and SQLite tuning values are internal profile defaults instead of public env vars.

  • Back up the database and runtime configuration before upgrading.
  • Keep Gateway image/binary, environment variables, and reverse-proxy config traceable.
  • Validate /message, /event/create, and /thing/create against a test channel.
  • If private transports are enabled, verify that Android clients can fetch the updated /gateway/profile.
  • If MCP is enabled, verify /.well-known/*, /oauth/*, and /mcp still use the external HTTPS address.