Production deployment guide for Network Optimizer.
| Option | Best For | Guide |
|---|---|---|
| Linux + Docker | Self-built servers, VMs, cloud (recommended) | Below |
| Proxmox LXC | Homelab virtualization, one-liner install | Proxmox Guide |
| NAS + Docker | Synology, QNAP, Unraid | NAS Deployment |
| Home Assistant | Add-ons | Home Assistant |
| Windows Installer | Windows desktops/servers | Download from Releases |
| macOS Native | Mac servers, multi-gigabit speed testing | macOS Installation |
| Linux Native | Maximum performance, no Docker | Native Guide |
Deploy on any Linux server using Docker Compose. This is the recommended approach for self-built NAS, home servers, VMs, and cloud instances.
Requirements:
- Docker 20.10+ and Docker Compose 2.0+
- 2GB RAM minimum (4GB recommended)
- 10GB disk space
- Ubuntu 20.04+, Debian 11+, RHEL/CentOS 8+, or compatible
# Install Docker (if not already installed)
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out and back in for group changesChoose a stable location: Deploy to a permanent directory like
/opt/network-optimizer. Avoid home directories or/tmpwhich may cause issues with permissions, cleanup, or migrations.
Option A: Pull Docker Image (Recommended)
# Create directory in /opt (recommended)
sudo mkdir -p /opt/network-optimizer && sudo chown $USER: /opt/network-optimizer
cd /opt/network-optimizer
curl -o docker-compose.yml https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/docker-compose.prod.yml
curl -O https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/.env.example
cp .env.example .env
nano .env # Set timezone and other options (optional)
docker compose up -dOption B: Build from Source
cd /opt # or your preferred stable location
sudo git clone https://github.com/Ozark-Connect/NetworkOptimizer.git
sudo chown -R $USER: NetworkOptimizer
cd NetworkOptimizer/docker
cp .env.example .env
nano .env # Set timezone and other options (optional)
docker compose build
docker compose up -dVerify Installation:
# Check logs for the auto-generated admin password
docker logs network-optimizer 2>&1 | grep -A5 "AUTO-GENERATED"
# Verify health
docker compose ps
curl http://localhost:8042/api/healthAccess at: http://your-server:8042
Host Networking (Recommended for Linux):
# docker-compose.yml uses network_mode: host by default
# This provides best performance and accurate IP detectionBridge Networking (if host mode unavailable):
# Use docker-compose.macos.yml which uses port mapping
# IMPORTANT: Set HOST_IP in .env to your server's IP for accurate path analysis
docker compose -f docker-compose.macos.yml up -d# View logs
docker compose logs -f
# Restart
docker compose restart
# Stop
docker compose down
# Update to latest
docker compose pull
docker compose up -d
# Full rebuild (after Dockerfile changes)
docker compose build --no-cache
docker compose up -d# Enable Docker to start on boot
sudo systemctl enable docker
# Docker Compose containers with restart: unless-stopped will auto-startOr create a dedicated systemd service:
sudo cat > /etc/systemd/system/network-optimizer.service << 'EOF'
[Unit]
Description=Network Optimizer
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/network-optimizer/docker
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable network-optimizerThe easiest way to deploy on Proxmox. Run this one-liner on your Proxmox VE host:
bash -c "$(wget -qLO - https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/proxmox/install.sh)"The interactive script will:
- Create a privileged Debian LXC container
- Install Docker and Docker Compose
- Deploy Network Optimizer with Docker Compose
- Optionally deploy a Traefik HTTPS proxy with automatic Let's Encrypt certificates (requires Cloudflare DNS)
- Configure auto-start on boot
Requirements:
- Proxmox VE 7.0 or later
- 10GB disk space, 2GB RAM minimum
- Internet access for downloading images
After Installation:
# Get the auto-generated admin password
pct exec <CT_ID> -- docker logs network-optimizer 2>&1 | grep -A5 "AUTO-GENERATED"
# Access the web UI
http://<container-ip>:8042For advanced configuration, troubleshooting, and manual installation see the full Proxmox guide.
For commercial NAS devices with container support.
- Install Container Manager from Package Center
- Clone or upload the repository to
/docker/network-optimizer - Copy
.env.exampleto.envand configure - Create project in Container Manager pointing to docker-compose.yml
- Start containers
Note: If using bridge networking, set HOST_IP in .env to your NAS IP address.
- Install Container Station
- Create shared folders
- Import
docker-compose.yml - Configure environment variables
- Deploy stack
- Install Community Applications plugin
- Search for "Network Optimizer"
- Deploy both network-optimizer and network-optimizer-speedtest containers
Community templates maintained by @stefan-matic.
Or use manual Docker Compose deployment (note: cannot be managed by Unraid GUI if deployed via compose)
For maximum network performance or systems without Docker, run natively on the host.
Best for:
- macOS systems (avoids Docker Desktop's ~1.8 Gbps network throughput limitation)
- Systems where Docker overhead is undesirable
- Dedicated appliances
Supported Platforms:
- macOS 11+ (Intel or Apple Silicon)
- Linux (Ubuntu 20.04+, Debian 11+, RHEL 8+)
- Windows: Use the Windows Installer instead
See Native Deployment Guide for macOS and Linux instructions.
Network Optimizer can be installed as two Home Assistant add-ons. See issue #201 for setup instructions and discussion.
For the initial admin password, check the add-on's Log tab instead of using the docker logs command.
- Docker and Docker Compose installed
- Sufficient disk space (10GB minimum)
- Network access to UniFi Controller
- Firewall rules configured (if applicable)
-
.envfile configured with secure passwords - SSL certificates ready (if using HTTPS)
- SSH enabled on UniFi devices (required for SQM and LAN speed testing, see below)
These detailed steps are for NAS deployment. For other deployment options, see the guides above.
Note: If
docker composedoesn't work on older NAS firmware, trydocker-compose(hyphenated).
Choose a stable location: Deploy to a permanent directory like
/volume1/docker/network-optimizer(Synology) or equivalent. Avoid temporary locations that may be cleaned up or have permission issues.
Option A: Pull Docker Image (Recommended)
mkdir network-optimizer && cd network-optimizer
curl -o docker-compose.yml https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/docker-compose.prod.yml
curl -O https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/.env.exampleOption B: Build from Source
git clone https://github.com/Ozark-Connect/NetworkOptimizer.git
cd NetworkOptimizer/docker# Copy template
cp .env.example .env
# Edit with your settings
nano .envRecommended changes:
# Set your timezone
TZ=America/ChicagoAdmin Password:
On first run, an auto-generated password is displayed in the logs. After logging in, go to Settings > Admin Password to set your own password (recommended).
Password precedence: Database (Settings UI) > APP_PASSWORD env var > Auto-generated
Optionally, set APP_PASSWORD in .env if you want to configure a password before first login.
docker compose up -d# Check service health
docker compose ps
# View logs
docker compose logs -f
# Test health endpoint
curl http://localhost:8042/api/healthExpected output:
NAME STATUS
network-optimizer Up (healthy)
- Web UI: http://your-server:8042
Use nginx, Caddy, or Traefik for SSL termination.
If the reverse proxy is on the same host, add to your .env:
BIND_LOCALHOST_ONLY=trueThis binds the app to 127.0.0.1:8042 instead of all interfaces, so only the local proxy can access it.
If you use the browser-based speed test (OpenSpeedTest), Traefik is the recommended reverse proxy. Most proxies negotiate HTTP/2 at the TLS level, and HTTP/2 multiplexing interferes with speed test throughput measurements. Traefik's per-router TLS options let you force HTTP/1.1 for the speed test hostname while keeping HTTP/2 for the main app - all on one port 443.
See NetworkOptimizer-Proxy for a ready-to-use Docker Compose setup with automatic Let's Encrypt certificates via Cloudflare DNS-01.
Proxmox users: The Proxmox LXC installer can set up Traefik automatically during installation.
Windows users: Traefik is available as an optional feature in the MSI installer.
# /etc/nginx/sites-available/network-optimizer
server {
listen 80;
server_name network-optimizer.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name network-optimizer.example.com;
ssl_certificate /etc/letsencrypt/live/network-optimizer.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/network-optimizer.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Blazor Web UI
location / {
proxy_pass http://localhost:8042;
proxy_http_version 1.1;
# WebSocket support for Blazor
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_set_header X-Forwarded-Proto $scheme;
# Timeouts for long-running operations
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}Enable and restart:
sudo ln -s /etc/nginx/sites-available/network-optimizer /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx# /etc/caddy/Caddyfile
network-optimizer.example.com {
reverse_proxy localhost:8042
}Restart Caddy:
sudo systemctl reload caddy# Allow SSH
sudo ufw allow 22/tcp
# Allow HTTP/HTTPS (if using reverse proxy)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Or allow direct access to the web UI
sudo ufw allow 8042/tcp # Web UI
sudo ufw enablesudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-port=8042/tcp
sudo firewall-cmd --reloadCreate backup script:
#!/bin/bash
# /usr/local/bin/backup-network-optimizer.sh
BACKUP_DIR=/backups/network-optimizer
DATE=$(date +%Y%m%d-%H%M%S)
# Create backup directory
mkdir -p $BACKUP_DIR
# Backup SQLite data and configuration
tar czf $BACKUP_DIR/data-$DATE.tar.gz -C /path/to/docker data/
# Cleanup old backups (keep last 7 days)
find $BACKUP_DIR -type f -mtime +7 -delete
echo "Backup completed: $DATE"Add to crontab:
# Daily backup at 2 AM
0 2 * * * /usr/local/bin/backup-network-optimizer.sh >> /var/log/network-optimizer-backup.log 2>&1# Stop services
docker compose down
# Restore data
tar xzf /backups/network-optimizer/data-20240101-020000.tar.gz -C /path/to/docker/
# Start services
docker compose up -dUse Docker healthchecks:
# Check all services
watch docker compose ps
# Monitor resource usage
docker statsCentralized logging with rsyslog or similar:
# docker-compose.yml addition
logging:
driver: syslog
options:
syslog-address: "udp://your-syslog-server:514"
tag: "network-optimizer"Use external monitoring:
- UptimeRobot
- Healthchecks.io
- Self-hosted Uptime Kuma
Configure health check endpoint:
# Monitor this endpoint
http://your-server:8042/api/healthAdd resource constraints for production:
# docker-compose.override.yml
services:
network-optimizer:
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '1.0'
memory: 1G
restart: alwaysApply with:
docker compose up -dControl log verbosity via environment variables in .env:
# General framework logging (Microsoft, EF Core, ASP.NET, etc.)
LOG_LEVEL=Information
# Network Optimizer application logging
APP_LOG_LEVEL=DebugLog Levels (least to most verbose): Critical, Error, Warning, Information, Debug, Trace
Common configurations:
| Scenario | LOG_LEVEL | APP_LOG_LEVEL |
|---|---|---|
| Production (default) | Information | Information |
| Debugging app issues | Information | Debug |
| Full diagnostics | Debug | Debug |
After changing .env, recreate the container to apply:
docker compose down && docker compose up -dNote: docker compose restart does NOT reload environment variables. You must recreate the container.
View logs:
# Follow logs
docker compose logs -f network-optimizer
# Last 100 lines
docker compose logs --tail=100 network-optimizerOn Windows, logs are written to <install-dir>\logs\networkoptimizer-YYYY-MM-DD.log (rolling daily, 7-day retention).
To change log levels, set environment variables on the Windows service via the registry. This avoids modifying any config files.
Enable debug logging for Network Optimizer:
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\NetworkOptimizer"
$existing = (Get-ItemProperty $regPath -Name Environment -ErrorAction SilentlyContinue).Environment
$env = [string[]](@($existing | Where-Object { $_ }) + "Logging__LogLevel__NetworkOptimizer=Debug")
Set-ItemProperty $regPath -Name Environment -Value $env
Restart-Service NetworkOptimizerEnable debug logging for Traefik (HTTPS certificate issues):
If HTTPS isn't working after a couple minutes (certificate errors in the browser), enable Traefik debug logging to see why certificate issuance is failing. Traefik runs as a child process and its output is captured into the app log. You need both the Traefik log level (controls what Traefik emits) and the app log level (controls what gets written to the log file):
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\NetworkOptimizer"
$existing = (Get-ItemProperty $regPath -Name Environment -ErrorAction SilentlyContinue).Environment
$env = [string[]](@($existing | Where-Object { $_ }) + "Logging__LogLevel__NetworkOptimizer=Debug")
Set-ItemProperty $regPath -Name Environment -Value $env
# Also set Traefik's own log level to DEBUG (this is separate from the app log level)
Set-ItemProperty -Path "HKLM:\SOFTWARE\Ozark Connect\Network Optimizer" -Name "TRAEFIK_LOG_LEVEL" -Value "DEBUG"
Restart-Service NetworkOptimizerRemove debug logging when done:
# Remove service environment variables
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\NetworkOptimizer"
$env = [string[]]((Get-ItemProperty $regPath -Name Environment).Environment | Where-Object { $_ -notlike "Logging__*" })
if ($env.Count -gt 0) {
Set-ItemProperty $regPath -Name Environment -Value $env
} else {
Remove-ItemProperty $regPath -Name Environment -ErrorAction SilentlyContinue
}
# Reset Traefik log level
Set-ItemProperty -Path "HKLM:\SOFTWARE\Ozark Connect\Network Optimizer" -Name "TRAEFIK_LOG_LEVEL" -Value "INFO"
Restart-Service NetworkOptimizerView logs:
# Follow the current log file
Get-Content "<install-dir>\logs\networkoptimizer-*.log" -Tail 50 -WaitIf you deployed using the pre-built Docker image:
cd /path/to/network-optimizer
docker compose pull
docker compose up -dIf you cloned the repository and build locally:
cd /path/to/NetworkOptimizer
git fetch origin
git checkout main
git pull
cd docker && docker compose build && docker compose up -dFor significant updates (major version changes or Dockerfile modifications), use --no-cache:
docker compose build --no-cache
docker compose up -dDownload the latest MSI from GitHub Releases and run it. The installer upgrades in-place, preserving your database, settings, and encryption keys. The Network Optimizer service restarts automatically after the upgrade.
cd NetworkOptimizer
git pull
./scripts/install-macos-native.shThe install script preserves your database, encryption keys, and start.sh configuration by backing them up before reinstalling. See the macOS Installation Guide for details.
docker compose ps
docker compose logs -fIf you've been building from source and want to switch to the pre-built Docker images:
Why migrate? Pre-built images are faster to update (no build step), tested before release, and don't require the full git repository.
Important: When you build locally, Docker tags your image as ghcr.io/ozark-connect/network-optimizer:latest. Simply running docker compose pull won't overwrite this because the compose file has a build: directive. You need to force the pull and switch to the production compose file.
cd /opt/network-optimizer # or wherever you deployed
# Stop running containers
docker compose down
# Force pull registry images (overwrites locally-built images)
docker pull ghcr.io/ozark-connect/network-optimizer:latest
docker pull ghcr.io/ozark-connect/speedtest:latest
# Back up your current compose file (optional)
mv docker-compose.yml docker-compose.yml.build-backup
# Download the production compose file (no build directives)
curl -o docker-compose.yml https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/docker/docker-compose.prod.yml
# Start with pre-built images
docker compose up -d
# Optional: clean up old build cache to free disk space
docker builder prune -fYour data/, logs/, and .env files are preserved. Future updates are now just:
docker compose pull && docker compose up -dIf you've forgotten your password or need to reset it, use the reset script:
curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/reset-password.sh | bashOr download and run it (useful inside Proxmox LXC or restricted environments):
curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/reset-password.sh -o reset-password.sh
bash reset-password.shThe script auto-detects your Docker container, clears the password, restarts, and displays the new temporary password.
Manual fallback (if you prefer not to use the script):
# Clear the password from the database
docker exec network-optimizer sqlite3 /app/data/network_optimizer.db \
"UPDATE AdminSettings SET Password = NULL, Enabled = 0;"
# Restart to trigger auto-generated password
docker restart network-optimizer
# View the new auto-generated password
docker logs network-optimizer 2>&1 | grep -A5 "AUTO-GENERATED"# Check logs for errors
docker compose logs network-optimizer
# Common issues:
# - Port 8042 already in use: stop conflicting service or change port
# - Permission denied on data directory: check ownership of mounted volumes
# - Out of disk space: df -h- Verify the controller URL is correct (include https:// and port if non-standard)
- Ensure you're using a Local Access Only account, not Ubiquiti SSO (see UniFi Account setup)
- Check network connectivity:
curl -k https://your-controller:443 - For self-signed certificates, enable "Ignore SSL errors" in Settings
# Test SSH manually from the container
docker exec -it network-optimizer ssh username@gateway-ip
# Common issues:
# - SSH not enabled on device (see UniFi SSH Configuration section)
# - Wrong credentials
# - Firewall blocking port 22
# - Host key verification (container may need to accept new host keys)Blazor Server uses WebSocket connections. If the UI shows "Reconnecting..." or won't load:
- Check that your reverse proxy supports WebSockets (see nginx/Caddy examples above)
- Ensure proxy timeouts are sufficient (60s+)
- Check browser console for connection errors
The SQLite database is stored in the data/ volume. If you encounter database errors:
# Check database file exists and has correct permissions
docker exec network-optimizer ls -la /app/data/
# View recent application logs
docker compose logs --tail=100 network-optimizerThe .env file and SQLite database contain sensitive information:
# Restrict .env file permissions
chmod 600 .env
# Data directory contains the database with stored credentials
chmod 700 data/Network Optimizer stores UniFi controller credentials and SSH passwords. Limit access to the web UI:
- Use a reverse proxy with authentication if exposing beyond your local network
- Consider firewall rules to restrict access to trusted IPs
- Use HTTPS via reverse proxy (see examples above)
Network Optimizer supports UniFi OS devices (UDM, UCG, UDR, Cloud Key) and self-hosted UniFi Network Server installations.
Create a dedicated Local Access Only account on your UniFi controller for Network Optimizer. Ubiquiti SSO accounts will not work.
Quick Setup: Create a Local Access Only account with Super Admin role.
Restricted Setup (recommended):
- Open UniFi Network:
https://<gateway-ip>orhttps://unifi.ui.com - Click Admin & Users at the bottom of the side menu
- Click Create New → Create New User
- Enter a name and email for this service account
- Check Admin and Restrict to Local Access Only
- Uncheck Use a Predefined Role and set:
- Network: View Only
- Protect: View Only
- User & Account Management: None
- Set a secure password and save
Use this username and password in Network Optimizer Settings.
- GitHub Issues: https://github.com/Ozark-Connect/NetworkOptimizer/issues
- Email: tj@ozarkconnect.net
SSH access is required for some features but not others. Here's what needs what:
| Feature | Gateway SSH | Device SSH |
|---|---|---|
| Adaptive SQM | Required | - |
| WAN Speed Test (gateway-based) | Required | - |
| WAN Speed Test (server-based) | - | - |
| LAN Speed Test (gateway) | Required | - |
| LAN Speed Test (devices) | - | Required |
| Client Speed Test | - | - |
| Security Audit | - | - |
| Config Optimizer | - | - |
| Wi-Fi Optimizer | - | - |
Important: Both SSH settings must be configured via the UniFi Network web interface. These options are not available in the iOS or Android UniFi apps.
Enables SSH access to Cloud Gateways (UCG, UDM, UDM Pro, etc.):
- Open UniFi Network:
https://<gateway-ip>orhttps://unifi.ui.com - Sign in to your Console
- Click Settings on the bottom portion of the side menu
- Navigate to Control Plane → Console
- Enable SSH and set a secure password
Use root as the username and the password you set above.
For UXG (non-Cloud Gateway): Enable SSH using the Device SSH steps below, but enter those credentials in Network Optimizer's Gateway SSH settings.
Enables SSH access to adopted devices (switches, access points, modems):
- Open UniFi Network:
https://<gateway-ip>orhttps://unifi.ui.com - Sign in to your Console
- Click UniFi Devices on the side menu
- In the left-hand filter menu, select Device Updates and Settings at the bottom
- Expand Device SSH Settings at the bottom
- Check Device SSH Authentication
- Set a username and secure password (optionally add SSH public keys)
- Save
Note: This is a separate credential from Gateway SSH.
Once SSH is enabled in UniFi, enter the same credentials in Network Optimizer's Settings page.
- Go to Settings → Gateway SSH
- Enter your gateway's IP address, username (
root), and the SSH password you set in UniFi - Click Test SSH Connection to verify connectivity
- Click Check iperf3 Status to confirm iperf3 is available for speed tests
As an alternative to password authentication, you can provide a Private Key Path (e.g., /app/ssh-keys/gateway_key). Leave the password blank when using key-based authentication.
- Go to Settings → Device SSH
- Enter the username and password you configured in UniFi's Device SSH Settings
- Click Test SSH Connection - it will automatically find a device on your network to test against
Private key authentication is also supported. Enter the key path (e.g., /app/ssh-keys/id_rsa) and leave the password blank.
In LAN Speed Test, when you add a custom speed test device and check Start iperf3 server before test, you can override the global Device SSH credentials for that specific device. Override fields include username, password, and private key path. Leave any field blank to fall back to the global Device SSH settings.
This is useful for non-UniFi equipment or devices with different credentials.
If SSH connections are failing:
- Check credentials - Use the Test SSH Connection button in Settings to verify your credentials are correct
- Check UniFi firewall rules - Ensure SSH traffic is allowed between the Network Optimizer server and your gateway/devices
- Check CyberSecure IPS - SSH connections may be blocked by the rule "ET SCAN Potential SSH Scan OUTBOUND". Look for blocked connections in Insights → Flows, then create a Suppression for this signature in the Logs section
Enable speed testing from any device on your LAN (phones, tablets, laptops, IoT devices) without requiring SSH access.
Two methods are available:
| Method | Best For | Port |
|---|---|---|
| OpenSpeedTest™ | Browser-based testing from any device | 3005 (configurable) |
| iperf3 Server | CLI testing with iperf3 clients | 5201 |
Results from both methods are stored in Network Optimizer and visible in the Client Speed Test page.
Why separate containers? OpenSpeedTest runs as its own container (not proxied through Network Optimizer) for performance reasons. Speed tests can push massive bandwidth (multi-gigabit to 100 Gbps on high-end networks), and routing that traffic through a reverse proxy or the .NET application would add overhead and reduce accuracy. The only data sent to Network Optimizer is the small JSON result payload after the test completes.
Bundled as part of the Docker Compose stack. Access at http://your-server:3005.
Configuration (in .env):
# Main app identity (feel free to omit N/A settings)
HOST_IP=192.168.1.100 # Optional - for path analysis if auto-detection fails
HOST_NAME=nas # Optional - friendly hostname (requires DNS)
REVERSE_PROXIED_HOST_NAME=... # Optional - if main app is behind HTTPS proxy
# SpeedTest-specific (feel free to omit N/A settings)
OPENSPEEDTEST_PORT=3005 # Optional - change if port 3005 conflicts
OPENSPEEDTEST_HOST=speedtest.local # Optional - if speedtest uses different hostname than main app
OPENSPEEDTEST_HTTPS=true # Optional - if speedtest is behind TLS proxy (for geolocated speed test result map)
OPENSPEEDTEST_HTTPS_PORT=443 # Optional - HTTPS port if not 443See .env.example for full documentation on each setting.
Usage:
- Open
http://your-server:3005from any device on your network - Run the speed test
- Results automatically appear in Network Optimizer's Client Speed Test page
When serving OpenSpeedTest over HTTPS (OPENSPEEDTEST_HTTPS=true), the main Network Optimizer app must also be accessible via HTTPS. This is a browser security requirement - HTTPS pages cannot make requests to HTTP endpoints (mixed active content).
Valid Configurations:
| Speedtest Protocol | Main App Protocol | Configuration Required |
|---|---|---|
| HTTP | HTTP | HOST_NAME or HOST_IP |
| HTTP | HTTPS | REVERSE_PROXIED_HOST_NAME |
| HTTPS | HTTPS | OPENSPEEDTEST_HTTPS=true + REVERSE_PROXIED_HOST_NAME |
| HTTPS | HTTP | ❌ Not supported (browser blocks mixed content) |
Example - Both behind HTTPS reverse proxy:
HOST_NAME=nas
REVERSE_PROXIED_HOST_NAME=optimizer.example.com
OPENSPEEDTEST_HOST=speedtest.example.com
OPENSPEEDTEST_HTTPS=trueIf you see this error in browser console:
Blocked loading mixed active content "http://..."
It means your speedtest is HTTPS but trying to POST results to an HTTP endpoint. Set REVERSE_PROXIED_HOST_NAME to fix.
Run iperf3 as a server inside the Network Optimizer container for CLI-based testing.
Enable in .env:
IPERF3_SERVER_ENABLED=trueUsage from client devices:
# Upload test (client to server, 4 streams)
iperf3 -c your-server -P 4
# Download test (server to client, 4 streams)
iperf3 -c your-server -P 4 -R
# Bidirectional test (runs both directions simultaneously)
iperf3 -c your-server -P 4 --bidirResults are captured automatically and stored with client IP identification.
Before enabling these features, check for existing services using the same ports:
# Check for iperf3 server already running
sudo netstat -tlnp | grep 5201
# or
sudo ss -tlnp | grep 5201
# Check for existing services on port 3005
sudo netstat -tlnp | grep 3005
docker ps | grep -E "3000|3005"Common conflicts:
| Port | Service | Resolution |
|---|---|---|
| 5201 | Existing iperf3 server | Stop: sudo systemctl stop iperf3 |
| 3005 | OpenSpeedTest port conflict | Set OPENSPEEDTEST_PORT=3006 (or another free port) in .env |
Container name conflicts:
The bundled OpenSpeedTest uses container name openspeedtest. If you have an existing container with this name:
# Remove existing container
docker stop openspeedtest && docker rm openspeedtest
# Then start the Network Optimizer stack
docker compose up -dDeploy an OpenSpeedTest instance to a remote server (VPS, cloud VM, etc.) to let clients test their internet (WAN) speed from any device on your network. Results are automatically posted back to your Network Optimizer instance.
How it works: The client's browser connects to the remote speed test server. Traffic flows: client → your WAN → internet → remote server → internet → your WAN → client. The result is posted back to Network Optimizer with a server identifier, and stored as a WAN speed test result.
Requirements:
- A remote server with Docker (any cloud VPS works)
- Port 3005 (or your chosen port) open on the remote server
- HTTPS on the external server (required - see note below)
Why HTTPS? Modern browsers enforce Private Network Access rules. The speed test page is served from a public IP, but the browser (on your LAN) posts results back to Network Optimizer (a private IP). Browsers block this unless the page origin is HTTPS (a secure context).
Quick deploy (run on the remote server):
curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/deploy-external-speedtest.sh | bash -s -- https://optimizer.example.com my-server-name 3005Or manually:
git clone --depth 1 https://github.com/Ozark-Connect/NetworkOptimizer.git /opt/netopt-speed-test
cd /opt/netopt-speed-testCreate docker-compose.yml:
services:
speedtest:
build:
context: .
dockerfile: docker/openspeedtest/Dockerfile
container_name: netopt-wan-speedtest
restart: unless-stopped
ports:
- "3005:3000"
environment:
- REVERSE_PROXIED_HOST_NAME=optimizer.example.com
- EXTERNAL_SERVER_ID=my-server-namedocker compose up -dThen in Network Optimizer Settings:
- Go to Settings → External Speed Test Server
- Enter the remote server's hostname, port, and scheme (HTTPS)
- Give it a friendly name (e.g., "Chicago VPS")
- Save - this enables CORS for the remote server and populates the Client WAN Speed Test page
Setting up HTTPS: If you already have a reverse proxy for Network Optimizer and its LAN speed test server (e.g., Traefik or Caddy), you can add a route for the external speed test hostname pointing to your VPS - no need to install a separate proxy on the remote server. The reverse proxy must force HTTP/1.1 for accurate speed test results (HTTP/2 multiplexing interferes with throughput measurement).
- Traefik - supports per-route HTTP/1.1 TLS options. Add a route like
speedtest-wan.example.compointing to your VPS on port 3005 with the same HTTP/1.1 config used for your LAN speed test. - Caddy - automatic Let's Encrypt, simple configuration. Note: Caddy negotiates HTTP/2 by default at the TLS level. For the most accurate speed test results, configure it to use HTTP/1.1 for the speed test hostname.
Then update the external server settings in Network Optimizer to use https scheme and port 443.
To disable client speed testing components:
# Disable iperf3 server (default)
IPERF3_SERVER_ENABLED=false
# To completely disable OpenSpeedTest, comment it out in docker-compose.yml
# or use a custom override fileAfter deployment:
- Access web UI and complete initial setup
- Connect to UniFi Controller
- Configure SSH access for gateway and devices (see above)
- Run security audit
- Configure SQM settings (if applicable)
- Set up client speed testing (optional, see above)
See main documentation for feature guides.