Set Up External Signal Server

This guide is part of the Splitting Your Self-Hosted Deployment guide. It covers extracting the Signal server to a dedicated machine.

In most deployments, the embedded Signal server works well and does not need to be extracted. Consider running an external Signal server if you want to separate it from the Management server for organizational or infrastructure reasons.

For active-active HA across multiple Signal instances, openZro ships a distributed dispatcher that fans out signaling messages between Signal nodes via Redis or NATS — no commercial license required. The single-instance setup on this page is the simpler topology that fits most operators; graduate to the dispatched mode when the in-memory connection state of one Signal node becomes a scaling ceiling.

Server Requirements

  • A Linux VM with at least 1 CPU and 1GB RAM
  • Public IP address
  • A domain name pointing to the server (e.g., signal.example.com)
  • Docker installed
  • Firewall ports open: 80/tcp (Let's Encrypt HTTP challenge) and 443/tcp (gRPC/WebSocket client communication)

Create Signal Configuration

On your signal server, create a directory and configuration:

mkdir -p ~/openzro-signal
cd ~/openzro-signal

Like the relay, the signal server can automatically obtain TLS certificates via Let's Encrypt.

Create signal.env with your signal settings:

NB_PORT=443
NB_LOG_LEVEL=info

# TLS via Let's Encrypt (automatic certificate provisioning)
NB_LETSENCRYPT_DOMAIN=signal.example.com

Create docker-compose.yml:

services:
  signal:
    image: openzro/signal:latest
    container_name: openzro-signal
    restart: unless-stopped
    ports:
      - '443:443'
      - '80:80'
    env_file:
      - signal.env
    volumes:
      - signal_data:/var/lib/openzro
    logging:
      driver: "json-file"
      options:
        max-size: "500m"
        max-file: "2"

volumes:
  signal_data:

Alternative: TLS with Existing Certificates

If you have existing TLS certificates, replace the Let's Encrypt variable in signal.env with:

# Replace the NB_LETSENCRYPT_DOMAIN line with:
NB_CERT_FILE=/certs/fullchain.pem
NB_CERT_KEY=/certs/privkey.pem

And add a certificate volume to docker-compose.yml:

    volumes:
      - /path/to/certs:/certs:ro
      - signal_data:/var/lib/openzro

Start the Signal Server

docker compose up -d

Verify it's running:

docker compose logs -f

If you configured Let's Encrypt, trigger certificate provisioning with an HTTPS request:

curl -v https://signal.example.com/

Confirm the certificate was issued:

* Server certificate:
*  subject: CN=signal.example.com
*  issuer: C=US; O=Let's Encrypt; CN=E8
*  SSL certificate verify ok.

Update Main Server Configuration

On your main server, add signalUri to config.yaml. This disables the embedded Signal server:

server:
  # ... existing settings ...

  # External signal server
  signalUri: "https://signal.example.com:443"

Restart the main server:

docker compose down
docker compose up -d

Verify Signal Extraction

Check the main server logs to confirm the embedded Signal is disabled:

docker compose logs openzro-server
INFO combined/cmd/root.go:   Management: true (log level: info)
INFO combined/cmd/root.go:   Signal: false (log level: )
INFO combined/cmd/root.go:   Relay: false (log level: )