Production Deployment
This guide covers running UrsaMU on a Linux VPS or dedicated server. The
examples use Ubuntu/Debian and nginx, but the approach works on any Linux
distribution.
Before You Start
You need:
- Deno — deno.land v1.40+
- A domain name pointed at your server’s IP (for TLS)
- Ports
4201,4203reachable from the internet (or whatever you configure) - A strong value ready for
JWT_SECRET
Install Deno if you haven’t:
curl -fsSL https://deno.land/install.sh | sh
# Add to PATH — follow the printed instructions, then:
source ~/.bashrc
Environment Variables
Set these before starting the server. The simplest approach is a .env file in
your game project root (UrsaMU loads it via @std/dotenv at startup):
# .env — DO NOT commit this file
JWT_SECRET=replace-this-with-a-long-random-string
Generate a strong secret:
openssl rand -base64 48
Why this matters: If
JWT_SECRETis not set, a random secret is
generated at startup — meaning every restart logs all players out. Set it
once and keep it stable.
You can also override the default ports via environment variables:
URSAMU_HTTP_PORT=4203 # HTTP + WebSocket hub (default: 4203)
URSAMU_TELNET_PORT=4201 # Telnet sidecar (default: 4201)
Or set ports in your game’s config/config.json:
{
"server": {
"http": 4203,
"telnet": 4201,
"ws": 4202
}
}
First Run
On a fresh server with an empty database, start the server interactively once
to complete first-run setup:
deno task server
The server prints:
Fresh database detected — no players exist yet.
Connect via telnet and run:
create <name> <password>
The first player created is automatically given superuser access.
Connect with a telnet client (telnet localhost 4201) and create your account.
The first player created receives the superuser flag automatically.
After that, switch to daemon mode for normal operation.
Daemon Mode
UrsaMU includes built-in daemon scripts that start both processes in the
background, write logs to logs/, and track PIDs.
# Start in background
deno task daemon
# Check status
deno task status
# Follow live logs
deno task logs
# Stop
deno task stop
# Stop and restart
deno task restart
What deno task daemon does:
- Starts
src/telnet.tsin the background → logs tologs/telnet.log - Starts
src/main.tsin the background → logs tologs/main.log - Writes PIDs to
.ursamu.pid
The PIDs file is cleaned up automatically on deno task stop.
Development vs production: Use
deno task startduring development — it
enables file watching so both servers restart on code changes. Use
deno task daemonin production where you want the servers to stay up
without reloading on filesystem events.
systemd
For tighter OS integration (automatic restart on crash, start on boot), run
UrsaMU as a systemd service instead of using the daemon scripts.
Create /etc/systemd/system/ursamu-main.service:
[Unit]
Description=UrsaMU Main Server
After=network.target
Wants=ursamu-telnet.service
[Service]
Type=simple
User=ursamu
WorkingDirectory=/home/ursamu/my-game
EnvironmentFile=/home/ursamu/my-game/.env
ExecStart=/home/ursamu/.deno/bin/deno run --allow-all --unstable-detect-cjs --unstable-kv src/main.ts
Restart=on-failure
RestartSec=5
StandardOutput=append:/home/ursamu/my-game/logs/main.log
StandardError=append:/home/ursamu/my-game/logs/main.log
[Install]
WantedBy=multi-user.target
Create /etc/systemd/system/ursamu-telnet.service:
[Unit]
Description=UrsaMU Telnet Sidecar
After=network.target
[Service]
Type=simple
User=ursamu
WorkingDirectory=/home/ursamu/my-game
EnvironmentFile=/home/ursamu/my-game/.env
ExecStart=/home/ursamu/.deno/bin/deno run --allow-all --unstable-detect-cjs --unstable-kv src/telnet.ts
Restart=on-failure
RestartSec=5
StandardOutput=append:/home/ursamu/my-game/logs/telnet.log
StandardError=append:/home/ursamu/my-game/logs/telnet.log
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable ursamu-main ursamu-telnet
sudo systemctl start ursamu-main ursamu-telnet
sudo systemctl status ursamu-main ursamu-telnet
Adjust User, WorkingDirectory, and EnvironmentFile paths to match your
setup. Run UrsaMU as a dedicated non-root user — create one with:
sudo useradd -m -s /bin/bash ursamu
Nginx and TLS
Place nginx in front of the Hub to terminate TLS. This lets your game use
wss:// (secure WebSocket) and https:// without modifying UrsaMU itself.
Install nginx and Certbot
sudo apt install nginx certbot python3-certbot-nginx
sudo certbot --nginx -d yourgame.example.com
nginx configuration
Create /etc/nginx/sites-available/ursamu:
server {
listen 443 ssl http2;
server_name yourgame.example.com;
ssl_certificate /etc/letsencrypt/live/yourgame.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourgame.example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# HTTP REST API
location /api/ {
proxy_pass http://127.0.0.1:4203;
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;
}
# Health check
location /health {
proxy_pass http://127.0.0.1:4203;
}
# WebSocket
location /ws {
proxy_pass http://127.0.0.1:4203;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
# Redirect HTTP → HTTPS
server {
listen 80;
server_name yourgame.example.com;
return 301 https://$host$request_uri;
}
Enable and reload:
sudo ln -s /etc/nginx/sites-available/ursamu /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
WebSocket connection URL with TLS
Clients connecting through the nginx proxy use:
wss://yourgame.example.com/ws?token=<jwt>&client=web
The proxy forwards the WebSocket upgrade to port 4203. The X-Forwarded-For
header is passed through, so brute-force rate limiting still sees the real
client IP.
Telnet and TLS
Standard Telnet (telnet:4201) does not support TLS. For encrypted Telnet
connections, clients should use a TLS-capable MU* client (e.g. Mudlet with SSL
enabled) pointed directly at port 4201, with nginx/stunnel wrapping that port.
Most game operators leave Telnet unencrypted and direct players to use the
WebSocket connection for sensitive operations.
Firewall
Open only the ports you need. With nginx handling TLS:
# UFW example
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP (nginx, for redirect + Certbot)
sudo ufw allow 443/tcp # HTTPS (nginx)
sudo ufw allow 4201/tcp # Telnet (direct)
sudo ufw enable
Port 4203 should not be exposed directly — all HTTP/WS traffic should
flow through nginx on 443. Only Telnet needs a public port of its own.
Logs
UrsaMU writes three log files to logs/:
| File | Contents |
|---|---|
logs/main.log |
Hub stdout/stderr — server events, errors |
logs/telnet.log |
Telnet sidecar stdout/stderr |
logs/error.log |
Unhandled errors logged by logError() |
logs/security.log |
Auth events — login failures, resets, admin actions |
Follow live:
deno task logs # tails main.log and telnet.log
tail -f logs/security.log
Log rotation
Prevent unbounded growth with logrotate. Create
/etc/logrotate.d/ursamu:
/home/ursamu/my-game/logs/*.log {
daily
rotate 14
compress
missingok
notifempty
copytruncate
}
copytruncate truncates the file in place rather than moving it, so the
running server keeps writing without needing a restart.
Updates
UrsaMU is a JSR package — your game project depends on a version pinned in
deno.json. To update to the latest engine:
# Preview what would change
deno run -A jsr:@ursamu/ursamu/cli update --dry-run
# Apply the update
deno run -A jsr:@ursamu/ursamu/cli update
After updating, restart the servers:
deno task restart # if using daemon mode
sudo systemctl restart ursamu-main ursamu-telnet # if using systemd
Check the changelog before
updating on a live game to catch any breaking changes.