CCTV OME for Sites
Guide for deploying OvenMediaEngine at remote sites to stream CCTV feeds to the central admin platform.
Overview
Each site needs:
- A Linux box running Docker + OvenMediaEngine
- Network access to local cameras (RTSP)
- 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:
- Download Rufus: https://rufus.ie/
- Insert USB drive
- Select the Ubuntu ISO
- Click Start
- Wait for completion
Step 3: Install Ubuntu Server
- Insert USB into target PC
- Boot from USB (usually F12, F2, or DEL at startup for boot menu)
- Select "Install Ubuntu Server"
- 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
- 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:
Option 1: Cloudflare Tunnel (Recommended)
Free, no port forwarding needed.
- Create Cloudflare account and add domain
- 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
- Authenticate:
cloudflared tunnel login
- Create tunnel:
cloudflared tunnel create sitename-ome
- 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
- Run tunnel:
cloudflared tunnel run sitename-ome
- 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
Part 3C: Starlink / Heavy CGNAT Scenarios
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 │ │
│ └─────────────┘ │
└──────────────────────────────────────────────────────────────┘
Option A: WireGuard VPN (Recommended)
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:
- Transcode at the site - Reduce resolution/bitrate before sending
- 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
Bandwidth Considerations for Starlink
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_IPis 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
| Port | Protocol | Purpose |
|---|---|---|
| 3333 | TCP | WebSocket signaling |
| 3478 | TCP | TURN relay (media) |
| Command | Purpose |
|---|---|
docker logs ome | View OME logs |
docker restart ome | Restart OME |
docker exec -it ome sh | Shell into container |
Last updated: December 2025