by Pivec Labs
Relay Deployment Oracle Cloud Free Tier

Relay Deployment Guide (Oracle Cloud Free Tier)

Self-host a coturn relay on an Oracle Cloud Free Tier VPS for NetCrafter Relay.

There's no dedicated game server to deploy with NetCrafter Relay — HostActionRouter.cs compiles into the same client build as everyone else. But two small pieces of infrastructure do need to run somewhere with a real, public, non-CGNAT IP address: the coturn relay itself, and a small rendezvous service that resolves join codes to a host's relayed address (coturn only relays traffic — it doesn't do address-book lookups). This guide stands both up on the same Oracle Cloud Always Free tier compute instance — a public IP, indefinitely free, not a trial — since there's no reason to provision two servers for a small relay (2–4 players typical, up to 8–10).

Both are fully self-hosted by you. Pivec Labs does not run a shared coturn or rendezvous service for buyers to point at — every value in Relay Settings needs to come from a server you control.

Once you've created the instance (Section 01) and connected to it (Section 02), Section 03 installs and configures both pieces in one automated pass via install-relay-server.sh — this is the recommended path. Sections 04-09 walk through exactly the same steps by hand, kept as a reference for anyone who wants to understand or customize what the script does, or who'd rather not run it at all.

01

00 — Before you start#

You'll need:

  • An Oracle Cloud account (oracle.com/cloud/free) — identity verification requires a card, but Always Free resources are never charged.
  • An SSH key pair to log into the instance (ssh-keygen locally if you don't have one).
  • About 20 minutes.
02

01 — Create the compute instance#

01.1 — Start instance creation

  1. Go to Oracle Cloud Console → Compute → Instances → Create Instance
  2. Name it: netcrafter-relay

01.2 — Choose the image

  1. Under Image, click Change Image
  2. Select: Canonical Ubuntu 22.04 (not 20.04, not Minimal, not aarch64)
  3. Confirm it shows "Always Free eligible"

01.3 — Choose the shape

This step causes the most problems — read carefully.

Two Always Free shapes are available:

Option A — VM.Standard.A1.Flex (Ampere ARM)

  • Up to 4 OCPU / 24 GB RAM free
  • Recommended: 2 OCPU / 12 GB RAM
  • Better performance for coturn
  • Frequently shows "Out of capacity" error — see Troubleshooting below

Option B — VM.Standard.E2.1.Micro (AMD x86)

  • 1 OCPU / 1 GB RAM
  • Always available — no capacity issues
  • Sufficient for NetCrafter Relay (2–4 players typical, up to 8–10)
  • Recommended if A1 is unavailable

To find these shapes:

  1. Click Shape series → Specialty and previous generation
  2. Look for shapes marked Always Free-eligible
  3. Select VM.Standard.A1.Flex OR VM.Standard.E2.1.Micro

01.4 — Configure networking

Do not skip any step:

  1. Under Primary VNIC → Virtual cloud network, select Create new virtual cloud network
  2. Enter a name: netcrafter-vcn
  3. Under Subnet, select Create new public subnet
  4. Enter a name: public-subnet
  5. Enter CIDR block: 10.0.0.0/24
  6. Under Private IPv4 address, select Automatically assign private IPv4 address
  7. Under Public IPv4 address, select Automatically assign public IPv4 address

The Public IPv4 checkbox will be greyed out until you enter a valid CIDR block in step 5. Enter 10.0.0.0/24 and click elsewhere — the checkbox will become available.

01.4a — Create Internet Gateway and Route Table

This step is critical and easy to miss. Without an Internet Gateway, your instance will have a public IP but will be completely unreachable from the internet — SSH will time out even with correct security list rules.

After creating your VCN:

  1. Go to Networking → Virtual Cloud Networks → netcrafter-vcn
  2. In the left menu click Internet Gateways
  3. Click Create Internet Gateway
  4. Name: netcrafter-igw
  5. Compartment: your root compartment
  6. Click Create Internet Gateway

Then add the route rule:

  1. In the left menu click Route Tables
  2. Click Default Route Table for netcrafter-vcn
  3. Click Add Route Rules
  4. Fill in:
    • Target Type: Internet Gateway
    • Destination CIDR Block: 0.0.0.0/0
    • Target Internet Gateway: netcrafter-igw
  5. Click Add Route Rules

Without this route rule, outbound traffic from your instance cannot reach the internet and inbound connections will time out.

01.5 — Add SSH keys

  1. Under Add SSH keys, select Generate a key pair for me
  2. Click Download private key — saves as ssh-key-YYYY-MM-DD.key
  3. Click Download public key — saves as ssh-key-YYYY-MM-DD.key.pub
  4. Store both files safely: C:\Users\YourName\.ssh\

You cannot download the private key again after this step. If you lose it you cannot connect to the instance.

The public key field will be automatically populated after downloading.

Open required ports in the Security List

While you have the Security List open, add ingress rules for coturn:

Go to: Networking → Virtual Cloud Networks → netcrafter-vcn → public-subnet → Security Lists → Default Security List → Add Ingress Rules

Add these rules (one at a time):

Source CIDR Protocol Port Purpose
0.0.0.0/0 TCP 22 SSH
0.0.0.0/0 TCP 3478 TURN/STUN
0.0.0.0/0 UDP 3478 TURN/STUN
0.0.0.0/0 TCP 5349 TURN over TLS
0.0.0.0/0 UDP 5349 TURN over TLS
0.0.0.0/0 UDP 49152-65535 coturn relay ports

Port 22 may already exist by default. Check before adding to avoid duplicates.

01.6 — Boot volume

Leave all boot volume settings as default:

  • Do not check custom boot volume size
  • Do not check in-transit encryption
  • Do not attach block volumes

01.7 — Review and create

Confirm:

  • Image: Canonical Ubuntu 22.04
  • Shape: VM.Standard.A1.Flex or VM.Standard.E2.1.Micro
  • Public IPv4 address: Yes
  • SSH keys: present

Click Create.

Wait 1-2 minutes for status to change from Provisioning to Running.

Note the Public IP address shown on the instance details page.

Troubleshooting — Out of Capacity

If you see:

text
"Out of capacity for shape VM.Standard.A1.Flex in availability domain AD-1"

Try these in order:

  1. Change availability domain — try AD-2 or AD-3 instead of AD-1
  2. Remove fault domain — set to "Let Oracle choose the best fault domain"
  3. Switch to E2.1.Micro — under Shape, select Specialty and previous generation, then pick VM.Standard.E2.1.Micro. Always available and sufficient for NetCrafter Relay.
  4. Retry later — Oracle releases A1 capacity randomly. Try again in 30-60 minutes or the following morning.

Note: You cannot change region after starting instance creation. If your region is consistently full, start over and select a different region from the Oracle Cloud Console home page.

Troubleshooting — SSH Connection Timed Out

SSH times out even though port 22 is in the Security List. This is almost always caused by a missing Internet Gateway or route rule.

Check this in order:

  1. Go to Networking → Virtual Cloud Networks → netcrafter-vcn
  2. Click Internet Gateways in the left menu
    • If NONE exists → create one named netcrafter-igw (see Section 01.4a)
  3. Click Route Tables → Default Route Table
    • Confirm there is a rule with: Destination 0.0.0.0/0, Target Internet Gateway
    • If missing → Add Route Rules with these values

The Security List controls which ports are open. The Internet Gateway + Route Table controls whether any traffic can reach your instance at all. Both are required.

03

02 — Connect#

bash
ssh -i /path/to/your_private_key ubuntu@<PUBLIC_IP>
05

04 — Open the coturn ports (both layers)#

Sections 04-09 are the manual, step-by-step version of what install-relay-server.sh (Section 03) does in one automated pass. If you've already run the script, everything below is already done — read on only if you want to understand, verify, or customize any of it by hand.

Oracle Cloud has two firewall layers, and both must allow the traffic — this is the most common thing people miss. (The rendezvous service gets its own port later, in Section 09 — same two-layer gotcha applies there too.)

(a) Security List (cloud-level, stateless)

Console → Networking → Virtual Cloud Networks → your VCN → Security Lists → the default security list → Add Ingress Rules:

Source CIDR Protocol Destination Port Range Purpose
0.0.0.0/0 UDP 3478 STUN/TURN listener
0.0.0.0/0 TCP 3478 TURN over TCP (fallback)
0.0.0.0/0 UDP 49160-49200 Relay port range (must match min-port/max-port below)

A narrow relay port range (here, 41 ports) is enough for a handful of concurrent sessions (2–4 players typical, up to 8–10) and keeps the open-port surface small — widen it only if you expect many simultaneous sessions.

(b) The instance's own firewall

Oracle's Ubuntu images ship with iptables rules (via netfilter-persistent/oracle-cloud-agent) that block everything not explicitly allowed, on top of the Security List above. Open the same ports locally:

bash
sudo iptables -I INPUT -p udp --dport 3478 -j ACCEPT
sudo iptables -I INPUT -p tcp --dport 3478 -j ACCEPT
sudo iptables -I INPUT -p udp --dport 49160:49200 -j ACCEPT

sudo apt install -y iptables-persistent   # if not already installed
sudo netfilter-persistent save
06

05 — Install coturn#

bash
sudo apt update
sudo apt install -y coturn
07

06 — Configure turnserver.conf#

Edit /etc/turnserver.conf:

text
listening-port=3478
fingerprint
lt-cred-mech
realm=netcrafter.relay
user=netcrafter-relay:YOUR_SHARED_SECRET_HERE
min-port=49160
max-port=49200
external-ip=<PUBLIC_IP>/<PRIVATE_IP>
no-tls
no-dtls
log-file=/var/log/turnserver.log
simple-log
no-multicast-peers
  • user=netcrafter-relay:YOUR_SHARED_SECRET_HERE — this is the StaticSecret long-term credential: a single username/password pair every peer uses. Pick a real random secret, not a password you use elsewhere.
  • external-ip=<PUBLIC_IP>/<PRIVATE_IP> — Oracle instances have a private IP (find it with ip addr show, usually on ens3/eth0) behind a separately NAT'd public IP. This tells coturn to advertise the public IP in relayed-address attributes while binding to the private interface. Without this, peers receive an unreachable private IP and allocation succeeds but relaying silently fails.
  • no-tls/no-dtls — keeps the MVP setup simple (plain UDP relay, matching StunTurnClient.cs, which doesn't do DTLS). Fine for a small co-op relay (2–4 players typical, up to 8–10); add TLS later if you want encrypted TURN control traffic.
08

07 — Enable and start coturn#

bash
sudo nano /etc/default/coturn   # set TURNSERVER_ENABLED=1
sudo systemctl enable --now coturn
sudo systemctl status coturn    # confirm "active (running)"
sudo tail -f /var/log/turnserver.log   # watch for startup errors
09

08 — Test the relay server#

The quickest way to verify coturn is working is using a free browser-based TURN checker — no extra software needed (no Linux-only tools like turnutils_uclient required).

  1. Open this URL in your browser: webrtc.github.io trickle-ice sample
  2. Scroll down to the ICE servers section
  3. Remove any existing servers and add yours:
text
URI:      turn:YOUR_PUBLIC_IP:3478   (e.g. turn:92.5.181.120:3478)
Username: netcrafter-relay
Password: <your shared secret from turnserver.conf>
  1. Click Add Server
  2. Click Gather candidates
  3. Wait 10-15 seconds

✅ SUCCESS — you should see one or more candidates with type: relay appear in the list. This confirms coturn is correctly configured end to end.

FAILURE — if no relay candidates appear:

  • Check coturn is running: sudo systemctl status coturn
  • Check the log for errors: sudo tail -f /var/log/turnserver.log
  • Confirm ports 3478 TCP and UDP are open in both the Oracle Security List AND the instance iptables rules
  • Confirm external-ip in turnserver.conf has the correct public/private IP format: external-ip=PUBLIC_IP/PRIVATE_IP
10

09 — Install the rendezvous service#

Join codes need a small lookup service so a host's relayed address can be found by a joiner (and vice versa) — coturn only relays traffic, it doesn't do address-book lookups. This section installs a minimal one on the same instance, listening on its own port, so you don't need to provision a second server.

This is a small, hobby-project-grade service — no auth beyond knowing the join code, no HTTPS by default, no clustering. That matches the same security posture already accepted for the StaticSecret TURN credential above: fine for a small self-hosted relay (2–4 players typical, up to 8–10), not something to expose as shared production infrastructure for strangers.

09.1 — Open its port (both layers)

Pick a port not already used by coturn — this guide uses 8787.

(a) Security List:

Console → Networking → Virtual Cloud Networks → your VCN → Security Lists → the default security list → Add Ingress Rules:

Source CIDR Protocol Destination Port Range Purpose
0.0.0.0/0 TCP 8787 Rendezvous HTTP service

(b) The instance's own firewall:

bash
sudo iptables -I INPUT -p tcp --dport 8787 -j ACCEPT
sudo netfilter-persistent save

09.2 — Copy the rendezvous script onto the instance

This is the manual, by-hand version of what install-relay-server.sh (Section 03) does automatically. Skip to Section 03 if you just want it running.

The service is a single Python 3 file with no pip installs required — Ubuntu 22.04 ships Python 3 by default, and the script only uses the standard library (http.server, sqlite3).

It ships with NetCrafter Relay at:

text
Assets/PivecLabs/NetCrafterRelay/ServerTools/rendezvous_server.py
Assets/PivecLabs/NetCrafterRelay/ServerTools/netcrafter-relay-rendezvous.service

Copy both to the instance (from your local machine — adjust the source path to wherever your Unity project lives):

bash
scp -i /path/to/your_private_key \
  "Assets/PivecLabs/NetCrafterRelay/ServerTools/rendezvous_server.py" \
  "Assets/PivecLabs/NetCrafterRelay/ServerTools/netcrafter-relay-rendezvous.service" \
  ubuntu@<PUBLIC_IP>:~/

09.3 — Install and start it as a systemd service

bash
sudo mkdir -p /opt/netcrafter-relay
sudo mv ~/rendezvous_server.py /opt/netcrafter-relay/

sudo useradd --system --no-create-home --shell /usr/sbin/nologin netcrafter-relay || true
sudo mkdir -p /var/lib/netcrafter-relay-rendezvous
sudo chown -R netcrafter-relay:netcrafter-relay /var/lib/netcrafter-relay-rendezvous

sudo mv ~/netcrafter-relay-rendezvous.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now netcrafter-relay-rendezvous
sudo systemctl status netcrafter-relay-rendezvous   # confirm "active (running)"

If useradd reports "user already exists," that's fine — the || true just makes this step safe to re-run.

09.4 — Test it

bash
curl http://<PUBLIC_IP>:8787/

✅ SUCCESS — you should see {"status": "ok", "service": "netcrafter-relay-rendezvous"}.

FAILURE:

  • Check the service is running: sudo systemctl status netcrafter-relay-rendezvous
  • Check its logs: sudo journalctl -u netcrafter-relay-rendezvous -f
  • Confirm port 8787 TCP is open in both the Oracle Security List AND the instance's iptables rules (same two-layer gotcha as coturn — see Section 04)

Then confirm the actual join-code contract end to end (this is the same sequence NetCrafter Relay's generated code drives automatically — see RelayConnectionManager.tpl's HostSession()/JoinSession()/PollForPeers()):

bash
# Simulate a host registering:
curl -X POST http://<PUBLIC_IP>:8787/host -d '{"relayedAddress":"1.2.3.4:5000"}'
# -> {"joinCode": "AB3F9", "userId": 0}

# Simulate a joiner resolving that code (use the joinCode from above):
curl -X POST http://<PUBLIC_IP>:8787/join/AB3F9 -d '{"relayedAddress":"5.6.7.8:6000"}'
# -> {"relayedAddress": "1.2.3.4:5000"}

# Simulate the host polling for peers:
curl http://<PUBLIC_IP>:8787/host/AB3F9/peers
# -> {"peers": ["5.6.7.8:6000"]}

✅ SUCCESS — the join code from step 1 resolves to the host's address in step 2, and shows up in the peer list in step 3. This is exactly the flow a real host + joiner exercise automatically.

11

10 — Point NetCrafter Relay at both#

In the NetCrafter Relay editor window → Settings → Relay Settings:

Field Value
Relay Host <PUBLIC_IP> (or a DNS name pointed at it) — no scheme
Relay Port 3478
Rendezvous URL http://<PUBLIC_IP>:8787 — requires the http:// prefix
Credential Mode StaticSecret
TURN Username netcrafter-relay (must match user= in turnserver.conf)
Shared Secret the same secret from user=name:secret

Rendezvous URL and Relay Host sit right next to each other but expect different formats — Relay Host is a bare host with no scheme (TURN doesn't use one), while Rendezvous URL is a plain HTTP endpoint and needs the http:// (or https://) prefix. Entering <PUBLIC_IP>:8787 instead of http://<PUBLIC_IP>:8787 is auto-corrected at runtime, but it used to fail with a generic "Cannot connect to destination host" and no hint why — copy it exactly as shown above to avoid depending on that fallback.

Rendezvous URL has no working default — the field is required and ships blank. Leaving it blank fails the join-code flow at runtime with a clear error rather than a silent hang; there is no Pivec Labs-hosted fallback to fall back to.

Click Save Settings. The generated RelayConnectionManager reads these values live from this same RelaySettings asset at runtime (via its connectionSettings field, auto-wired by Prefab Generator) — there's no need to regenerate code or recreate the prefab just because a value here changed.

12

11 — Security notes#

  • The shared secret lives only in the RelaySettings.asset ScriptableObject (Definitions/{Name}/RelaySettings.asset) — it is read live via connectionSettings.sharedSecret, never baked into generated C# source. Treat it as a low-value credential appropriate for a hobby/small-studio relay regardless — not a production secret, never one reused from elsewhere — but it won't end up committed to source control just because you committed generated code.
  • Anyone who gets a copy of your RelaySettings.asset (or the RelayConnectionManager prefab referencing it) can see this secret and, in principle, allocate through your relay. For small co-op sessions (2–4 players typical, up to 8–10) the blast radius is small, but it's worth knowing. A production-grade setup would use RestApi credential mode — time-limited HMAC credentials minted by a rendezvous service — which is reserved for a future release and not yet implemented.
  • The rendezvous service has no authentication beyond knowing the join code — anyone who guesses or brute-forces a 5-character code within its ~15 minute lifetime could poll its peer list. At 2-8 players and short-lived codes, this is a low-value target, matching the same posture already accepted for the TURN shared secret above.
  • The rendezvous service's database is a single SQLite file at /var/lib/netcrafter-relay-rendezvous/rendezvous.db. It's designed to be disposable — only ever holds short-lived join codes — so there's no need to back it up.
  • Always Free has an outbound data transfer allowance (10TB/month at time of writing) — comfortably enough for casual co-op traffic; keep an eye on it if you expect heavy use.
  • Keep the instance patched: sudo apt update && sudo apt upgrade -y periodically.
13

12 — What this guide does not cover#

  • RestApi credential mode — time-limited HMAC-signed TURN credentials minted by a server-side service, instead of the single long-term StaticSecret used throughout this guide. Reserved for a future release; not yet implemented.
  • TLS/HTTPS for either coturn (control channel) or the rendezvous HTTP service — both run plain UDP/HTTP here, matching the MVP posture described above. Add TLS yourself if you need encrypted control traffic for your use case.
  • Scaling beyond a single instance — this guide is sized for a handful of concurrent sessions (2–4 players typical, up to 8–10) on one Always Free box, not a matchmaking service for many simultaneous games.
  • Provisioning the instance itself, or your cloud provider's network-level firewall — install-relay-server.sh (Section 03) automates everything that happens on the instance over SSH. Creating the instance (Section 01) and opening ports in your provider's own console (Oracle's Security List or equivalent) both happen outside SSH, so no script — this one included — can reach or automate them.