Skip to main content

CCTV OME for Sites

Guide for deploying OvenMediaEngine at remote sites to stream CCTV feeds to the central admin platform.


Overview

Each site needs:

  1. A Linux box running Docker + OvenMediaEngine
  2. Network access to local cameras (RTSP)
  3. A way for admin.codexdiz.com to reach the OME instance

Two deployment scenarios:

  • Scenario A: Site has a fixed public WAN IP (easy)
  • Scenario B: Site has dynamic IP or behind CGNAT (needs tunnel)

Part 1: Converting Windows PC to Linux

Requirements

  • Old desktop PC (4GB+ RAM, any CPU from last 10 years)
  • USB drive (8GB+) for installer
  • Ethernet connection to local network

Step 1: Download Ubuntu Server

https://ubuntu.com/download/server

Download the LTS version (e.g., Ubuntu 24.04 LTS).

Step 2: Create Bootable USB

On Windows, use Rufus:

  1. Download Rufus: https://rufus.ie/
  2. Insert USB drive
  3. Select the Ubuntu ISO
  4. Click Start
  5. Wait for completion

Step 3: Install Ubuntu Server

  1. Insert USB into target PC
  2. Boot from USB (usually F12, F2, or DEL at startup for boot menu)
  3. Select "Install Ubuntu Server"
  4. Follow prompts:
    • Language: English
    • Keyboard: Your layout
    • Network: Use DHCP (configure static later)
    • Storage: Use entire disk (erase Windows)
    • Profile: Create user (e.g., siteadmin)
    • SSH: Enable OpenSSH server (important!)
    • Snaps: Skip all
  5. Reboot when complete, remove USB

Step 4: Initial Setup

SSH into the new box from another computer:

ssh siteadmin@<local-ip>

Update system:

sudo apt update && sudo apt upgrade -y

Set static IP (optional but recommended):

sudo nano /etc/netplan/00-installer-config.yaml

Example config:

network:
version: 2
ethernets:
enp0s3: # Your interface name
dhcp4: no
addresses:
- 192.168.1.100/24 # Static IP for this box
routes:
- to: default
via: 192.168.1.1 # Router IP
nameservers:
addresses: [8.8.8.8, 8.8.4.4]

Apply: sudo netplan apply

Step 5: Install Docker

# Install Docker
curl -fsSL https://get.docker.com | sudo sh

# Add user to docker group
sudo usermod -aG docker $USER

# Logout and back in for group change
exit
# SSH back in

# Verify
docker --version

Part 2: OvenMediaEngine Setup

Create OME directories

mkdir -p ~/ome/origin_conf
mkdir -p ~/ome/media

Create Server.xml

nano ~/ome/origin_conf/Server.xml

Paste this template (customize camera IPs and credentials):

<?xml version="1.0" encoding="UTF-8"?>
<Server version="8">
<Name>OvenMediaEngine</Name>
<Type>origin</Type>
<IP>*</IP>
<PrivacyProtection>false</PrivacyProtection>
<StunServer>stun.ovenmediaengine.com:13478</StunServer>

<Modules>
<HTTP2><Enable>true</Enable></HTTP2>
<LLHLS><Enable>false</Enable></LLHLS>
<P2P><Enable>false</Enable></P2P>
</Modules>

<Bind>
<Providers>
<RTSPC><WorkerCount>4</WorkerCount></RTSPC>
<WebRTC>
<Signalling>
<Port>3333</Port>
<WorkerCount>1</WorkerCount>
</Signalling>
<IceCandidates>
<!-- TCP only - UDP often blocked -->
<TcpRelay>${env:OME_HOST_IP:*}:3478</TcpRelay>
<TcpForce>true</TcpForce>
<TcpRelayWorkerCount>1</TcpRelayWorkerCount>
</IceCandidates>
</WebRTC>
</Providers>

<Publishers>
<WebRTC>
<Signalling>
<Port>3333</Port>
<WorkerCount>1</WorkerCount>
</Signalling>
<IceCandidates>
<TcpRelay>${env:OME_HOST_IP:*}:3478</TcpRelay>
<TcpForce>true</TcpForce>
<TcpRelayWorkerCount>1</TcpRelayWorkerCount>
</IceCandidates>
</WebRTC>
</Publishers>
</Bind>

<VirtualHosts>
<VirtualHost>
<Name>default</Name>
<Host><Names><Name>*</Name></Names></Host>
<CrossDomains><Url>*</Url></CrossDomains>

<!-- Add camera Origins here -->
<Origins>
<!-- Example: Cam01 -->
<Origin>
<Location>/SiteName/Cam01</Location>
<Pass>
<Scheme>rtsp</Scheme>
<Urls><Url>admin:password@192.168.1.10:554/stream1</Url></Urls>
</Pass>
</Origin>
<!-- Add more cameras... -->
</Origins>

