| name | resonate-server-deployment |
| description | Deploy and configure the Resonate server on Linux systems with systemd. Covers installation, public URL configuration, JWT authentication, and troubleshooting. |
| license | Apache-2.0 |
Resonate Server Deployment
Overview
Deploy the Resonate server on Linux systems using systemd for process management. This skill covers installation, configuration of public URLs, JWT authentication setup, and common troubleshooting scenarios.
Deploying on GCP instead? See resonate-server-deployment-cloud-run for the Cloud Run + Cloud SQL variant.
Prerequisites
- Linux system with systemd
- Root/sudo access
- curl installed
- (Optional) openssl for generating JWT keys
- (Optional) jwt-cli for generating tokens
Quick Start
Basic Deployment (No Auth)
curl -L -o deploy-resonate.sh https://example.com/deploy-resonate.sh
chmod +x deploy-resonate.sh
sudo ./deploy-resonate.sh
Deployment with Public URL and JWT Auth
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem
sudo RESONATE_SERVER__URL=https://resonate.example.com \
RESONATE_AUTH__PUBLICKEY=/path/to/public_key.pem \
./deploy-resonate.sh
Configuration Options
| Environment Variable | Default | Description |
|---|
RESONATE_VERSION | v0.9.8 | Server version the install script downloads |
RESONATE_SERVER__PORT | 8001 | HTTP API port |
RESONATE_SERVER__URL | (none) | Public URL for the server (e.g., https://resonate.example.com) |
RESONATE_AUTH__PUBLICKEY | (none) | Path to JWT public key file; setting it enables JWT auth (there is no separate enable flag) |
Server Flags Reference
The Resonate server binary accepts these flags:
resonate serve [flags]
Flags:
--server-url string Public URL for the server (included in response headers)
--server-port int HTTP API port (default 8001)
--auth-publickey string Path to JWT public key for authentication
--storage-type string Storage backend: sqlite or postgres (default sqlite)
--storage-postgres-url string PostgreSQL connection URL
All flags are also settable via RESONATE_-prefixed environment variables with __ for nesting, e.g. RESONATE_SERVER__URL, RESONATE_AUTH__PUBLICKEY, RESONATE_STORAGE__POSTGRES__URL.
Architecture
Internet
│
▼
┌────────────────┐
│ Nginx/Caddy │ (SSL termination)
│ Port 443 │
└────────────────┘
│
▼
┌────────────────┐
│ Resonate Server│ (systemd service)
│ Port 8001 │
└────────────────┘
│
┌────────┴────────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ Worker 1 │ │ Worker 2 │
└──────────┘ └──────────┘
Systemd Service Configuration
Basic Service File
[Unit]
Description=Resonate Server
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/var/lib/resonate
ExecStart=/usr/local/bin/resonate serve
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=resonate
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Service File with Public URL and Auth
[Unit]
Description=Resonate Server
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/var/lib/resonate
ExecStart=/usr/local/bin/resonate serve \
--server-url https://resonate.example.com \
--auth-publickey /etc/resonate/public_key.pem
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=resonate
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
JWT Authentication Setup
1. Generate RSA Key Pair
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem
2. Install JWT CLI (for generating tokens)
brew install mike-engel/jwt-cli/jwt-cli
curl -L -o jwt https://github.com/mike-engel/jwt-cli/releases/latest/download/jwt-linux
chmod +x jwt
sudo mv jwt /usr/local/bin/
3. Generate Client Tokens
IMPORTANT: An empty payload {} will DENY all access. You must include a prefix claim.
jwt encode --secret @private_key.pem -A RS256 '{"prefix":""}'
jwt encode --secret @private_key.pem -A RS256 '{"role":"admin"}'
jwt encode --secret @private_key.pem -A RS256 '{"prefix":"my-app"}'
jwt encode --secret @private_key.pem -A RS256 --exp='+30 days' '{"prefix":""}'
Prefix claim behavior:
| Payload | Access |
|---|
{} | DENIED (no prefix claim = unauthorized) |
{"prefix": ""} | ALL promises (empty string = unrestricted) |
{"prefix": "my-app"} | Only promises starting with my-app |
{"role": "admin"} | ALL promises (admin role) |
4. Configure Server
scp public_key.pem root@server:/etc/resonate/
sudo RESONATE_AUTH__PUBLICKEY=/etc/resonate/public_key.pem \
./deploy-resonate.sh --update-service
5. Configure Clients
Pass the server URL and a JWT token to the client. TypeScript shown — every SDK takes a URL + token the same way; see the per-SDK skill (resonate-basic-ephemeral-world-usage-{typescript,python,rust,go}) for client init in your language. The RESONATE_TOKEN env var and the Authorization: Bearer <jwt> header are identical across all SDKs.
const resonate = new Resonate({
url: "https://resonate.example.com",
token: process.env.RESONATE_TOKEN
});
const headers = {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
};
Why API URL Matters
The --server-url flag tells the Resonate server its public address. This is critical when:
- Workers poll for tasks: The server returns URLs that workers use for callbacks
- Clients connect from different networks: The server advertises its address
- Behind a reverse proxy: Server needs to know the external URL, not localhost
Without --server-url:
- Server returns
http://localhost:8001 in responses
- External workers can't reach callback URLs
- Polling may fail with connection errors
With --server-url:
- Server returns
https://resonate.example.com in responses
- Workers and clients use the correct public URL
Directory Structure
/usr/local/bin/
└── resonate # Server binary
/etc/resonate/
├── public_key.pem # JWT public key (if auth enabled)
└── resonate.yml # Optional config file
/var/lib/resonate/
└── resonate.db # SQLite database (default)
/etc/systemd/system/
└── resonate.service # Systemd service file
Common Operations
View Logs
journalctl -u resonate -f
journalctl -u resonate -n 100
journalctl -u resonate -b
journalctl -u resonate --since "2024-01-28 10:00:00"
Service Management
systemctl status resonate
systemctl start resonate
systemctl stop resonate
systemctl restart resonate
systemctl enable resonate
systemctl disable resonate
systemctl daemon-reload
Check Configuration
systemctl cat resonate
ps aux | grep resonate
curl http://localhost:8001/promises
Troubleshooting
401 Unauthorized Errors
Symptoms:
- Clients get 401 errors
- "missing authorization header" in logs
- Workers can't poll for tasks
Causes & Fixes:
-
Auth enabled on server but client has no token — add a token to the client (TypeScript shown; every SDK takes a token the same way — see the per-SDK skills)
const resonate = new Resonate({
url: "https://resonate.example.com",
token: process.env.RESONATE_TOKEN
});
-
Token is expired
jwt encode --secret @private_key.pem -A RS256 --exp='+30 days' '{"prefix":""}'
-
Wrong public key on server
openssl rsa -in private_key.pem -pubout | diff - public_key.pem
-
Token signed with wrong private key
jwt decode $TOKEN
Server Not Responding
Symptoms:
curl http://localhost:8001 hangs or refuses connection
- Service shows as active but port not open
Causes & Fixes:
-
Service crashed on startup
journalctl -u resonate -n 50
-
Port already in use
netstat -tlnp | grep 8001
-
Firewall blocking port
ufw status
Workers Can't Connect
Symptoms:
- Workers start but never receive tasks
- "connection refused" errors
Causes & Fixes:
-
Wrong RESONATE_URL in worker
RESONATE_URL=https://resonate.example.com
-
Missing --server-url on server
-
SSL/TLS issues
curl -v https://resonate.example.com/promises
Database Issues
Symptoms:
- "database is locked" errors
- Data not persisting across restarts
Causes & Fixes:
-
Multiple processes accessing SQLite
ps aux | grep resonate
-
Wrong working directory
find / -name "resonate.db" 2>/dev/null
-
Disk full
df -h
Production Checklist
Reverse Proxy Configuration
Nginx
server {
listen 443 ssl;
server_name resonate.example.com;
ssl_certificate /etc/letsencrypt/live/resonate.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/resonate.example.com/privkey.pem;
location / {
proxy_pass http://localhost:8001;
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;
# For long-polling
proxy_read_timeout 120s;
proxy_send_timeout 120s;
}
}
Caddy
resonate.example.com {
reverse_proxy localhost:8001
}
Summary
Key deployment steps:
- Install binary from GitHub releases
- Create systemd service with appropriate flags
- Configure
--server-url for public accessibility
- Enable JWT auth with
--auth-publickey if needed
- Set up reverse proxy with SSL
- Configure clients with correct URL and token
Critical flags:
--server-url: Server's public URL (required for external access)
--auth-publickey: JWT public key path (required for auth)