<Applications>
<Application>
<Name>*</Name>
<Type>live</Type>
<Providers><RTSPPull /></Providers>
<OutputProfiles>
<OutputProfile>
<Name>bypass</Name>
<OutputStreamName>${OriginStreamName}</OutputStreamName>
<Encodes>
<Video><Bypass>true</Bypass></Video>
<Audio><Bypass>true</Bypass></Audio>
</Encodes>
</OutputProfile>
</OutputProfiles>
<Publishers>
<WebRTC><Timeout>30000</Timeout></WebRTC>
</Publishers>
</Application>
</Applications>
</VirtualHost>
</VirtualHosts>
</Server>

Important: Replace camera RTSP URLs with actual values. Test RTSP first:

ffplay rtsp://admin:password@192.168.1.10:554/stream1

Part 3A: Fixed WAN IP Deployment

If the site has a static public IP address (e.g., 203.0.113.50):

Router Port Forwarding

Forward these ports to the OME box's internal IP:

  • TCP 3333 → OME box (WebSocket signaling)
  • TCP 3478 → OME box (TURN relay)

Start OME with Public IP

docker run -d --name ome --restart unless-stopped \
--network host \
-e OME_HOST_IP=203.0.113.50 \
-v ~/ome/origin_conf:/opt/ovenmediaengine/bin/origin_conf:Z \
-v ~/ome/media:/opt/ovenmediaengine/media:Z \
airensoft/ovenmediaengine:latest

Replace 203.0.113.50 with the site's actual public IP.

Configure Admin Platform

Add to CCTV page configuration:

const STREAM_MAP = {
sitename: {
baseUrl: 'wss://203.0.113.50:3333', // Direct to site
prefix: 'SiteName',
cameras: ['Cam01', 'Cam02', ...]
}
};

Or proxy through nginx (recommended for SSL):

# In admin.codexdiz.com nginx config
location /ws-sitename/ {
proxy_pass http://203.0.113.50:3333/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}

Then use: wss://admin.codexdiz.com/ws-sitename/SiteName/Cam01


Part 3B: Dynamic IP / CGNAT Deployment

If the site doesn't have a static public IP or is behind carrier-grade NAT:

Free, no port forwarding needed.

  1. Create Cloudflare account and add domain
  2. Install cloudflared on OME box:
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o cloudflared.deb
sudo dpkg -i cloudflared.deb
  1. Authenticate:
cloudflared tunnel login
  1. Create tunnel:
cloudflared tunnel create sitename-ome
  1. Configure tunnel (~/.cloudflared/config.yml):
tunnel: <tunnel-id>
credentials-file: /home/siteadmin/.cloudflared/<tunnel-id>.json

ingress:
- hostname: sitename-ome.yourdomain.com
service: http://localhost:3333
- hostname: sitename-turn.yourdomain.com
service: tcp://localhost:3478
- service: http_status:404
  1. Run tunnel:
cloudflared tunnel run sitename-ome
  1. Install as service:
sudo cloudflared service install

Option 2: WireGuard VPN

Create a VPN between site and admin server.

On admin server (bazzite):

# Install WireGuard
sudo apt install wireguard

# Generate keys
wg genkey | tee privatekey | wg pubkey > publickey

# Create config /etc/wireguard/wg0.conf
[Interface]
Address = 10.200.200.1/24
ListenPort = 51820
PrivateKey = <admin-private-key>

[Peer]
PublicKey = <site-public-key>
AllowedIPs = 10.200.200.2/32

On site OME box:

[Interface]
Address = 10.200.200.2/24
PrivateKey = <site-private-key>

[Peer]
PublicKey = <admin-public-key>
Endpoint = admin.codexdiz.com:51820
AllowedIPs = 10.200.200.1/32
PersistentKeepalive = 25

Then OME streams accessible at 10.200.200.2:3333 from admin server.

Option 3: SSH Reverse Tunnel

Simple but less reliable long-term.

On site box, create persistent reverse tunnel:

# Install autossh
sudo apt install autossh

# Create tunnel (runs OME port 3333 on admin server as port 3334)
autossh -M 0 -f -N -R 3334:localhost:3333 user@admin.codexdiz.com

Starlink and some mobile/rural ISPs use carrier-grade NAT (CGNAT) where many customers share a single public IP. This means:

  • No incoming connections possible
  • Port forwarding doesn't work
  • Even DDNS won't help (no dedicated public IP)
  • The site must connect OUT to you, not the other way around

Architecture: Site Connects to Central Hub

┌──────────────────────────────────────────────────────────────┐
│ Central Hub (You) │
│ Fixed WAN IP: 211.27.58.145 │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ nginx │◄────────│ WireGuard │◄─── VPN Tunnel │
│ │ admin │ │ Server │ from site │
│ └─────────────┘ └─────────────┘ │
│ │ │
│ VPN IP: 10.200.200.1 │
└──────────────────────────────────────────────────────────────┘


VPN Tunnel
(outbound)

┌──────────────────────────────────┴───────────────────────────┐
│ Remote Site (Starlink) │
│ CGNAT IP: 100.x.x.x (not routable) │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ OME │ │ WireGuard │ │
│ │ Docker │ │ Client │ │
│ │ :3333 │ │ 10.200.200.2│ │
│ └─────────────┘ └─────────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ NVR │ │
│ │ Cameras │ │
│ └─────────────┘ │
└──────────────────────────────────────────────────────────────┘

Site connects to your WireGuard server. You can then reach their OME via VPN IP.

On your central server (bazzite):

# Install WireGuard
sudo apt install wireguard

# Generate server keys
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey

# Create config
sudo nano /etc/wireguard/wg0.conf
[Interface]
Address = 10.200.200.1/24
ListenPort = 51820
PrivateKey = <your-server-private-key>

# Site 1 - Starlink location
[Peer]
PublicKey = <site1-public-key>
AllowedIPs = 10.200.200.2/32

# Site 2 - Another location
[Peer]
PublicKey = <site2-public-key>
AllowedIPs = 10.200.200.3/32
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0

On the remote site:

sudo apt install wireguard
wg genkey | tee privatekey | wg pubkey > publickey

sudo nano /etc/wireguard/wg0.conf
[Interface]
Address = 10.200.200.2/24
PrivateKey = <site-private-key>

[Peer]
PublicKey = <your-server-public-key>
Endpoint = 211.27.58.145:51820
AllowedIPs = 10.200.200.0/24
PersistentKeepalive = 25
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0

Then start OME with VPN IP:

docker run -d --name ome ... -e OME_HOST_IP=10.200.200.2 ...

Access from admin: Proxy to 10.200.200.2:3333 via nginx or access directly over VPN.

Option B: Cloudflare Tunnel

See Option 1 above - works well for Starlink sites.

Option C: Dedicated Re-streaming Box

For sites with very poor upload bandwidth, you may need to:

  1. Transcode at the site - Reduce resolution/bitrate before sending
  2. Or re-stream at central - Site pushes low-res, central has beefy GPU to transcode for viewers

This requires:

  • Site OME configured to PUSH streams (not just pull)
  • Central server with transcoding capability
  • More bandwidth and complexity

Starlink typically provides:

  • 50-200 Mbps download
  • 10-20 Mbps upload (variable)
  • 20-40ms latency (good for real-time)

Per camera stream: ~2-4 Mbps 4 cameras streaming simultaneously: 8-16 Mbps upload

This should work on Starlink, but:

  • Limit concurrent viewers
  • Consider lower resolution sub-streams
  • Monitor for upload saturation

Part 4: Management Scripts

Create these scripts on each OME box:

ome-start.sh

#!/bin/bash
PUBLIC_IP="${1:-YOUR_PUBLIC_IP}"
docker run -d --name ome --restart unless-stopped \
--network host \
-e OME_HOST_IP=$PUBLIC_IP \
-v ~/ome/origin_conf:/opt/ovenmediaengine/bin/origin_conf:Z \
-v ~/ome/media:/opt/ovenmediaengine/media:Z \
airensoft/ovenmediaengine:latest

ome-stop.sh

#!/bin/bash
docker stop ome && docker rm ome

ome-logs.sh

#!/bin/bash
docker logs -f ome --tail ${1:-50}

ome-restart.sh

#!/bin/bash
docker restart ome

Make executable:

chmod +x ome-*.sh

Part 5: Camera Requirements

For smooth streaming:

  • Codec: H.264 (NOT H.265/HEVC - requires transcoding)
  • Resolution: 1080p or 4MP recommended
  • Framerate: 15fps sufficient for security
  • Bitrate: 2-4 Mbps per camera
  • Audio: Optional (PCMA codec not supported, use AAC or disable)

Part 6: Testing

Test locally on OME box

# Check OME is running
docker ps | grep ome

# Check logs for errors
docker logs ome | tail -50

# Test RTSP pull is working (look for "stream has been started")
docker logs ome | grep -i "started to play"

Test from admin

Use OvenPlayer demo: https://demo.ovenplayer.com/

Enter: wss://YOUR_OME_ADDRESS:3333/SiteName/Cam01


Troubleshooting

"401 Unauthorized" from camera

  • Check RTSP credentials in Server.xml
  • Don't URL-encode special characters (use ! not %21)
  • Test RTSP URL with ffplay first

ICE timeout / black screen

  • Ensure TCP 3333 and 3478 are accessible
  • Check OME_HOST_IP is set to reachable IP
  • Verify firewall allows incoming connections

Stream not found

  • Check Origin Location matches request path
  • Restart OME after Server.xml changes
  • Check OME logs for "Could not find application"

High latency

  • Check camera is H.264 (not H.265)
  • Reduce camera bitrate
  • Check network bandwidth between site and viewer

Quick Reference

PortProtocolPurpose
3333TCPWebSocket signaling
3478TCPTURN relay (media)
CommandPurpose
docker logs omeView OME logs
docker restart omeRestart OME
docker exec -it ome shShell into container

Last updated: December 2025